The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
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/1.2.3; Rserve/1.8.13
Let’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: 0x7fc52bdc84d0>
#> <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.
Here is an example which demonstrates on how to extend ?EncodeDecodeMiddleware to handle additional content types:
encode_decode_middleware = EncodeDecodeMiddleware$new()
encode_decode_middleware$ContentHandlers$set_encode(
"text/csv",
function(x) {
con = rawConnection(raw(0), "w")
on.exit(close(con))
write.csv(x, con, row.names = FALSE)
rawConnectionValue(con)
}
)
encode_decode_middleware$ContentHandlers$set_decode(
"text/csv",
function(x) {
res = try({
con = textConnection(rawToChar(x), open = "r")
on.exit(close(con))
read.csv(con)
}, silent = TRUE)
if (inherits(res, "try-error")) {
raise(HTTPError$bad_request(body = attributes(res)$condition$message))
}
return(res)
}
)
Extended middleware needs to be provided to the application constructor:
Now let’s test it:
app$add_get("/iris", FUN = function(.req, .res) {
.res$set_content_type("text/csv")
.res$set_body(iris)
})
req = Request$new(path = "/iris", method = "GET")
res = app$process_request(req)
iris_out = read.csv(textConnection(rawToChar(res$body)))
head(iris_out)
#> Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#> 1 5.1 3.5 1.4 0.2 setosa
#> 2 4.9 3.0 1.4 0.2 setosa
#> 3 4.7 3.2 1.3 0.2 setosa
#> 4 4.6 3.1 1.5 0.2 setosa
#> 5 5.0 3.6 1.4 0.2 setosa
#> 6 5.4 3.9 1.7 0.4 setosa
app$add_post("/in", FUN = function(.req, .res) {
str(.req$body)
})
req = Request$new(path = "/in", method = "POST", body = res$body, content_type = "text/csv")
app$process_request(req)
#> 'data.frame': 150 obs. of 5 variables:
#> $ Sepal.Length: num 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
#> $ Sepal.Width : num 3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
#> $ Petal.Length: num 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
#> $ Petal.Width : num 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
#> $ Species : chr "setosa" "setosa" "setosa" "setosa" ...
#> <RestRserve Response>
#> status code: 200 OK
#> content-type: text/plain
#> <Headers>
#> Server: RestRserve/1.2.3; Rserve/1.8.13
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.