| Title: | A Framework for Building HTTP API |
|---|---|
| Description: | Allows to easily create high-performance full featured HTTP APIs from R functions. Provides high-level classes such as 'Request', 'Response', 'Application', 'Middleware' in order to streamline server side application development. Out of the box allows to serve requests using 'Rserve' package, but flexible enough to integrate with other HTTP servers such as 'httpuv'. |
| Authors: | Dmitry Selivanov [aut, cre] (ORCID: <https://orcid.org/0000-0001-5413-1506>), Artem Klevtsov [aut] (ORCID: <https://orcid.org/0000-0003-0492-6647>), David Zimmermann [ctb], rexy.ai [cph, fnd] |
| Maintainer: | Dmitry Selivanov <[email protected]> |
| License: | GPL (>= 2) |
| Version: | 1.2.4 |
| Built: | 2026-05-31 06:09:27 UTC |
| Source: | https://github.com/rexyai/restrserve |
Creates Application object. Application provides an interface for building high-performance REST API by registering R functions as handlers http requests.
There are several advanced options to control how HTTP headers are processed:
options("RestRserve.headers.server") controls response "Server" header
options("RestRserve.headers.split") controls which header values
split by comma during parsing. See
https://en.wikipedia.org/wiki/List_of_HTTP_header_fields,
https://stackoverflow.com/a/29550711/3048453
There is also an option to switch-off runtime types validation in
the Request/Response handlers. This might provide some performance gains,
but ultimately leads to less robust applications. Use at your own risk!
See options("RestRserve.runtime.asserts")
loggerLogger object which records events during request processing.
Alternatively user can use loggers from lgr package as a drop-in
replacement - Logger methods and loggers created by lgr share
function signatures.
content_typeDefault response body content type.
HTTPErrorClass which raises HTTP errors.
Global HTTPError is used by default. In theory user can replace it with
his own class (see RestRserve:::HTTPErrorFactory). However we believe
in the majority of the cases using HTTPError will be enough.
endpointsPrints all the registered routes with allowed methods.
new()
Creates Application object.
Application$new( middleware = list(EncodeDecodeMiddleware$new()), content_type = "text/plain", ... )
middlewareList of Middleware objects.
content_typeDefault response body content (media) type. "text/plain" by default.
...Not used at the moment.
add_route()
Adds endpoint and register user-supplied R function as a handler.
Application$add_route(
path,
method,
FUN,
match = c("exact", "partial", "regex"),
...
)pathEndpoint path.
methodHTTP method. Allowed methods at the moment:
GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH.
FUNUser function to handle requests. FUN must take two arguments:
first is request (Request) and second is response (Response).
The goal of the user function is to modify response or throw
exception (call raise() or stop()).
Both response and request objects modified in-place and internally
passed further to RestRserve execution pipeline.
matchDefines how route will be processed. Allowed values:
exact - match route as is. Returns 404 if route is not matched.
partial - match route as prefix. Returns 404 if prefix are not matched.
regex - match route as template. Returns 404 if template pattern not matched.
...Not used.
add_get()
Shorthand to Application$add_route() with GET method.
Application$add_get(
path,
FUN,
match = c("exact", "partial", "regex"),
...,
add_head = TRUE
)pathEndpoint path.
FUNUser function to handle requests. FUN must take two arguments:
first is request (Request) and second is response (Response).
The goal of the user function is to modify response or throw
exception (call raise() or stop()).
Both response and request objects modified in-place and internally
passed further to RestRserve execution pipeline.
matchDefines how route will be processed. Allowed values:
exact - match route as is. Returns 404 if route is not matched.
partial - match route as prefix. Returns 404 if prefix are not matched.
regex - match route as template. Returns 404 if template pattern not matched.
...Not used.
add_headAdds HEAD method.
add_post()
Shorthand to Application$add_route() with POST method.
Application$add_post(path, FUN, match = c("exact", "partial", "regex"), ...)pathEndpoint path.
FUNUser function to handle requests. FUN must take two arguments:
first is request (Request) and second is response (Response).
The goal of the user function is to modify response or throw
exception (call raise() or stop()).
Both response and request objects modified in-place and internally
passed further to RestRserve execution pipeline.
matchDefines how route will be processed. Allowed values:
exact - match route as is. Returns 404 if route is not matched.
partial - match route as prefix. Returns 404 if prefix are not matched.
regex - match route as template. Returns 404 if template pattern not matched.
...Not used.
add_static()
Adds GET method to serve file or directory at file_path.
Application$add_static(path, file_path, content_type = NULL, ...)
pathEndpoint path.
file_pathPath file or directory.
content_typeMIME-type for the content.
If content_type = NULL then MIME code content_type will be inferred
automatically (from file extension).
If it will be impossible to guess about file type then content_type will
be set to application/octet-stream.
...Not used.
add_openapi()
Adds endpoint to serve OpenAPI description of available methods.
Application$add_openapi(path = "/openapi.yaml", file_path = "openapi.yaml")
pathpath Endpoint path.
file_pathPath to the OpenAPI specification file.
add_swagger_ui()
Adds endpoint to show Swagger UI.
Application$add_swagger_ui( path = "/swagger", path_openapi = "/openapi.yaml", use_cdn = TRUE, path_swagger_assets = "/__swagger__/", file_path = "swagger-ui.html" )
pathpath Endpoint path.
path_openapiPath to the OpenAPI specification file.
use_cdnUse CDN to load Swagger UI libraries.
path_swagger_assetsSwagger UI asstes endpoint.
file_pathPath to Swagger UI HTML file.
append_middleware()
Appends middleware to handlers pipeline.
Application$append_middleware(mw)
mwMiddleware object.
process_request()
Process incoming request and generate Response object.
Application$process_request(request = NULL)
requestRequest object.
Useful for tests your handlers before deploy application.
print()
Prints application details.
Application$print()
clone()
The objects of this class are cloneable with this method.
Application$clone(deep = FALSE)
deepWhether to make a deep clone.
HTTPError Middleware Request Response
# init logger app_logger = Logger$new() # set log level for the middleware app_logger$set_log_level("debug") # set logger name app_logger$set_name("MW Logger") # init middleware to logging mw = Middleware$new( process_request = function(rq, rs) { app_logger$info(sprintf("Incomming request (id %s): %s", rq$id, rq$path)) }, process_response = function(rq, rs) { app_logger$info(sprintf("Outgoing response (id %s): %s", rq$id, rs$status)) }, id = "awesome-app-logger" ) # init application app = Application$new(middleware = list(mw)) # set internal log level app$logger$set_log_level("error") # define simply request handler status_handler = function(rq, rs) { rs$set_body("OK") rs$set_content_type("text/plain") rs$set_status_code(200L) } # add route app$add_get("/status", status_handler, "exact") # add static file handler desc_file = system.file("DESCRIPTION", package = "RestRserve") # add route app$add_static("/desc", desc_file, "text/plain") # define say message handler say_handler = function(rq, rs) { who = rq$parameters_path[["user"]] msg = rq$parameters_query[["message"]] if (is.null(msg)) msg = "Hello" rs$set_body(paste(who, "say", dQuote(msg))) rs$set_content_type("text/plain") rs$set_status_code(200L) } # add route app$add_get("/say/{user}", say_handler, "regex") # print application info app # test app # simulate requests not_found_rq = Request$new(path = "/no") status_rq = Request$new(path = "/status") desc_rq = Request$new(path = "/desc") say_rq = Request$new(path = "/say/anonym", parameters_query = list("message" = "Hola")) # process prepared requests app$process_request(not_found_rq) app$process_request(status_rq) app$process_request(desc_rq) app$process_request(say_rq) # run app backend = BackendRserve$new() if (interactive()) { backend$start(app, 8080) }# init logger app_logger = Logger$new() # set log level for the middleware app_logger$set_log_level("debug") # set logger name app_logger$set_name("MW Logger") # init middleware to logging mw = Middleware$new( process_request = function(rq, rs) { app_logger$info(sprintf("Incomming request (id %s): %s", rq$id, rq$path)) }, process_response = function(rq, rs) { app_logger$info(sprintf("Outgoing response (id %s): %s", rq$id, rs$status)) }, id = "awesome-app-logger" ) # init application app = Application$new(middleware = list(mw)) # set internal log level app$logger$set_log_level("error") # define simply request handler status_handler = function(rq, rs) { rs$set_body("OK") rs$set_content_type("text/plain") rs$set_status_code(200L) } # add route app$add_get("/status", status_handler, "exact") # add static file handler desc_file = system.file("DESCRIPTION", package = "RestRserve") # add route app$add_static("/desc", desc_file, "text/plain") # define say message handler say_handler = function(rq, rs) { who = rq$parameters_path[["user"]] msg = rq$parameters_query[["message"]] if (is.null(msg)) msg = "Hello" rs$set_body(paste(who, "say", dQuote(msg))) rs$set_content_type("text/plain") rs$set_status_code(200L) } # add route app$add_get("/say/{user}", say_handler, "regex") # print application info app # test app # simulate requests not_found_rq = Request$new(path = "/no") status_rq = Request$new(path = "/status") desc_rq = Request$new(path = "/desc") say_rq = Request$new(path = "/say/anonym", parameters_query = list("message" = "Hola")) # process prepared requests app$process_request(not_found_rq) app$process_request(status_rq) app$process_request(desc_rq) app$process_request(say_rq) # run app backend = BackendRserve$new() if (interactive()) { backend$start(app, 8080) }
Creates ApplicationProcess to hold PID of the running application.
pidProcess identificator.
new()
Creates ApplicationProcess object
ApplicationProcess$new(pid)
pidProcess identificator.
kill()
Send signal to process.
ApplicationProcess$kill(signal = 15L)
signalSignal code.
clone()
The objects of this class are cloneable with this method.
ApplicationProcess$clone(deep = FALSE)
deepWhether to make a deep clone.
Creates AuthBackendBasic class object.
RestRserve::AuthBackend -> AuthBackendBasic
new()
Creates AuthBackendBasic class object.
AuthBackendBasic$new(FUN)
FUNFunction to perform authentication which takes two arguments -
user and password. Returns boolean - whether access is allowed for
a requested user or not.
authenticate()
Provide authentication for the given request.
AuthBackendBasic$authenticate(request, response)
Boolean - whether access is allowed for a requested user or not.
clone()
The objects of this class are cloneable with this method.
AuthBackendBasic$clone(deep = FALSE)
deepWhether to make a deep clone.
AuthMiddleware Request Response
Other AuthBackend:
AuthBackend,
AuthBackendBearer,
AuthMiddleware
# init users database user_db = list( "user-1" = "password-1", "user-2" = "password-2" ) # define authentication handler auth_fun = function(user, password) { if (is.null(user_db[[user]])) return(FALSE) # not found if (!identical(user_db[[user]], password)) return(FALSE) # incorrect return(TRUE) } # init backend auth_backend = AuthBackendBasic$new(FUN = auth_fun) # test backend # define credentials (see RFC) creds = jsonlite::base64_enc("user-1:password-1") # generate request headers h = list("Authorization" = sprintf("Basic %s", creds)) # simulate request rq = Request$new(path = "/", headers = h) # init response object rs = Response$new() # perform authentication auth_backend$authenticate(rq, rs) # TRUE# init users database user_db = list( "user-1" = "password-1", "user-2" = "password-2" ) # define authentication handler auth_fun = function(user, password) { if (is.null(user_db[[user]])) return(FALSE) # not found if (!identical(user_db[[user]], password)) return(FALSE) # incorrect return(TRUE) } # init backend auth_backend = AuthBackendBasic$new(FUN = auth_fun) # test backend # define credentials (see RFC) creds = jsonlite::base64_enc("user-1:password-1") # generate request headers h = list("Authorization" = sprintf("Basic %s", creds)) # simulate request rq = Request$new(path = "/", headers = h) # init response object rs = Response$new() # perform authentication auth_backend$authenticate(rq, rs) # TRUE
Creates AuthBackendBearer class object.
RestRserve::AuthBackend -> AuthBackendBearer
new()
Creates AuthBackendBearer class object.
AuthBackendBearer$new(FUN)
FUNFunction to perform authentication which takes one arguments - token.
Returns boolean - whether access is allowed for a requested token or not.
authenticate()
Provide authentication for the given request.
AuthBackendBearer$authenticate(request, response)
Boolean - whether access is allowed for a requested user or not.
clone()
The objects of this class are cloneable with this method.
AuthBackendBearer$clone(deep = FALSE)
deepWhether to make a deep clone.
AuthMiddleware Request Response
Other AuthBackend:
AuthBackend,
AuthBackendBasic,
AuthMiddleware
token_db = list( "valid-token" = as.POSIXct("2099-12-31", tz = "GMT"), "expired-token" = as.POSIXct("1900-01-01", tz = "GMT") ) auth_fun = function(token) { if (is.null(token_db[[token]])) return(FALSE) # not found if (Sys.time() > token_db[[token]]) return(FALSE) # expired return(TRUE) } # init backend auth_backend = AuthBackendBearer$new(FUN = auth_fun) # test backend # define credentials (see RFC) token = "valid-token" # generate request headers h = list("Authorization" = sprintf("Bearer %s", token)) # simulate request rq = Request$new(path = "/", headers = h) # init response object rs = Response$new() # perform authentication auth_backend$authenticate(rq, rs) # TRUEtoken_db = list( "valid-token" = as.POSIXct("2099-12-31", tz = "GMT"), "expired-token" = as.POSIXct("1900-01-01", tz = "GMT") ) auth_fun = function(token) { if (is.null(token_db[[token]])) return(FALSE) # not found if (Sys.time() > token_db[[token]]) return(FALSE) # expired return(TRUE) } # init backend auth_backend = AuthBackendBearer$new(FUN = auth_fun) # test backend # define credentials (see RFC) token = "valid-token" # generate request headers h = list("Authorization" = sprintf("Bearer %s", token)) # simulate request rq = Request$new(path = "/", headers = h) # init response object rs = Response$new() # perform authentication auth_backend$authenticate(rq, rs) # TRUE
Adds various authorizations to Application.
RestRserve::Middleware -> AuthMiddleware
new()
Creeates AuthMiddleware object.
AuthMiddleware$new( auth_backend, routes, match = "exact", id = "AuthMiddleware" )
auth_backendAuthentication backend.
routesRoutes paths to protect.
matchHow routes will be matched: "exact" or "partial" (as prefix).
idMiddleware id.
clone()
The objects of this class are cloneable with this method.
AuthMiddleware$clone(deep = FALSE)
deepWhether to make a deep clone.
Other AuthBackend:
AuthBackend,
AuthBackendBasic,
AuthBackendBearer
Creates BackendRserve object which can start Application using Rserve backend.
RestRserve::Backend -> BackendRserve
new()
Creates BackendRserve object.
BackendRserve$new(..., jit_level = 0L, precompile = FALSE)
...Not used at the moment.
jit_levelchanges R's byte compiler level to this value before app start.
precompiletry to use R's byte compiler to pre-compile
start()
Starts RestRserve application from current R session.
BackendRserve$start(app, http_port = 8080, ..., background = FALSE)
appApplication object.
http_portHTTP port for application. Negative values (such as -1) means not to expose plain http.
...Key-value pairs of the Rserve configuration. If contains
"http.port" then http_port will be silently replaced with its value.
backgroundWhether to try to launch in background process on UNIX.
ApplicationProcess object when background = TRUE.
set_request()
Parse request and set to it fields.
BackendRserve$set_request( request, path = "/", parameters_query = NULL, headers = NULL, body = NULL )
requestRequest object.
pathCharacter with requested path. Always starts with /.
parameters_queryA named character vector with URL decoded query parameters.
headersRequest HTTP headers.
bodyRequest body. Can be NULL, raw vector or named character
vector for the URL encoded form (like a parameters_query parameter).
request modified object.
convert_response()
Convert self object to Rserve compatible structure.
BackendRserve$convert_response(response)
responseResponse object.
List with the following structure:
body: can be a character vector of length one or a raw vector.
if the character vector is named "file" then the content of a file of
that name is the body.
If the character vector is named "tmpfile" then the content of a
temporary file of that name is the body.
content-type: must be a character vector of length one or NULL
(if present, else default is "text/plain").
headers: must be a character vector - the elements will have CRLF
appended and neither Content-type nor Content-length may be used.
status-code: must be an integer if present (default is 200).
clone()
The objects of this class are cloneable with this method.
BackendRserve$clone(deep = FALSE)
deepWhether to make a deep clone.
Adds CORS to Application. CORS Middleware out of the box in RestRserve to turn on/off the CORS
Headers on preflight validation from the browser.
Cross Origin Resource Sharing is an additional security check done by moderns
browsers to avoid request between different domains. To allow it RestRserve
has easy way to enable your CORS policies. By default CORS policies are disabled.
So if any request is coming from a different domain will be blocked
by the browser as default because RestRserve will not send the headers required
by the browser to allow cross site resource sharing. You can change this easy
just by providing CORSMiddleware as middleware to the Application.
RestRserve::Middleware -> CORSMiddleware
new()
Creates CORS middleware object
CORSMiddleware$new(routes = "/", match = "partial", id = "CORSMiddleware")
routesRoutes paths to protect.
matchHow routes will be matched: exact or partial (as prefix).
idMiddleware id.
clone()
The objects of this class are cloneable with this method.
CORSMiddleware$clone(deep = FALSE)
deepWhether to make a deep clone.
app = Application$new(middleware = list(CORSMiddleware$new())) app$add_post(path = "/hello", FUN = function(req, res) { res$set_body("Hello from RestRserve!") }) app$add_route("/hello", method = "OPTIONS", FUN = function(req, res) { res$set_header("Allow", "POST, OPTIONS") }) req = Request$new( path = "/hello", headers = list("Access-Control-Request-Method" = "POST"), method = "OPTIONS" ) app$process_request(req)app = Application$new(middleware = list(CORSMiddleware$new())) app$add_post(path = "/hello", FUN = function(req, res) { res$set_body("Hello from RestRserve!") }) app$add_route("/hello", method = "OPTIONS", FUN = function(req, res) { res$set_header("Allow", "POST, OPTIONS") }) req = Request$new( path = "/hello", headers = list("Access-Control-Request-Method" = "POST"), method = "OPTIONS" ) app$process_request(req)
Controls how RestRserve encodes and decodes different content types. This middleware is passed by default to the Application constructor.
RestRserve::Middleware -> EncodeDecodeMiddleware
ContentHandlersClass which controls how RestRserve encodes and
decodes different content types. See ContentHandlers for documentation.
User can add new encoding and decoding methods for new content types
using set_encode and set_decode methods.
In theory user can replace it with his own class
(see RestRserve:::ContentHandlersFactory). However we believe that in
the majority of the cases using ContentHandlers will be enough.
new()
Creates EncodeDecodeMiddleware middleware object.
EncodeDecodeMiddleware$new(id = "EncodeDecodeMiddleware")
idMiddleware id.
clone()
The objects of this class are cloneable with this method.
EncodeDecodeMiddleware$clone(deep = FALSE)
deepWhether to make a deep clone.
Middleware Application ContentHandlers
Adds ETag to an Application.
ETags are header information that enable the caching of content.
If enabled, RestRserve will return an ETag (eg a hash of a file) alongside
the last time it was modified.
When a request is sent, additional headers such as
If-None-Match,
If-Match,
If-Modified-Since,
and
If-Unmodified-Since,
can be passed to the server as well.
If the conditions are met (different hash in case of a If-None-Match header
or a later file modification in case of a given If-Modified-Since header),
the server does not send the requested file but returns a
304 status
code, indicating, that the data on the requesting device is up-to-date.
Note that if both headers are provided, the If-None-Match header takes
precedence.
Furthermore, the middleware also supports the headers If-Match, which
returns the object if the hash matches (it also supports "*" to always return
the file), as well as If-Unmodified-Since, which returns the object if it
has not been modified since a certain time.
If the conditions are not met, a
412 status
code is returned (Precondition Failed).
See examples below.
RestRserve::Middleware -> EtagMiddleware
hash_functionFunction that takes an object or file and computes the hash of it
last_modified_functionFunction that takes an object or file and computes the last time it was modified
new()
Creates ETag middleware object
ETagMiddleware$new(
routes = "/",
match = "partial",
id = "ETagMiddleware",
hash_function = function(body) {
if ("file" %in% names(body)) {
digest::digest(file = body[["file"]], algo = "crc32")
}
else {
digest::digest(body, algo = "crc32")
}
},
last_modified_function = function(body) {
if ("file" %in% names(body)) {
as.POSIXlt(file.info(body[["file"]])[["mtime"]], tz = "GMT")
}
else {
as.POSIXlt(Sys.time(), tz = "GMT")
}
}
)routesRoutes paths to protect.
matchHow routes will be matched: exact or partial (as prefix).
idMiddleware id.
hash_functiona function that generates the ETag hash. The function takes the body of the response and returns a single character. Default is crc32 using digest::digest.
last_modified_functiona function that takes the body of the response and returns the last time this was changed. The default is to take the mtime (last time the file was modified) if its a file, if the body does not contain a file, the current time is returned ( resulting in no caching)
clone()
The objects of this class are cloneable with this method.
ETagMiddleware$clone(deep = FALSE)
deepWhether to make a deep clone.
############################################################################# # setup a static directory with ETag caching static_dir = file.path(tempdir(), "static") if (!dir.exists(static_dir)) dir.create(static_dir) file_path = file.path(static_dir, "example.txt") writeLines("Hello World", file_path) # get the time the file was last modified in UTC time last_modified = as.POSIXlt(file.info(file_path)[["mtime"]], tz = "UTC") file_hash = digest::digest(file = file_path, algo = "crc32") time_fmt = "%a, %d %b %Y %H:%M:%S GMT" ############################################################################# # setup the Application with the ETag Middleware app = Application$new() app$append_middleware(ETagMiddleware$new()) app$add_static(path = "/", static_dir) ############################################################################# # Example Requests # Request the file returns the file with ETag headers req = Request$new(path = "/example.txt") # note that it also returns the Last-Modified and ETag headers app$process_request(req) # provide matching hash of the file in the If-None-Match header to check Etag # => 304 Not Modified (Can be cached) req = Request$new(path = "/example.txt", headers = list("If-None-Match" = file_hash)) # note status_code 304 Not Modified app$process_request(req) # provide a wrong hash, returns the file normally req = Request$new(path = "/example.txt", headers = list("If-None-Match" = "WRONG HASH")) app$process_request(req) # alternatively, you can provide a timestamp in the If-Modified-Since header # => 304 Not Modified (Can be cached) modified_since = format(last_modified + 1, time_fmt) req = Request$new(path = "/example.txt", headers = list("If-Modified-Since" = modified_since)) app$process_request(req) # provide both headers: If-None-Match takes precedence # in this case: # - if none match => modified (No cache) # - if modified since => NOT MODIFIED (cached) # => Overall: modified = no cache modified_since = format(last_modified + 1, time_fmt) req = Request$new(path = "/example.txt", headers = list("If-None-Match" = "CLEARLY WRONG", "If-Modified-Since" = modified_since)) app$process_request(req) # provide matching hash of the file in the If-Match header to check Etag # => 412 Precondition Failed req = Request$new(path = "/example.txt", headers = list("If-Match" = "OTHER HASH")) # note status_code 412 Precondition Failed app$process_request(req) # Use If-Unmodified-Since unmodified_since = format(last_modified - 1, time_fmt) req = Request$new(path = "/example.txt", headers = list("If-Unmodified-Since" = unmodified_since) ) # note status_code 412 Precondition Failed app$process_request(req) ############################################################################# # use an alternative hash function (use name of the file) hash_on_filename = function(x) x # also use an alternate last_modified time function always_1900 = function(x) as.POSIXlt("1900-01-01 12:34:56", tz = "GMT") # setup the app again app = Application$new(middleware = list( ETagMiddleware$new(hash_function = hash_on_filename, last_modified_function = always_1900) )) app$add_static(path = "/", file_path = static_dir) # test the requests req = Request$new(path = "/example.txt") (res = app$process_request(req)) filename = res$body[["file"]] req = Request$new(path = "/example.txt", headers = list("If-None-Match" = filename)) app$process_request(req)############################################################################# # setup a static directory with ETag caching static_dir = file.path(tempdir(), "static") if (!dir.exists(static_dir)) dir.create(static_dir) file_path = file.path(static_dir, "example.txt") writeLines("Hello World", file_path) # get the time the file was last modified in UTC time last_modified = as.POSIXlt(file.info(file_path)[["mtime"]], tz = "UTC") file_hash = digest::digest(file = file_path, algo = "crc32") time_fmt = "%a, %d %b %Y %H:%M:%S GMT" ############################################################################# # setup the Application with the ETag Middleware app = Application$new() app$append_middleware(ETagMiddleware$new()) app$add_static(path = "/", static_dir) ############################################################################# # Example Requests # Request the file returns the file with ETag headers req = Request$new(path = "/example.txt") # note that it also returns the Last-Modified and ETag headers app$process_request(req) # provide matching hash of the file in the If-None-Match header to check Etag # => 304 Not Modified (Can be cached) req = Request$new(path = "/example.txt", headers = list("If-None-Match" = file_hash)) # note status_code 304 Not Modified app$process_request(req) # provide a wrong hash, returns the file normally req = Request$new(path = "/example.txt", headers = list("If-None-Match" = "WRONG HASH")) app$process_request(req) # alternatively, you can provide a timestamp in the If-Modified-Since header # => 304 Not Modified (Can be cached) modified_since = format(last_modified + 1, time_fmt) req = Request$new(path = "/example.txt", headers = list("If-Modified-Since" = modified_since)) app$process_request(req) # provide both headers: If-None-Match takes precedence # in this case: # - if none match => modified (No cache) # - if modified since => NOT MODIFIED (cached) # => Overall: modified = no cache modified_since = format(last_modified + 1, time_fmt) req = Request$new(path = "/example.txt", headers = list("If-None-Match" = "CLEARLY WRONG", "If-Modified-Since" = modified_since)) app$process_request(req) # provide matching hash of the file in the If-Match header to check Etag # => 412 Precondition Failed req = Request$new(path = "/example.txt", headers = list("If-Match" = "OTHER HASH")) # note status_code 412 Precondition Failed app$process_request(req) # Use If-Unmodified-Since unmodified_since = format(last_modified - 1, time_fmt) req = Request$new(path = "/example.txt", headers = list("If-Unmodified-Since" = unmodified_since) ) # note status_code 412 Precondition Failed app$process_request(req) ############################################################################# # use an alternative hash function (use name of the file) hash_on_filename = function(x) x # also use an alternate last_modified time function always_1900 = function(x) as.POSIXlt("1900-01-01 12:34:56", tz = "GMT") # setup the app again app = Application$new(middleware = list( ETagMiddleware$new(hash_function = hash_on_filename, last_modified_function = always_1900) )) app$add_static(path = "/", file_path = static_dir) # test the requests req = Request$new(path = "/example.txt") (res = app$process_request(req)) filename = res$body[["file"]] req = Request$new(path = "/example.txt", headers = list("If-None-Match" = filename)) app$process_request(req)
Conversions between POSIXct to HTTP Date objects.
from |
|
# convert POSIXct to HTTP date string as(0, "HTTPDate") # Thu, 01 Jan 1970 00:00:00 GMT as(Sys.time(), "HTTPDate") # parse HTTP date string to POSIXct dt = "Thu, 01 Jan 1970 00:00:00 GMT" class(dt) = "HTTPDate" as(dt, "POSIXct")# convert POSIXct to HTTP date string as(0, "HTTPDate") # Thu, 01 Jan 1970 00:00:00 GMT as(Sys.time(), "HTTPDate") # parse HTTP date string to POSIXct dt = "Thu, 01 Jan 1970 00:00:00 GMT" class(dt) = "HTTPDate" as(dt, "POSIXct")
request and reponse placeholders for IDE hints
.req .res.req .res
An object of class Request (inherits from R6) of length 28.
An object of class Response (inherits from R6) of length 26.
library(RestRserve) app = Application$new() app$add_get("/foo", FUN = function(.req, .res) { # since .res is a dummy instance of Response class # exported by RestRserve # IDE facilitates with autocompletion! .res$set_body("bar") # in the same time all the modifications happen with local objects # so you get right results in the end }) response = app$process_request(Request$new(path = "/foo")) response$bodylibrary(RestRserve) app = Application$new() app$add_get("/foo", FUN = function(.req, .res) { # since .res is a dummy instance of Response class # exported by RestRserve # IDE facilitates with autocompletion! .res$set_body("bar") # in the same time all the modifications happen with local objects # so you get right results in the end }) response = app$process_request(Request$new(path = "/foo")) response$body
Creates Logger object which can be used for logging with different level of verbosity. Log messages are in JSON format.
new()
Creates Logger object.
Logger$new(
level = c("info", "fatal", "error", "warn", "debug", "trace", "off", "all"),
name = "ROOT",
printer = NULL
)levelLog level. Allowed values: info, fatal, error, warn, debug, trace, off, all.
nameLogger name.
printerLogger with sink defined by printer function.
It should have signature function(timestamp, level, logger_name, pid, message).
By default when printer = NULL logger writes message in JSON format to stdout.
set_name()
Sets logger name.
Logger$set_name(name = "ROOT")
nameLogger name.
set_log_level()
Sets log level.
Logger$set_log_level(
level = c("info", "fatal", "error", "warn", "debug", "trace", "off", "all")
)levelLog level. Allowed values: info, fatal, error, warn, debug, trace, off, all.
set_printer()
Sets function which defines how to print logs.
Logger$set_printer(FUN = NULL)
FUNPrinter function. Should be a function with 6 formal arguments: timestamp, level, logger_name, pid, message.
trace()
Write trace message.
Logger$trace(msg, ...)
msgLog message.
...Additionals params.
debug()
Write debug message.
Logger$debug(msg, ...)
msgLog message.
...Additionals params.
info()
Write information message.
Logger$info(msg, ...)
msgLog message.
...Additionals params.
warn()
Write warning message.
Logger$warn(msg, ...)
msgLog message.
...Additionals params.
error()
Write error message.
Logger$error(msg, ...)
msgLog message.
...Additionals params.
fatal()
Write fatal error message.
Logger$fatal(msg, ...)
msgLog message.
...Additionals params.
clone()
The objects of this class are cloneable with this method.
Logger$clone(deep = FALSE)
deepWhether to make a deep clone.
# init logger logger = Logger$new("info") # write info message logger$info("hello world") # write extended log entry logger$info("", context = list(message = "hello world", code = 0L))# init logger logger = Logger$new("info") # write info message logger$info("hello world") # write extended log entry logger$info("", context = list(message = "hello world", code = 0L))
Creates Middleware object.
Middleware is a very useful concept which allows to perform
preprocessing of requests and post-processing of responses. Middleware has
an access to both request and response objects and can modify them.
This way each request can be checked/modified before passing handler and
response can be post processed (for example this way we developer can set up
custom error messages).
process_requestFunction which takes 2 arguments - request and
response objects (class Request and Response correspondingly) and
modify request and response or throw exception using HTTPError helper.
Function is called before request is routed to handler.
Usually process_request is used to perform logging, check authorization, etc.
process_responseFunction which takes 2 arguments - request and
response objects (class Request and Response correspondingly) and
modify request and response or throw exception using HTTPError helper.
Function is called after request is processed by handler.
Usually process_response is used to perform logging, custom error handling, etc.
idMiddleware id.
new()
Creates middleware object
Middleware$new( process_request = function(request, response) TRUE, process_response = function(request, response) TRUE, id = "Middleware" )
process_requestModify request or response objects or throw
exception using [HTTPError] helper. This function evaluate before
router handler called.
process_responseModify request or response objects or throw
exception using [HTTPError] helper. This function evaluate after
router handler called.
idMiddleware id.
clone()
The objects of this class are cloneable with this method.
Middleware$clone(deep = FALSE)
deepWhether to make a deep clone.
Interrupts request processing and signals RestRserve to return HTTPError
raise(x)raise(x)
x |
instance of Response. Can be created using HTTPError. see examples. |
None - stops execution of the current expression and executes an error action.
# catch exception res = try(raise(HTTPError$bad_request()), silent = TRUE) cond = attr(res, "condition") # response is a valid Response instace identical(cond$response$body$error, "400 Bad Request")# catch exception res = try(raise(HTTPError$bad_request()), silent = TRUE) cond = attr(res, "condition") # response is a valid Response instace identical(cond$response$body$error, "400 Bad Request")
Called internally for handling incoming requests from Rserve side. Also useful for testing.
pathRequest path.
methodRequest HTTP method.
headersRequest headers.
cookiesRequest cookies.
contextEnvironment to store any data. Can be used in middlewares.
content_typeRequest body content type.
bodyRequest body.
parameters_queryRequest query parameters.
parameters_bodyRequest body parameters.
parameters_pathList of parameters extracted from templated path
after routing. For example if we have some handler listening at
/job/{job_id} and we are receiving request at /job/1 then
parameters_path will be list(job_id = "1").
It is important to understand that parameters_path will be available
(not empty) only after request will reach handler.
This effectively means that parameters_path can be used inside handler
and response middleware (but not request middleware!).
filesStructure which contains positions and lengths of files for the multipart body.
decodeFunction to decode body for the specific content type.
idAutomatically generated UUID for each request. Read only.
dateRequest Date header converted to POSIXct.
acceptSplitted Accept request header.
accept_jsonRequest accepts JSON response.
accept_xmlRequest accepts XML response.
new()
Creates Request object
Request$new(
path = "/",
method = c("GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE",
"PATCH"),
parameters_query = list(),
parameters_body = list(),
headers = list(),
body = NULL,
cookies = list(),
content_type = NULL,
decode = NULL,
...
)pathCharacter with requested path. Always starts with /.
methodRequest HTTP method.
parameters_queryA named list with URL decoded query parameters.
parameters_bodyA named list with URL decoded body parameters. This field is helpful when request is a urlencoded form or a multipart form.
headersRequest HTTP headers represented as named list.
bodyRequest body. Can be anything and in conjunction with
content_type defines how HTTP body will be represented.
cookiesCookies represented as named list. Note that cookies
should be provided explicitly - they won't be derived from headers.
content_typeHTTP content type. Note that content_type
should be provided explicitly - it won't be derived from headers.
decodeFunction to decode body for the specific content type.
...Not used at this moment.
set_id()
Set request id.
Request$set_id(id = uuid::UUIDgenerate(TRUE))
idRequest id.
reset()
Resets request object. This is not useful for end user, but useful for RestRserve internals - resetting R6 class is much faster then initialize it.
Request$reset()
get_header()
Get HTTP response header value. If requested header is empty returns default.
Request$get_header(name, default = NULL)
nameHeader field name.
defaultDefault value if header does not exists.
Header field values (character string).
get_param_query()
Get request query parameter by name.
Request$get_param_query(name)
nameQuery parameter name.
Query parameter value (character string).
get_param_body()
Get request body parameter by name.
Request$get_param_body(name)
nameBody field name.
Body field value.
get_param_path()
Get templated path parameter by name.
Request$get_param_path(name)
namePath parameter name.
Path parameter value.
get_file()
Extract specific file from multipart body.
Request$get_file(name)
nameBody file name.
Raw vector with filname and content-type attributes.
print()
Print method.
Request$print()
clone()
The objects of this class are cloneable with this method.
Request$clone(deep = FALSE)
deepWhether to make a deep clone.
# init simply request rq = Request$new( path = "/", parameters_query = list( "param1" = "value1", "param2" = "value2" ), headers = list( "Content-encoding" = "identity", "Custom-field" = "value" ), cookies = list( "sessionId" = "1" ) ) # get request UUID rq$id # get content accept rq$accept # get request content type rq$content_type # get header by name (lower case) rq$get_header("custom-field") # get query param by name rq$get_param_query("param1") # print request rq# init simply request rq = Request$new( path = "/", parameters_query = list( "param1" = "value1", "param2" = "value2" ), headers = list( "Content-encoding" = "identity", "Custom-field" = "value" ), cookies = list( "sessionId" = "1" ) ) # get request UUID rq$id # get content accept rq$accept # get request content type rq$content_type # get header by name (lower case) rq$get_header("custom-field") # get query param by name rq$get_param_query("param1") # print request rq
Creates response object.
bodyResponse body.
If it is a named character with a name file or tmpfile
then the value is considered as a path to a file and content oh this file
is served as body. The latter will be deleted once served.
content_typeResponse body content (media) type. Will be translated
to Content-type header.
headersResponse headers.
status_codeResponse HTTP status code.
cookiesResponse cookies. Will be translated to Set-Cookie headers.
contextEnvironment to store any data. Can be used in middlewares.
encodeFunction to encode body for specific content.
statusPaste together status code and description.
new()
Creates Response object
Response$new(
body = NULL,
content_type = "text/plain",
headers = list(Server = getOption("RestRserve.headers.server")),
status_code = 200L,
encode = NULL,
...
)bodyResponse body.
content_typeResponse body content (media) type.
headersResponse headers.
status_codeResponse status code.
encodeFunction to encode body for specific content.
...Not used at this moment.
reset()
Resets response object. This is not useful for end user, but useful for RestRserve internals - resetting R6 class is much faster then initialize it.
Response$reset()
set_content_type()
Set content type for response body.
Response$set_content_type(content_type = "text/plain")
content_typeResponse body content (media) type.
set_status_code()
Set HTTP status code for response. See docs on MDN.
Response$set_status_code(code)
codeStatus code as integer number.
has_header()
Determine whether or not the response header exists.
Response$has_header(name)
nameHeader field name.
Logical value.
get_header()
Get HTTP response header value. If requested header is empty returns default.
Response$get_header(name, default = NULL)
nameHeader field name.
defaultDefault value if header does not exists.
Header field values (character string).
set_header()
Set HTTP response header. Content-type and Content-length headers not
allowed (use content_type field instead).
Response$set_header(name, value)
nameHeader field name.
valueHeader field value.
delete_header()
Unset HTTP response header.
Response$delete_header(name)
nameHeader field name.
Logical value.
append_header()
Append HTTP response header. If header exists , separator will be used.
Don't use this method to set cookie (use set_cookie method instead).
Response$append_header(name, value)
nameHeader field name.
valueHeader field value.
set_date()
Set Date HTTP header. See docs on MDN.
Response$set_date(dtm = Sys.time())
dtmPOSIXct value.
unset_date()
Unset Date HTTP header.
Response$unset_date()
Logical value.
set_cookie()
Set cookie. See docs on MDN.
Response$set_cookie( name, value, expires = NULL, max_age = NULL, domain = NULL, path = NULL, secure = NULL, http_only = NULL )
nameCookie name.
valueCookie value.
expiresCookie expires date and time (POSIXct).
max_ageMax cookie age (integer).
domainCookie domain.
pathCookie path.
secureCookie secure flag.
http_onlyCookie HTTP only flag.
unset_cookie()
Unset cookie with given name.
Response$unset_cookie(name)
nameCookie name.
Logical value.
set_body()
Set response body.
Response$set_body(body)
bodyResponse body.
set_response()
Set response fields.
Response$set_response( status_code, body = NULL, content_type = self$content_type )
status_codeResponse HTTP status code.
bodyResponse body.
content_typecontent_type Response body content (media) type.
print()
Print method.
Response$print()
clone()
The objects of this class are cloneable with this method.
Response$clone(deep = FALSE)
deepWhether to make a deep clone.
# init response rs = Response$new() # set body media type rs$set_content_type("text/plain") # set body content rs$set_body("OK") # set response status code rs$set_status_code(200L) # print response rs # init response rs = Response$new() # static file path file_path = system.file("DESCRIPTION", package = "RestRserve") # get last file modification timestamp file_mtime = file.mtime(file_path) # set body rs$set_body(c("file" = file_path)) # set content type rs$set_content_type("text/plain") # set current timestamp rs$set_date() # set 'last-modified' header rs$set_header("Last-Modified", as(file_mtime, "HTTPDate")) # print response rs# init response rs = Response$new() # set body media type rs$set_content_type("text/plain") # set body content rs$set_body("OK") # set response status code rs$set_status_code(200L) # print response rs # init response rs = Response$new() # static file path file_path = system.file("DESCRIPTION", package = "RestRserve") # get last file modification timestamp file_mtime = file.mtime(file_path) # set body rs$set_body(c("file" = file_path)) # set content type rs$set_content_type("text/plain") # set current timestamp rs$set_date() # set 'last-modified' header rs$set_header("Last-Modified", as(file_mtime, "HTTPDate")) # print response rs
Encode R objects as JSON. Wrapper around jsonlite::toJSON with
default parameters set to following values:
dataframe = 'columns', auto_unbox = unbox, null = 'null', na = 'null'.
to_json(x, unbox = TRUE)to_json(x, unbox = TRUE)
x |
the object to be encoded |
unbox |
|
JSON string
to_json(NULL) to_json(list(name = "value"))to_json(NULL) to_json(list(name = "value"))