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(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(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(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 a 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 awebqueue
withpackages = 'webqueue'
.
wq <- webqueue(
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(
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(status = status, body = body, 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 to modify req
on the
foreground process before it is passed to the handler, and to modify
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
before
it is passed to the background process. 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(
parse = parse,
handler = function (req) { req$counter } )
fetch('http://localhost:8080')
#> [1] "1"
fetch('http://localhost:8080')
#> [1] "2"
fetch('http://localhost:8080')
#> [1] "3"
wq$stop()
Job Hooks
After parse
is called, the resulting req
is
added to a job
(see jobqueue::job_class
).
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
(<job>$req
) 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 `jobqueue`
hooks$submitted <- function (job) { job$req$ARGS$b <- 2 }
# Accepted by the `jobqueue`
hooks$queued <- function (job) { job$req$ARGS$c <- 3 }
# Last chance to edit
hooks$starting <- function (job) { job$req$ARGS$d <- 4 }
wq <- webqueue(
handler = function (req) { req$ARGS },
hooks = hooks )
cat(fetch('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
(<job>$req
) objects. 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, access<job>$output
.
- To stop the request from this callback, use
<job>$stop()
. - The return value is used as the new
resp
.