knitr::opts_chunk$set( collapse = TRUE, comment = "#>" )
run_bg = function(expr) { args = c("--vanilla", "-q") expr_c = deparse(substitute(expr)) expr_c = paste(expr_c, collapse = "\n") pid = sys::r_background(c(args, "-e", expr_c), std_out = TRUE, std_err = TRUE) return(pid) }
Suppose you've developed a very useful algorithm or statistical model and you need to integrate it with some external system. Nowadays HTTP became de facto a lingua-franca for this kind of tasks.
In this article we will demonstrate how to use RestRserve to build a basic REST API.
Generally RestRserve workflow consists of several major steps:
Application$new()
request
and response
as an input. request
and response
are instances of RestRserve::Request
and RestRserve::Response
. It is important to remember that both request
and response
are mutable objects.response
in place or raise()
exception in case of errorlibrary(RestRserve) app = Application$new()
For simplicity we will use Fibonacci number calculation as an algorithm we want to expose.
calc_fib = function(n) { if (n < 0L) stop("n should be >= 0") if (n == 0L) return(0L) if (n == 1L || n == 2L) return(1L) x = rep(1L, n) for (i in 3L:n) { x[[i]] = x[[i - 1]] + x[[i - 2]] } return(x[[n]]) }
Create function which will handle requests.
fib_handler = function(.req, .res) { n = as.integer(.req$parameters_query[["n"]]) if (length(n) == 0L || is.na(n)) { raise(HTTPError$bad_request()) } .res$set_body(as.character(calc_fib(n))) .res$set_content_type("text/plain") }
You may have noticed strange .req
and .res
argument names. Starting from RestRserve
v0.4.0 these "reserved" names allows to benefit from autocomplete:
Technically .req
and .res
are just empty instances of ?Request
and ?Response
classes exported by RestRserve
in order to make autocomplete work.
app$add_get(path = "/fib", FUN = fib_handler)
Now we can test our application without starting it:
request = Request$new(path = "/fib", parameters_query = list(n = 10)) response = app$process_request(request) cat("Response status:", response$status) cat("Response body:", response$body)
It is generally a good idea to write unit tests against application. One can use a common framework such as tinytest.
Generally it is a good idea to provide documentation along with the API. Convenient way to do that is to supply a openapi specification. This as simple as adding a yaml file as an additional endpoint:
openapi: 3.0.1 info: title: RestRserve OpenAPI version: '1.0' servers: - url: / paths: /fib: get: description: Calculates Fibonacci number parameters: - name: "n" description: "x for Fibonnacci number" in: query schema: type: integer example: 10 required: true responses: 200: description: API response content: text/plain: schema: type: string example: 5 400: description: Bad Request
yaml_file = system.file("examples", "openapi", "openapi.yaml", package = "RestRserve") app$add_openapi(path = "/openapi.yaml", file_path = yaml_file) app$add_swagger_ui(path = "/doc", path_openapi = "/openapi.yaml", use_cdn = TRUE)
Now all is ready and we can start application with Rserve backend. It will block R session and start listening for incoming requests.
backend = BackendRserve$new() backend$start(app, http_port = 8080)
Send request to calculate fibonacci number:
```{bash eval = FALSE} curl localhost:8080/fib?n=10
Check out a swagger UI in the browser: `http://localhost:8080/doc` ```r pid = run_bg({ library(RestRserve) calc_fib = function(n) { if (n < 0L) stop("n should be >= 0") if (n == 0L) return(0L) if (n == 1L || n == 2L) return(1L) x = rep(1L, n) for (i in 3L:n) x[[i]] = x[[i - 1]] + x[[i - 2]] x[[n]] } fib_handler = function(.req, .res) { n = as.integer(.req$parameters_query[["n"]]) if (length(n) == 0L || is.na(n)) { raise(HTTPError$bad_request()) } .res$set_body(as.character(calc_fib(n))) .res$set_content_type("text/plain") } app = RestRserve::Application$new() app$add_get(path = "/fib", FUN = fib_handler) yaml_file = system.file("examples", "openapi", "openapi.yaml", package = "RestRserve") app$add_openapi(path = "/openapi.yaml", file_path = yaml_file) app$add_swagger_ui(path = "/doc", path_openapi = "/openapi.yaml", use_cdn = TRUE) backend = BackendRserve$new() backend$start(app, http_port = 8080) }) tools::pskill(pid)
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.