1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
|
#' Create a staticPath object
#'
#' The \code{staticPath} function creates a \code{staticPath} object. Note that
#' if any of the arguments (other than \code{path}) are \code{NULL}, then that
#' means that for this particular static path, it should inherit the behavior
#' from the staticPathOptions set for the application as a whole.
#'
#' The \code{excludeStaticPath} function tells the application to ignore a
#' particular path for static serving. This is useful when you want to include a
#' path for static serving (like \code{"/"}) but then exclude a subdirectory of
#' it (like \code{"/dynamic"}) so that the subdirectory will always be passed to
#' the R code for handling requests. \code{excludeStaticPath} can be used not
#' only for directories; it can also exclude specific files.
#'
#' @param path The local path.
#' @inheritParams staticPathOptions
#'
#' @seealso \code{\link{staticPathOptions}}.
#'
#' @export
staticPath <- function(
path,
indexhtml = NULL,
fallthrough = NULL,
html_charset = NULL,
headers = NULL,
validation = NULL
) {
if (!is.character(path) || length(path) != 1 || path == "") {
stop("`path` must be a non-empty string.")
}
path <- normalizePath(path, winslash = "/", mustWork = TRUE)
path <- enc2utf8(path)
structure(
list(
path = path,
options = normalizeStaticPathOptions(staticPathOptions(
indexhtml = indexhtml,
fallthrough = fallthrough,
html_charset = html_charset,
headers = headers,
validation = validation,
exclude = FALSE
))
),
class = "staticPath"
)
}
#' @rdname staticPath
#' @export
excludeStaticPath <- function() {
structure(
list(
path = "",
options = staticPathOptions(
indexhtml = NULL,
fallthrough = NULL,
html_charset = NULL,
headers = NULL,
validation = NULL,
exclude = TRUE
)
),
class = "staticPath"
)
}
as.staticPath <- function(path) {
UseMethod("as.staticPath", path)
}
as.staticPath.staticPath <- function(path) {
path
}
as.staticPath.character <- function(path) {
staticPath(path)
}
as.staticPath.default <- function(path) {
stop("Cannot convert object of class ", class(path), " to a staticPath object.")
}
#' @export
print.staticPath <- function(x, ...) {
cat(format(x, ...), sep = "\n")
invisible(x)
}
#' @export
format.staticPath <- function(x, ...) {
ret <- paste0(
"<staticPath>\n",
" Local path: ", x$path, "\n",
format_opts(x$options)
)
}
#' Create options for static paths
#'
#'
#' @param indexhtml If an index.html file is present, should it be served up
#' when the client requests the static path or any subdirectory?
#' @param fallthrough With the default value, \code{FALSE}, if a request is made
#' for a file that doesn't exist, then httpuv will immediately send a 404
#' response from the background I/O thread, without needing to call back into
#' the main R thread. This offers the best performance. If the value is
#' \code{TRUE}, then instead of sending a 404 response, httpuv will call the
#' application's \code{call} function, and allow it to handle the request.
#' @param html_charset When HTML files are served, the value that will be
#' provided for \code{charset} in the Content-Type header. For example, with
#' the default value, \code{"utf-8"}, the header is \code{Content-Type:
#' text/html; charset=utf-8}. If \code{""} is used, then no \code{charset}
#' will be added in the Content-Type header.
#' @param headers Additional headers and values that will be included in the
#' response.
#' @param validation An optional validation pattern. Presently, the only type of
#' validation supported is an exact string match of a header. For example, if
#' \code{validation} is \code{'"abc" = "xyz"'}, then HTTP requests must have a
#' header named \code{abc} (case-insensitive) with the value \code{xyz}
#' (case-sensitive). If a request does not have a matching header, than httpuv
#' will give a 403 Forbidden response. If the \code{character(0)} (the
#' default), then no validation check will be performed.
#' @param exclude Should this path be excluded from static serving? (This is
#' only to be used internally, for \code{\link{excludeStaticPath}}.)
#'
#' @export
staticPathOptions <- function(
indexhtml = TRUE,
fallthrough = FALSE,
html_charset = "utf-8",
headers = list(),
validation = character(0),
exclude = FALSE
) {
res <- structure(
list(
indexhtml = indexhtml,
fallthrough = fallthrough,
html_charset = html_charset,
headers = headers,
validation = validation,
exclude = exclude
),
class = "staticPathOptions"
)
normalizeStaticPathOptions(res)
}
#' @export
print.staticPathOptions <- function(x, ...) {
cat(format(x, ...), sep = "\n")
invisible(x)
}
#' @export
format.staticPathOptions <- function(x, ...) {
paste0(
"<staticPathOptions>\n",
format_opts(x, format_empty = "<none>")
)
}
format_opts <- function(x, format_empty = "<inherit>") {
format_option <- function(opt) {
if (is.null(opt) || length(opt) == 0) {
format_empty
} else if (!is.null(names(opt))) {
# Named character vector
lines <- mapply(
function(name, value) paste0(' "', name, '" = "', value, '"'),
names(opt),
opt,
SIMPLIFY = FALSE,
USE.NAMES = FALSE
)
lines <- paste(as.character(lines), collapse = "\n")
lines <- paste0("\n", lines)
lines
} else {
paste(as.character(opt), collapse = " ")
}
}
ret <- paste0(
" Use index.html: ", format_option(x$indexhtml), "\n",
" Fallthrough to R: ", format_option(x$fallthrough), "\n",
" HTML charset: ", format_option(x$html_charset), "\n",
" Extra headers: ", format_option(x$headers), "\n",
" Validation params: ", format_option(x$validation), "\n",
" Exclude path: ", format_option(x$exclude), "\n"
)
}
# This function always returns a named list of staticPath objects. The names
# will all start with "/". The input can be a named character vector or a
# named list containing a mix of strings and staticPath objects. This function
# is idempotent.
normalizeStaticPaths <- function(paths) {
if (is.null(paths) || length(paths) == 0) {
return(list())
}
if (any_unnamed(paths)) {
stop("paths must be a named character vector, a named list containing strings and staticPath objects, or NULL.")
}
if (!is.character(paths) && !is.list(paths)) {
stop("paths must be a named character vector, a named list containing strings and staticPath objects, or NULL.")
}
# Convert to list of staticPath objects. Need this verbose wrapping of
# as.staticPath because of S3 dispatch for non-registered methods.
paths <- lapply(paths, function(path) as.staticPath(path))
# Make sure URL paths have a leading '/' and no trailing '/'.
names(paths) <- vapply(names(paths), function(path) {
path <- enc2utf8(path)
if (path == "") {
stop("All paths must be non-empty strings.")
}
# Ensure there's a leading / for every path
if (substr(path, 1, 1) != "/") {
path <- paste0("/", path)
}
# Strip trailing slashes, except when the path is just "/".
if (path != "/") {
path <- sub("/+$", "", path)
}
path
}, "")
paths
}
# Takes a staticPathOptions object and modifies it so that the resulting
# object is easier to work with on the C++ side. The resulting object is not
# meant to be modified on the R side. This function is idempotent; if the
# object has already been normalized, it will not be modified. For each entry,
# a NULL means to inherit.
normalizeStaticPathOptions <- function(opts) {
if (isTRUE(attr(opts, "normalized", exact = TRUE))) {
return(opts)
}
# html_charset can accept "" or character(0). But on the C++ side, we want
# "".
if (!is.null(opts$html_charset)) {
if (length(opts$html_charset) == 0) {
opts$html_charset <- ""
}
}
if (!is.null(opts$exclude)) {
if (!is.logical(opts$exclude) || length(opts$exclude) != 1) {
stop("`exclude` option must be TRUE or FALSE.")
}
}
# Can be a named list of strings, or a named character vector. On the C++
# side, we want a named character vector.
if (is.list(opts$headers)) {
# Convert list to named character vector
opts$headers <- unlist(opts$headers, recursive = FALSE)
# Special case: if opts$headers was an empty list before unlist(), it is
# now NULL. Replace it with an empty named character vector.
if (length(opts$headers) == 0) {
opts$headers <- c(a="a")[0]
}
if (!is.character(opts$headers) || any_unnamed(opts$headers)) {
stop("`headers` option must be a named list or character vector.")
}
}
if (!is.null(opts$validation)) {
if (!is.character(opts$validation) || length(opts$validation) > 1) {
stop("`validation` option must be a character vector with zero or one element.")
}
# Both "" and character(0) result in character(0). Length-1 strings other
# than "" will be parsed.
if (length(opts$validation) == 1) {
if (opts$validation == "") {
opts$validation <- character(0)
} else {
fail <- FALSE
tryCatch(
p <- parse(text = opts$validation)[[1]],
error = function(e) fail <<- TRUE
)
if (!fail) {
if (length(p) != 3 ||
p[[1]] != as.symbol("==") ||
!is.character(p[[2]]) ||
length(p[[2]]) != 1 ||
!is.character(p[[3]]) ||
length(p[[3]]) != 1)
{
fail <- TRUE
}
}
if (fail) {
stop("`validation` must be a string of the form: '\"xxx\" == \"yyy\"'")
}
# Turn it into a char vector for easier processing in C++
opts$validation <- as.character(p)
}
}
}
attr(opts, "normalized") <- TRUE
opts
}
|