addHandler adds a content handler to be used with the webserver.

rmAllHandlers removes all currently registered handlers.

defaultBodyParsers returns the request body parsers used by R function handlers with parse.body=TRUE.

addHandler(type, prefix, ...)
rmAllHandlers()
defaultBodyParsers()

Arguments

type

handler type

prefix

string, prefix at which the handler will be registered.

...

parameters for the handler, see details below.

Value

undefined (currently returns TRUE invisibly, but that is not guaranteed for the future).

Details

For each webserver request the content handlers are matched in sequence against the request's path and each matching handler gets a chance to provide the result for the request.

Most commonly used handlers are "static" which serves files from the file system, "rfun" which registers an R function to be called with the request details and "rewrite" which modifies the request path according to specified rules.

Static Handler

Syntax: addHandler("static", prefix, path, index=NULL, stop=FALSE, gzip=FALSE)

path must be an existing directory. It is expanded (see path.expand) and converted to an absolute path when the handler is added (on unix symbolic links are resolved), so later changes of the working directory have no effect.

When the prefix is matched, it is removed from the request path and the remaining path is appended to path to look up files. If the target matches a directory and index is a string, then index is appended to the path, unless the trailing slash is missing in which case a re-direct is returned to add the slash (this makes sure that relative paths in the index stay valid). If the resulting target is a file, it is served as the request result. Otherwise either 404 Not Found response is sent (if stop=TRUE) or the processing is passed to the next handler.

The static handler supports conditional GET requests based on last modified timestamp. It also supports HTTP range requests. If gzip=TRUE and the client has requested gzip compression then the handler checks if a file <target>.gz exists and is not older than <target> then the compressed file is served instead. This saves bandwidth for compressible static content.

Example: addHandler("static", "/", path=".", index="index.html", stop=TRUE)

Serves files from the current directory (at the time of the call) at the root of URLs. It will stop and not evaluate any further handlers even if the file is not found.

R Function Handler

This handler calls an R function with the same API as the internal R server and Rserve.

addHandler("rfun", prefix, FUN, parse.query=TRUE, parse.headers=TRUE, parse.body=FALSE, fork=FALSE)

FUN

function to call for a request (see below for details)

parse.query

logical, if TRUE then the query string is parsed into a named character vector, otherwise it is just one string (see below)

parse.headers

logical, if TRUE then the headers are parsed (see below), otherwise just a raw vector

parse.body

FALSE to pass the request body as-is, TRUE to decode it with the parsers returned by defaultBodyParsers() or a named list of parsers (see below)

fork

logical, if TRUE then the R processing will be delegated to forked workers, one per each I/O thread (not available on Windows). Otherwise the R code is evaluated in the current session.

The function FUN should have the following signature:

function(path, query, body, headers, ...)

path

string, path of the request

query

query string part of the request. If parse.query=TRUE (the default) then the query string is parsed into a named character vector, so, e.g., the query string "a=1&b=foo" will be parsed into the R object c(a="1", b="foo"). Otherwise it is just the string itself.

body

body part of the request or NULL if there is no body. Unless it is decoded by a parser (see parse.body below), it is a raw vector with the "content-type" attribute (if specified by the client).

headers

request headers. It is a raw vector unless parse.headers=TRUE in which case the headers are parsed into a named list where the header names are lowercase and the "-" character is replaced by "." so, for example, User-Agent is stored in the user.agent element of the list. Note that the parsing is experimental, since the interpretation can vary among header entries.

The server adds the following headers which are always present unless stated otherwise: Request-Method (request.method when parsed) is the request method name (GET, POST, etc.), Request-Proto (request.proto) is http or https, depending on the listener the request was received on, and Request-Client-CN (request.client.cn) is the common name of the client certificate. The latter is only present if the client has presented a valid certificate, i.e., on HTTPS listeners with tls.client.ca set (see webstart), and the certificate has a common name that can be passed in a header (without control characters and not excessively long), so handlers can use it to identify the client. Header names starting with Request- are reserved for the server, so such headers sent by the client are dropped. Behind a reverse proxy, these describe the connection from the proxy, the proxy may pass the original protocol on in another header such as X-Forwarded-Proto.

...

reserved for future extensions

The result is expected to have the following form:

list(result, content.type, headers, http.code)

Only the first list element is mandatory, all others are optional.

result

Can be either a string or a raw vector containing the body of the result to return to the client. As a special case, the result can be a named character vector of length one with either file or tempfile name in which case the result is interpreted as a path to a file whose contents will be served. In the case of tempfile the file will be removed once read.

content.type

string, MIME type of the result, defaults to "text/html"

headers

character vector (unnamed) of any additional headers to send. Note that it may not contain Content-Type nor Content-Length.

http.code

integer, valid HTTP code (100..999), defaults to 200L

One exception is that returning FALSE indicates that the handler has refused to process the request and so the search should continue to the next handler. Note that due to the relatively expensive nature of some evaluation methods (such as worker communication) it is recommended to consolidate R handlers instead of relying on each to signal its unwillingness to process the request.

If the returned object has any other form (e.g. if it is a character vector), it is assumed to be an error and it will result in 500 HTTP error code.

Example: addHandler("rfun", "/api/v1/add", function(path, query, ...) list(as.numeric(query["a"]) + as.numeric(query["b"])))

Body parsers. A parser is a function function(body, content.type) which receives the request body (raw vector) and the value of the Content-Type header (including parameters such as charset) and returns the decoded body which is then passed to FUN. The names of the list of parsers are media types (matched case-insensitively, parameters are ignored). A name can also start with * to match any media type ending with the rest (e.g., "*+json") or end with * to match any media type starting with the rest (e.g., "text/*"). An exact match takes precedence, otherwise the first matching name is used. If no parser matches, the body is passed as-is. If the parser fails, the request is answered with 400 Bad Request and FUN is not called.

defaultBodyParsers() returns a parser for URL encoded forms (application/x-www-form-urlencoded) which produces a named character vector like the parsed query and parsers for JSON (application/json and *+json) using jsonlite::fromJSON(). The list can be modified or extended to define the parsers of a handler, e.g.,

addHandler("rfun", "/api", FUN, parse.body=c(defaultBodyParsers(), list("text/csv"=function(body, content.type) read.csv(text=rawToChar(body)))))

Rewrite Handler

This handler modifies the request based on simple substitution rules.

addHandler("rewrite", prefix, replacement, stop=FALSE)

The prefix can contain the ? character as a wildcard which will match any number of characters other than /. The matched content is then available in the form of $n variables in the replacement where n is one of the integers 1..9. If the request prefix is matched then it is replaced by the replacement.

Example: addHandler("rewrite", "/api/?/?", "/r?name=$1&par=$2", stop=TRUE)

Will match request /api/copy/this?a=1 and rewrite it to /r?a=1&name=copy&par=this. Note that if the replacement contains a query string, it will be appended to the request query string.

The stop parameter only applies to subsequent rewrite requests, because the rewrite handler is not an actual content handler, it will still proceed to other content handlers regardless of the value of stop, but setting stop=TRUE will make sure that no more rewrite handlers will be consulted.

Author

Simon Urbanek