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] |
Maintainer: | Dmitry Selivanov <[email protected]> |
License: | GPL (>= 2) |
Version: | 1.2.2 |
Built: | 2025-03-13 04:29:45 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")
logger
Logger 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_type
Default response body content type.
HTTPError
Class 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.
endpoints
Prints all the registered routes with allowed methods.
new()
Creates Application object.
Application$new( middleware = list(EncodeDecodeMiddleware$new()), content_type = "text/plain", ... )
middleware
List of Middleware objects.
content_type
Default 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"), ... )
path
Endpoint path.
method
HTTP method. Allowed methods at the moment:
GET
, HEAD
, POST
, PUT
, DELETE
, OPTIONS
, PATCH
.
FUN
User 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.
match
Defines 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 )
path
Endpoint path.
FUN
User 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.
match
Defines 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_head
Adds HEAD method.
add_post()
Shorthand to Application$add_route()
with POST
method.
Application$add_post(path, FUN, match = c("exact", "partial", "regex"), ...)
path
Endpoint path.
FUN
User 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.
match
Defines 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, ...)
path
Endpoint path.
file_path
Path file or directory.
content_type
MIME-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")
path
path Endpoint path.
file_path
Path 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" )
path
path Endpoint path.
path_openapi
Path to the OpenAPI specification file.
use_cdn
Use CDN to load Swagger UI libraries.
path_swagger_assets
Swagger UI asstes endpoint.
file_path
Path to Swagger UI HTML file.
append_middleware()
Appends middleware to handlers pipeline.
Application$append_middleware(mw)
mw
Middleware object.
process_request()
Process incoming request and generate Response object.
Application$process_request(request = NULL)
request
Request 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)
deep
Whether 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.
pid
Process identificator.
new()
Creates ApplicationProcess object
ApplicationProcess$new(pid)
pid
Process identificator.
kill()
Send signal to process.
ApplicationProcess$kill(signal = 15L)
signal
Signal code.
clone()
The objects of this class are cloneable with this method.
ApplicationProcess$clone(deep = FALSE)
deep
Whether to make a deep clone.
Creates AuthBackendBasic class object.
RestRserve::AuthBackend
-> AuthBackendBasic
new()
Creates AuthBackendBasic class object.
AuthBackendBasic$new(FUN)
FUN
Function 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)
deep
Whether 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)
FUN
Function 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)
deep
Whether 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) # TRUE
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) # TRUE
Adds various authorizations to Application.
RestRserve::Middleware
-> AuthMiddleware
new()
Creeates AuthMiddleware object.
AuthMiddleware$new( auth_backend, routes, match = "exact", id = "AuthMiddleware" )
auth_backend
Authentication backend.
routes
Routes paths to protect.
match
How routes will be matched: "exact"
or "partial"
(as prefix).
id
Middleware id.
clone()
The objects of this class are cloneable with this method.
AuthMiddleware$clone(deep = FALSE)
deep
Whether 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_level
changes R's byte compiler level to this value before app start.
precompile
try 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)
app
Application object.
http_port
HTTP 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.
background
Whether 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 )
request
Request object.
path
Character with requested path. Always starts with /
.
parameters_query
A named character vector with URL decoded query parameters.
headers
Request HTTP headers.
body
Request 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)
response
Response 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)
deep
Whether 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")
routes
Routes paths to protect.
match
How routes will be matched: exact or partial (as prefix).
id
Middleware id.
clone()
The objects of this class are cloneable with this method.
CORSMiddleware$clone(deep = FALSE)
deep
Whether 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
ContentHandlers
Class 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")
id
Middleware id.
clone()
The objects of this class are cloneable with this method.
EncodeDecodeMiddleware$clone(deep = FALSE)
deep
Whether 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_function
Function that takes an object or file and computes the hash of it
last_modified_function
Function 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") } } )
routes
Routes paths to protect.
match
How routes will be matched: exact or partial (as prefix).
id
Middleware id.
hash_function
a 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_function
a 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)
deep
Whether 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$body
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$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 )
level
Log level. Allowed values: info, fatal, error, warn, debug, trace, off, all.
name
Logger name.
printer
Logger 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")
name
Logger name.
set_log_level()
Sets log level.
Logger$set_log_level( level = c("info", "fatal", "error", "warn", "debug", "trace", "off", "all") )
level
Log 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)
FUN
Printer function. Should be a function with 6 formal arguments: timestamp, level, logger_name, pid, message.
trace()
Write trace message.
Logger$trace(msg, ...)
msg
Log message.
...
Additionals params.
debug()
Write debug message.
Logger$debug(msg, ...)
msg
Log message.
...
Additionals params.
info()
Write information message.
Logger$info(msg, ...)
msg
Log message.
...
Additionals params.
warn()
Write warning message.
Logger$warn(msg, ...)
msg
Log message.
...
Additionals params.
error()
Write error message.
Logger$error(msg, ...)
msg
Log message.
...
Additionals params.
fatal()
Write fatal error message.
Logger$fatal(msg, ...)
msg
Log message.
...
Additionals params.
clone()
The objects of this class are cloneable with this method.
Logger$clone(deep = FALSE)
deep
Whether 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_request
Function 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_response
Function 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.
id
Middleware id.
new()
Creates middleware object
Middleware$new( process_request = function(request, response) TRUE, process_response = function(request, response) TRUE, id = "Middleware" )
process_request
Modify request
or response
objects or throw
exception using [HTTPError]
helper. This function evaluate before
router handler called.
process_response
Modify request
or response
objects or throw
exception using [HTTPError]
helper. This function evaluate after
router handler called.
id
Middleware id.
clone()
The objects of this class are cloneable with this method.
Middleware$clone(deep = FALSE)
deep
Whether 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.
path
Request path.
method
Request HTTP method.
headers
Request headers.
cookies
Request cookies.
context
Environment to store any data. Can be used in middlewares.
content_type
Request body content type.
body
Request body.
parameters_query
Request query parameters.
parameters_body
Request body parameters.
parameters_path
List 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!).
files
Structure which contains positions and lengths of files for the multipart body.
decode
Function to decode body for the specific content type.
id
Automatically generated UUID for each request. Read only.
date
Request Date
header converted to POSIXct
.
accept
Splitted Accept
request header.
accept_json
Request accepts JSON response.
accept_xml
Request 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, ... )
path
Character with requested path. Always starts with /
.
method
Request HTTP method.
parameters_query
A named list with URL decoded query parameters.
parameters_body
A named list with URL decoded body parameters. This field is helpful when request is a urlencoded form or a multipart form.
headers
Request HTTP headers represented as named list.
body
Request body. Can be anything and in conjunction with
content_type
defines how HTTP body will be represented.
cookies
Cookies represented as named list. Note that cookies
should be provided explicitly - they won't be derived from headers
.
content_type
HTTP content type. Note that content_type
should be provided explicitly - it won't be derived from headers
.
decode
Function 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))
id
Request 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)
name
Header field name.
default
Default 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)
name
Query parameter name.
Query parameter value (character string).
get_param_body()
Get request body parameter by name.
Request$get_param_body(name)
name
Body field name.
Body field value.
get_param_path()
Get templated path parameter by name.
Request$get_param_path(name)
name
Path parameter name.
Path parameter value.
get_file()
Extract specific file from multipart body.
Request$get_file(name)
name
Body 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)
deep
Whether 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.
body
Response 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_type
Response body content (media) type. Will be translated
to Content-type
header.
headers
Response headers.
status_code
Response HTTP status code.
cookies
Response cookies. Will be translated to Set-Cookie
headers.
context
Environment to store any data. Can be used in middlewares.
encode
Function to encode body for specific content.
status
Paste 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, ... )
body
Response body.
content_type
Response body content (media) type.
headers
Response headers.
status_code
Response status code.
encode
Function 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_type
Response body content (media) type.
set_status_code()
Set HTTP status code for response. See docs on MDN.
Response$set_status_code(code)
code
Status code as integer number.
has_header()
Determine whether or not the response header exists.
Response$has_header(name)
name
Header field name.
Logical value.
get_header()
Get HTTP response header value. If requested header is empty returns default
.
Response$get_header(name, default = NULL)
name
Header field name.
default
Default 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)
name
Header field name.
value
Header field value.
delete_header()
Unset HTTP response header.
Response$delete_header(name)
name
Header 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)
name
Header field name.
value
Header field value.
set_date()
Set Date
HTTP header. See docs on MDN.
Response$set_date(dtm = Sys.time())
dtm
POSIXct 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 )
name
Cookie name.
value
Cookie value.
expires
Cookie expires date and time (POSIXct).
max_age
Max cookie age (integer).
domain
Cookie domain.
path
Cookie path.
secure
Cookie secure flag.
http_only
Cookie HTTP only flag.
unset_cookie()
Unset cookie with given name.
Response$unset_cookie(name)
name
Cookie name.
Logical value.
set_body()
Set response body.
Response$set_body(body)
body
Response body.
set_response()
Set response fields.
Response$set_response( status_code, body = NULL, content_type = self$content_type )
status_code
Response HTTP status code.
body
Response body.
content_type
content_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)
deep
Whether 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"))