-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Description
I've recently migrated a Shiny application to using the UI-as-a-function approach, allowing for increased control on the initial request (to be able to serve a non-Shiny-app response). This is combined with Shiny's HTML template system, also allowing for finer control of the app's returned page (on the initial, non-websocket, request). The general structure looks like so:
ui <- function(req) {
custom_header_value(req)
if (some_test(req)) {
# return the static, non-app response
shiny::httpResponse(
content = nonapp_content(),
headers = list("x-custom-header" = custom_header_value(req))
)
} else {
# return the shiny app response
shiny::htmlTemplate(
text_ = the_template(),
document_ = TRUE,
named, template, variables, here
)
}
}This works great until I also want to include that x-custom-header in the Shiny app response.
My initial thought was to simply set htmlTemplate()'s return value as the content of httpResponse():
## does NOT work:
shiny::httpResponse(
content = shiny::htmlTemplate(...),
headers = list("x-custom-header" = custom_header_value(req)
)... but I quickly realized httpResponse() wants a fully-baked, i.e. rendered, HTML document, which htmlTemplate() does not provide.
That rendering step (going from an "html_document" and "shiny.tag.list" to a fully-baked HTML string happens here, in shiny:::uiHttpHandler() (called via shiny::shinyApp()):
Lines 256 to 261 in e3cf4fb
| if (inherits(uiValue, "httpResponse")) { | |
| return(uiValue) | |
| } else { | |
| html <- renderPage(uiValue, showcaseMode, testMode) | |
| return(httpResponse(200, content=html)) | |
| } |
A small chunk of uiHttpHandler() is used for determining the values for the other two arguments to renderPage(): showcase and testMode.
In my particular case, those two will always be disabled/FALSE, and so I was able to complete my initial goal like so:
ui <- function(req) {
custom_header_value(req)
if (some_test(req)) {
# return the static, non-app response
shiny::httpResponse(
content = nonapp_content(),
headers = list("x-custom-header" = custom_header_value(req, app = FALSE))
)
} else {
# return the shiny app response
shiny::httpResponse(
content =
shiny::htmlTemplate(
text_ = the_template(),
document_ = TRUE,
named, template, variables, here
) |>
shiny:::renderPage(), ## now we have a fully-baked HTML string
headers = list("x-custom-header" = custom_header_value(req, app =TRUE))
)
}
}This required accessing the non-exported shiny:::renderPage() function, which never feels great to put into production code, but I was otherwise pretty stumped for a way to add a header to the app's initial (pre-websocket) response.
Perhaps there's another way to do this?
If not, maybe the showcase- and testMode-determining logic can be 'officially' factored-out of uiHttpResponse() along with making renderPage() exported?
This way one could use the (working) pattern above while also retaining the potential use of those two flags (without simply re-implementing that flag-determining logic themselves ... i.e. don't repeat yourself).