Skip to content

Commit 36373ba

Browse files
authored
Merge pull request rstudio#2386 from rstudio/joe/bugfix/subapp-routing
Fix rstudio#2385: R Markdown documents containing subapps not rendering properly
2 parents f5392d7 + 1415b57 commit 36373ba

File tree

3 files changed

+67
-10
lines changed

3 files changed

+67
-10
lines changed

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Package: shiny
22
Type: Package
33
Title: Web Application Framework for R
4-
Version: 1.3.1
4+
Version: 1.3.1.9000
55
Authors@R: c(
66
person("Winston", "Chang", role = c("aut", "cre"), email = "[email protected]"),
77
person("Joe", "Cheng", role = "aut", email = "[email protected]"),

NEWS.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
1-
shiny 1.3.1
2-
===========
1+
shiny 1.3.1.9000
2+
================
33

4-
## Full changelog
4+
### Bug fixes
55

6-
### Breaking changes
6+
* Static CSS/JS resources in subapps in R Markdown documents did not render properly. ([#2386](https://github.com/rstudio/shiny/pull/2386))
77

8-
### New features
98

10-
### Minor new features and improvements
9+
shiny 1.3.1
10+
===========
1111

1212
### Bug fixes
1313

1414
* Fixed a performance issue introduced in v1.3.0 when using large nested lists within Shiny. ([#2377](https://github.com/rstudio/shiny/pull/2377))
1515

16-
### Documentation Updates
17-
1816

1917
shiny 1.3.0
2018
===========

R/server.R

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ registerClient <- function(client) {
2323

2424

2525
.globals$resourcePaths <- list()
26+
.globals$resources <- list()
2627

2728
.globals$showcaseDefault <- 0
2829

@@ -69,8 +70,62 @@ addResourcePath <- function(prefix, directoryPath) {
6970
getShinyOption("server")$setStaticPath(.list = stats::setNames(normalizedPath, prefix))
7071
}
7172

72-
# .globals$resourcePaths persists across runs of applications.
73+
# .globals$resourcePaths and .globals$resources persist across runs of applications.
7374
.globals$resourcePaths[[prefix]] <- staticPath(normalizedPath)
75+
# This is necessary because resourcePaths is only for serving assets out of C++;
76+
# to support subapps, we also need assets to be served out of R, because those
77+
# URLs are rewritten by R code (i.e. routeHandler) before they can be matched to
78+
# a resource path.
79+
.globals$resources[[prefix]] <- list(
80+
directoryPath = normalizedPath,
81+
func = staticHandler(normalizedPath)
82+
)
83+
}
84+
85+
# This function handles any GET request with two or more path elements where the
86+
# first path element matches a prefix that was previously added using
87+
# addResourcePath().
88+
#
89+
# For example, if `addResourcePath("foo", "~/bar")` was called, then a GET
90+
# request for /foo/one/two.html would rewrite the PATH_INFO as /one/two.html and
91+
# send it to the resource path function for "foo". As of this writing, that
92+
# function will always be a staticHandler, which serves up a file if it exists
93+
# and NULL if it does not.
94+
#
95+
# Since Shiny 1.3.x, assets registered via addResourcePath should mostly be
96+
# served out of httpuv's native static file serving features. However, in the
97+
# specific case of subapps, the R code path must be used, because subapps insert
98+
# a giant random ID into the beginning of the URL that must be stripped off by
99+
# an R route handler (see addSubApp()).
100+
resourcePathHandler <- function(req) {
101+
if (!identical(req$REQUEST_METHOD, 'GET'))
102+
return(NULL)
103+
104+
# e.g. "/foo/one/two.html"
105+
path <- req$PATH_INFO
106+
107+
match <- regexpr('^/([^/]+)/', path, perl=TRUE)
108+
if (match == -1)
109+
return(NULL)
110+
len <- attr(match, 'capture.length')
111+
# e.g. "foo"
112+
prefix <- substr(path, 2, 2 + len - 1)
113+
114+
resInfo <- .globals$resources[[prefix]]
115+
if (is.null(resInfo))
116+
return(NULL)
117+
118+
# e.g. "/one/two.html"
119+
suffix <- substr(path, 2 + len, nchar(path))
120+
121+
# Create a new request that's a clone of the current request, but adjust
122+
# PATH_INFO and SCRIPT_NAME to reflect that we have already matched the first
123+
# path element (e.g. "/foo"). See routeHandler() for more info.
124+
subreq <- as.environment(as.list(req, all.names=TRUE))
125+
subreq$PATH_INFO <- suffix
126+
subreq$SCRIPT_NAME <- paste(subreq$SCRIPT_NAME, substr(path, 1, 2 + len), sep='')
127+
128+
return(resInfo$func(subreq))
74129
}
75130

76131
#' Define Server Functionality
@@ -158,6 +213,8 @@ createAppHandlers <- function(httpHandlers, serverFuncSource) {
158213
appvars <- new.env()
159214
appvars$server <- NULL
160215

216+
sys.www.root <- system.file('www', package='shiny')
217+
161218
# This value, if non-NULL, must be present on all HTTP and WebSocket
162219
# requests as the Shiny-Shared-Secret header or else access will be
163220
# denied (403 response for HTTP, and instant close for websocket).
@@ -167,6 +224,8 @@ createAppHandlers <- function(httpHandlers, serverFuncSource) {
167224
http = joinHandlers(c(
168225
sessionHandler,
169226
httpHandlers,
227+
sys.www.root,
228+
resourcePathHandler,
170229
reactLogHandler
171230
)),
172231
ws = function(ws) {

0 commit comments

Comments
 (0)