The handler function is responsible for transforming a
request (req) into a response (resp).
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) is emptyenv().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()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()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()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()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()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.
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.
req are persistent.stop().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()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.
job and req are
persistent.job$stop().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()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$resultfrom within thereformatfunction - it will trigger an infinite recursion. Instead, accessjob$output.
job$stop().resp.