Let’s consider an example. We develop an application which calculates factorial of a number:
library(RestRserve)
backend = BackendRserve$new()
application = Application$new()
application$add_get(path = "/factorial", function(req, res) {
x = req$get_param_query("x")
x = as.integer(x)
res$set_body(factorial(x))
})Here is how request will be processed:
request = Request$new(
path = "/factorial",
method = "GET",
parameters_query = list(x = 10)
)
response = application$process_request(request)
response
#> <RestRserve Response>
#> status code: 200 OK
#> content-type: text/plain
#> <Headers>
#> Server: RestRserve/0.2.0.1Let’s take a closer look to the response object and its body property:
As we can see it is a numeric value. HTTP response body however can’t be an arbitrary R object. It should be something that external systems can understand - either character vector or raw vector. Fortunately application helps to avoid writing boilerplate code to encode the body. Based on the content_type property it can find encode function which will be used to transform body into a http body.
Two immediate questions can arise:
content_type is equal to text/plain?
content_type in Application constructor. It is text/plain by default, which means all the responses by default will have text/plain content type.text/plain? Can it encode any arbitrary content type?
ContentHandlers property. Out of the box it supports two content types - text/plain and application/json.For instance app1 and app2 are equal:
encode_decode_middleware = EncodeDecodeMiddleware$new()
app1 = Application$new(middleware = list())
app1$append_middleware(encode_decode_middleware)
app2 = Application$new()Here is example on how you can get the actual function used for application/json encoding:
FUN = encode_decode_middleware$ContentHandlers$get_encode('application/json')
FUN
#> function(x, unbox = TRUE) {
#> res = jsonlite::toJSON(x, dataframe = 'columns', auto_unbox = unbox, null = 'null', na = 'null')
#> unclass(res)
#> }
#> <bytecode: 0x7fda0d402d98>
#> <environment: namespace:RestRserve>We can manually override application default content-type:
application$add_get(path = "/factorial-json", function(req, res) {
x = as.integer(req$get_param_query("x"))
result = factorial(x)
res$set_body(list(result = result))
res$set_content_type("application/json")
})request = Request$new(
path = "/factorial-json",
method = "GET",
parameters_query = list(x = 10)
)
response = application$process_request(request)And here is a little bit more complex example where we store a binary object in the body. We will use R’s native serialization, but one can use protobuf, messagepack, etc.
application$add_get(path = "/factorial-rds", function(req, res) {
x = as.integer(req$get_param_query("x"))
result = factorial(x)
body_rds = serialize(list(result = result), connection = NULL)
res$set_body(body_rds)
res$set_content_type("application/x-rds")
})However function above won’t work correctly. Out of the box ContentHndlers doesn’t know anything about application/x-rds:
request = Request$new(
path = "/factorial-rds",
method = "GET",
parameters_query = list(x = 10)
)
response = application$process_request(request)
response$body
#> [1] "500 Internal Server Error: can't encode body with content_type = 'application/x-rds'"In order to resolve problem above we would need to either register application/x-rds content handler with ContentHandlers$set_encode() or manually specify encode function (identity in our case):
application$add_get(path = "/factorial-rds2", function(req, res) {
x = as.integer(req$get_param_query("x"))
result = factorial(x)
body_rds = serialize(list(result = result), connection = NULL)
res$set_body(body_rds)
res$set_content_type("application/x-rds")
res$encode = identity
})Now the answer is valid:
RestRserve facilitates with parsing incoming request body as well. Consider a service which expects JSON POST requests:
application = Application$new(content_type = "application/json")
application$add_post("/echo", function(req, res) {
res$set_body(req$body)
})
request = Request$new(path = "/echo", method = "POST", body = '{"hello":"world"}', content_type = "application/json")
response = application$process_request(request)
response$body
#> [1] "{\"hello\":\"world\"}"The logic behind decoding is also controlled by [EncodeDecodeMiddleware] and its ContentHandlers property.