The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
Between the moment a browser asks for a page and the moment it gets
an answer, brochure gives you two places to intervene. If you know
express.js, this is its middleware; in {shiny}
terms it is code that runs before any reactive context
exists.
GET, which R receives as a
req objectreq_handlers run, in orderreq_handlers runhttpResponse from that page’s UIres_handlers run, then the matched
page’sA req_handler takes req and must return it.
A res_handler takes res and req,
and must return res. Both accept formulas: .x
is req for a request handler, and res for a
response handler, where .y is then req.
A req_handler returning an httpResponse
short-circuits: that response is sent immediately, nothing else runs,
and no Shiny session is created. This is how you serve something that is
not a page — a healthcheck, a webhook receiver, a small JSON
endpoint:
brochureApp(
page(href = "/", ui = tagList(h1("Home"))),
page(
href = "/healthcheck",
ui = tagList(),
req_handlers = list(
~ shiny::httpResponse(200, content = "OK")
)
)
)The page still needs a ui, because page()
requires an href to bind to, but it is never rendered.
Note the asymmetry: an early httpResponse skips the
res_handlers too. If you rely on a response handler to set
a header on every answer, it will not see these.
Because a request handler can answer on its own, it can also refuse:
require_token <- function(req) {
token <- req$HTTP_X_TOKEN
if (is.null(token) || !identical(token, Sys.getenv("APP_TOKEN"))) {
return(shiny::httpResponse(401, content = "Unauthorized"))
}
req
}
brochureApp(
page(href = "/", ui = tagList(h1("Home"))),
req_handlers = list(require_token)
)This guards the HTTP request that serves the page. It does not guard the websocket the page opens afterwards, so treat it as a gate on the document, not as an authentication system.
brochureApp(
page(href = "/", ui = tagList(h1("Home"))),
res_handlers = list(
function(res, req) {
res$headers$`X-Frame-Options` <- "DENY"
res
}
)
)App level handlers run before page level ones, so a page can override what the app decided.
An app level handler runs for every matched page. A page level one
runs only for its own page, which is what you want for anything specific
— a cookie for the login page, a Cache-Control for a page
that is expensive to build.
The app level request handlers are the exception to “for every
matched page”: they run before the request is matched at all, so they
see urls that match no page, and can answer them. Everything else — the
page level handlers, and both levels of response handler — is skipped
when nothing matches, and the request gets content_404
directly.
A page answers GET. method changes that,
which is how a form post or a webhook gets a page of its own:
subscribe <- function(req) {
fields <- shiny::parseQueryString(
rawToChar(req$rook.input$read())
)
message("new subscriber: ", fields$email)
req
}
brochureApp(
page(
href = "/",
ui = tagList(
tags$form(
action = "/subscribe",
method = "post",
tags$input(type = "email", name = "email"),
tags$input(type = "submit", value = "Subscribe")
)
)
),
page(
href = "/subscribe",
method = "POST",
ui = tagList(h1("Thanks")),
req_handlers = list(subscribe)
)
)The method takes part in the matching, so a GET on
/subscribe matches nothing and gets
content_404.
What was posted is yours to read: the body is on the request, as
req$rook.input, and brochure does not touch it. A request
handler is the place for that, since it runs before the response is
built.
A www/ directory at the root of your app breaks those
pages, and brochure cannot do anything about it: runApp()
mounts that directory as an httpuv static path on /, and
httpuv answers anything that is not a GET or a
HEAD from there with a 400 before R ever sees the request.
The request handler never runs. Serve those files under a prefix instead
— addResourcePath("assets", "assets") and
href = "assets/site.css" — which is what a
{golem} app already does.
One thing to keep in mind: a page’s session is resolved on
the path alone. The websocket handshake a browser opens is a
GET whatever method the page was served on, so two pages
declared at the same href with different methods both run the server of
the first one declared.
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.