Overview
The handler
function is responsible for transforming a
request (req
) into a response (resp
).
HTTP Request
If the HTTP request is:
POST /dir/file.txt?c=world HTTP/1.1
Host: localhost:8080
User-Agent: httr2/1.1.0 r-curl/6.2.0 libcurl/8.10.1
Accept: */*
Accept-Encoding: deflate, gzip
Cookie: token=abc
x-session-id: 123
Content-Type: application/json
Content-Length: 25
{"a":[1,1,3],"b":"hello"}
Then as.list(req)
will be:
list(
ARGS = list(a = c(1L, 1L, 3L), b = "hello", c = "world"),
COOKIES = list(token = "abc"),
HEADERS = c(
`content-type` = "application/json",
host = "localhost:8080",
`user-agent` = "httr2/1.1.0 r-curl/6.2.0 libcurl/8.10.1",
`x-session-id` = "123" ),
PATH_INFO = "/dir/file.txt",
REMOTE_ADDR = "127.0.0.1",
REQUEST_METHOD = "POST",
SERVER_NAME = "127.0.0.1",
SERVER_PORT = "8080" )
Also good to know:
-
req
is a bare environment. -
parent.env(req)
isemptyenv()
.
HTTP Response
Simple
A list will be encoded as JSON.
wq <- WebQueue$new(handler = ~{ list(r = 2, d = 2) })
httr2::request('http://localhost:8080') |>
httr2::req_perform() |>
httr2::resp_raw()
#> HTTP/1.1 200 OK
#> Date: Thu, 13 Feb 2025 21:31:44 GMT
#> Content-Type: application/json; charset=utf-8
#> Content-Encoding: gzip
#> Transfer-Encoding: chunked
#>
#> {"r":[2],"d":[2]}
wq$stop()
A character vector will be concatenated together.
wq <- WebQueue$new(handler = ~{ LETTERS })
httr2::request('http://localhost:8080') |>
httr2::req_perform() |>
httr2::resp_raw()
#> HTTP/1.1 200 OK
#> Date: Thu, 13 Feb 2025 21:31:44 GMT
#> Content-Type: text/html; charset=utf-8
#> Content-Encoding: gzip
#> Transfer-Encoding: chunked
#>
#> ABCDEFGHIJKLMNOPQRSTUVWXYZ
wq$stop()
An integer will be interpreted as an HTTP status code.
wq <- WebQueue$new(handler = ~{ 404L })
httr2::request('http://localhost:8080') |>
httr2::req_error(is_error = function (resp) FALSE) |>
httr2::req_perform() |>
httr2::resp_raw()
#> HTTP/1.1 404 Not Found
#> Date: Thu, 13 Feb 2025 21:31:44 GMT
#> Content-Encoding: gzip
#> Transfer-Encoding: chunked
#>
#> Not Found
wq$stop()
Intermediate
To construct more complex HTTP response, use the
response()
, header()
, cookie()
,
and js_obj()
functions.
Important
These functions will not be in the handler’s environment by default. Either call them with the
webqueue::
prefix, or create a WebQueue withpackages = 'webqueue'
.
wq <- WebQueue$new(
packages = 'webqueue',
handler = ~{
body <- list(data = js_obj(list()))
token <- cookie(token = 'randomstring123')
uid <- header('x-user-id' = 100, expose = TRUE)
response(body, token, uid)
})
httr2::request('http://localhost:8080') |>
httr2::req_perform() |>
httr2::resp_raw()
#> HTTP/1.1 200 OK
#> Date: Thu, 13 Feb 2025 21:31:44 GMT
#> Set-Cookie: token=randomstring123
#> x-user-id: 100
#> Access-Control-Expose-Headers: x-user-id
#> Content-Type: application/json; charset=utf-8
#> Content-Encoding: gzip
#> Transfer-Encoding: chunked
#>
#> {"data":{}}
wq$stop()
Advanced
To bypass webqueue’s response formatting, wrap your response in
I()
to indicate it should be passed on to httpuv as-is. See
the help page for httpuv::startServer()
for a description
of the expected list(status, headers, body)
object.
Although it says body = NULL
is fine, I have found that to
not be the case.
wq <- WebQueue$new(
handler = ~{
status <- 200L
body <- '{"data":{}}'
headers <- list(
'Set-Cookie' = 'token=randomstring123',
'x-user-id' = '100',
'Access-Control-Expose-Headers' = 'x-user-id',
'Content-Type' = 'application/json; charset=utf-8' )
I(list(body = body, status = status, headers = headers))
})
httr2::request('http://localhost:8080') |>
httr2::req_perform() |>
httr2::resp_raw()
#> HTTP/1.1 200 OK
#> Date: Thu, 13 Feb 2025 21:31:44 GMT
#> Set-Cookie: token=randomstring123
#> x-user-id: 100
#> Access-Control-Expose-Headers: x-user-id
#> Content-Type: application/json; charset=utf-8
#> Content-Encoding: gzip
#> Transfer-Encoding: chunked
#>
#> {"data":{}}
wq$stop()
Pre/Post Modifications
The handler
function is evaluated on a background
process, and will not have access to any variables on the foreground
process.
However, there are opportunities make modifications on the foreground
process to req
before it is passed to the handler, and to
resp
after it is returned by the handler.
Important
The callbacks here are evaluated on the foreground process. Therefore, ensure they execute quickly so as to not bottleneck request handling.
Request Parsing
The parse
function is called on req
(an
environment). Aside from req$ARGS
and
req$COOKIES
, req
is exactly as received from
httpuv. After this callback, extraneous httpuv fields are removed from
req
to minimize the amount of data sent to the background
process.
- Any modifications to
req
are persistent. - To stop the request from this callback, use
stop()
. - The return value from
parse
is ignored.
parse <- local({
counter <- 1
function (req) {
req$counter <- counter
counter <<- counter + 1
}
})
wq <- WebQueue$new(
handler = function (req) { req$counter },
parse = parse )
RCurl::getURL('http://localhost:8080')
#> [1] "1"
RCurl::getURL('http://localhost:8080')
#> [1] "2"
RCurl::getURL('http://localhost:8080')
#> [1] "3"
wq$stop()
Job Hooks
After parse
is called, the resulting req
is
added to a Job. Callbacks are triggered when the Job enters the
'created'
, 'submitted'
, 'queued'
,
and 'starting'
states.
From these hooks you can edit both the job
and
req
environment objects.
- Any modifications to
job
andreq
are persistent. - To stop the request from this callback, use
job$stop()
. - The return value from hooks are ignored.
hooks <- list()
# Request received
hooks$created <- function (job) { job$req$ARGS$a <- 1 }
# Submitted to the Queue
hooks$submitted <- function (job) { job$req$ARGS$b <- 2 }
# Accepted by the Queue
hooks$queued <- function (job) { job$req$ARGS$c <- 3 }
# Last chance to edit
hooks$starting <- function (job) { job$req$ARGS$d <- 4 }
wq <- WebQueue$new(
handler = function (req) { req$ARGS },
hooks = hooks )
cat(RCurl::getURL('http://localhost:8080'))
#> {"a":[1],"b":[2],"c":[3],"d":[4]}
wq$stop()
Response Reformatting
The reformat
function lets you edit resp
immediately after it’s returned by handler
(before webqueue
and httpuv try to interpret it as an HTTP response).
You can access both the job
and req
environments. However, any changes made to req
by
handler
will not be reflected here.
Important
Do NOT call
job$result
from within thereformat
function - it will trigger an infinite recursion. Instead, accessjob$output
.
- To stop the request from this callback, use
job$stop()
. - The return value is used as the new
resp
.