9 Request
Throughout the previous chapters, you’ve probably read this over and over again:
The
reqobject is a list which contains everything you need to know about the incoming HTTP request.
And I just made you re-read it again. Because that’s exactly what the req object is.
library(ambiorix)
app <- Ambiorix$new()
app$get("/", function(req, res){
print(req)
res$send("check your R console.")
})
app$start()── A Request
• HEADERS: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8, gzip, deflate, br, zstd, en-US,en;q=0.9,
keep-alive, 127.0.0.1:7210, u=0, i, document, navigate, none, ?1, 1, and Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:147.0)
Gecko/20100101 Firefox/147.0
• HTTP_ACCEPT: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
• HTTP_ACCEPT_ENCODING: "gzip, deflate, br, zstd"
• HTTP_ACCEPT_LANGUAGE: "en-US,en;q=0.9"
• HTTP_CACHE_CONTROL:
• HTTP_CONNECTION: "keep-alive"
• HTTP_COOKIE:
• HTTP_HOST: "127.0.0.1:7210"
• HTTP_SEC_FETCH_DEST: "document"
• HTTP_SEC_FETCH_MODE: "navigate"
• HTTP_SEC_FETCH_SITE: "none"
• HTTP_SEC_FETCH_USER: "?1"
• HTTP_UPGRADE_INSECURE_REQUESTS: "1"
• HTTP_USER_AGENT: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:147.0) Gecko/20100101 Firefox/147.0"
• httpuv.version 1.6.16
• PATH_INFO: "/"
• QUERY_STRING: ""
• REMOTE_ADDR: "127.0.0.1"
• REMOTE_PORT: "55520"
• REQUEST_METHOD: "GET"
• SCRIPT_NAME: ""
• SERVER_NAME: "127.0.0.1"
• SERVER_PORT: "127.0.0.1"
• CONTENT_LENGTH:
• CONTENT_TYPE:
• HTTP_REFERER:
• rook.version: "1.1-0"
• rook.url_scheme: "http"Internally, req is a mutable list built on top of the {httpuv} and {Rook} specifications.
Ambiorix does not abstract it away or “sanitize” it into a new object. What you see is what came off the wire, plus a few conveniences added by Ambiorix.
Here, we shall explore some of its more important bits.
9.1 Query string parameters
req$query is a parsed, named list derived from QUERY_STRING. It contains the URL query parameters, extracted from the URL after the ? character.
9.1.1 Example: Basic Filtering
# URL: /products?category=books&limit=10
app$get("/products", function(req, res) {
category <- req$query$category %||% "fruits"
limit <- req$query$limit %||% "20"
limit <- as.integer(limit)
res$send(paste("Category:", category, "| Limit:", limit))
})Notes:
- Values are always strings. Ambiorix does not coerce types for you.
- Accessing a value which does not exist in
req$queryreturnsNULL, which is why we set default values forcategoryandlimit.
9.1.2 Handling Duplicate Keys
Unlike some frameworks that collapse duplicates, Ambiorix preserves them. If a query string contains the same key multiple times, req$query will contain multiple entries with that name.
# /filter?tag=r&tag=golang&page=10
req$query
# $tag
# [1] "r"
#
# $tag
# [1] "golang"
#
# $page
# [1] "10"So in this case, req$query is identical to this:
list(
tag = "r",
tag = "golang",
page = "10"
)Here’s a reprex on how you might handle such a case:
library(ambiorix)
app <- Ambiorix$new()
app$get("/", function(req, res) {
res$send("handling duplicate keys")
})
# URL: /filter?tag=r&tag=golang&page=10
app$get("/filter", function(req, res) {
qp <- req$query
print(qp)
tags <- qp[names(qp) == "tag"]
others <- qp[names(qp) != "tag"]
out <- list(
tags = unlist(tags)
)
out <- c(out, others)
res$json(out)
})
app$start(port = 3000L)9.2 Path parameters
Path parameters are dynamic segments of a URI, defined in your routes with a colon (:). Ambiorix parses these into req$params.
# URL: /users/52/posts/xyz
app$get("/users/:id/posts/:post_id", function(req, res) {
user_id <- req$params$id
# path parameters are ALWAYS strings:
user_id <- as.integer(user_id)
post_id <- req$params$post_id
out <- list(
user_id = user_id,
post_id = req$params$post_id
)
res$json(out)
})Like query parameters, path parameters are strings. Type conversion is your responsibility.
9.3 Bind variables
Because the req object is a mutable list, you can attach custom data to it. This is usually done in middleware so that those variables are available to the next handlers.
9.3.1 Example: Authentication Middleware
library(ambiorix)
app <- Ambiorix$new()
app$use(function(req, res) {
# set
req$user <- list(
uid = 10L,
first_name = "John",
last_name = "Coene",
status = "active"
)
})
app$get("/", function(req, res) {
# get
out <- list(user = req$user)
res$json(out)
})
app$start(port = 3000L)9.3.2 Example: Request Processing Time
library(ambiorix)
app <- Ambiorix$new()
app$get("/", function(req, res) {
res$send("Request processing time")
})
# middleware to "trace" the request start time
app$use(function(req, res) {
req$start_time <- Sys.time()
})
app$get("/ping", function(req, res) {
# pretend this is a time consuming calculation:
Sys.sleep(3)
duration <- Sys.time() - req$start_time
duration <- format(x = duration, format = "%S")
out <- paste("Request processed in:", duration)
res$send(out)
})
app$start(port = 3000L)9.4 Parsers
Ambiorix does not auto-parse request bodies.
There is no magic “body” field in req. You opt in by calling a parser. This avoids accidental parsing, ambiguous content-types, and wasted work.
Ambiorix provides built-in parsers to handle different types of request body data. These parsers make it easy to extract and work with data sent from forms, JSON APIs, and file uploads.
9.4.1 JSON Parser
Use parse_json() to parse JSON data from request bodies:
library(ambiorix)
app <- Ambiorix$new()
app$post("/api/data", function(req, res) {
data <- parse_json(req)
print(data)
res$json(list(received = data))
})
app$start()9.4.2 Form URL-Encoded Parser
Use parse_form_urlencoded() to parse standard HTML form data:
app$post("/form", function(req, res) {
form_data <- parse_form_urlencoded(req)
print(form_data$name) # Access form field by name
res$send("Form received!")
})9.4.3 Multipart Form Data Parser
Use parse_multipart() to handle multipart form data, including file uploads:
app$post("/upload", function(req, res) {
data <- parse_multipart(req)
# Handle regular form fields
print(data$username)
# Handle file uploads
if ("file" %in% names(data)) {
file_info <- data$file
# file_info contains:
# - value: Raw vector of file contents
# - content_type: MIME type (e.g., "image/png")
# - filename: Original filename
# - name: Form field name
# Save uploaded file
temp_path <- tempfile()
writeBin(file_info$value, temp_path)
}
res$send("Upload processed!")
})9.4.4 Custom Parsers
You can override the default parsers by setting global options:
# Custom JSON parser using jsonlite
my_json_parser <- function(body, ...) {
txt <- rawToChar(body)
jsonlite::fromJSON(txt, ...)
}
options(AMBIORIX_JSON_PARSER = my_json_parser)
# Custom multipart parser
options(AMBIORIX_MULTIPART_FORM_DATA_PARSER = my_multipart_parser)
# Custom form URL-encoded parser
options(AMBIORIX_FORM_URLENCODED_PARSER = my_form_parser)Custom parser functions must accept:
body: Raw vector containing the request datacontent_type: Content-Type header (for multipart parser only)...: Additional optional parameters