Use with care: Jug is still under heavy development and syntax/logic can change often
library(jug)
jug() %>%
get("/", function(req, res, err){
"Hello World!"
}) %>%
serve_it()
Serving the jug at http://127.0.0.1:8080
Jug is a small web development framework for R which relies heavily upon the httpuv
package. It’s main focus is to make building APIs for your code as easy as possible.
Jug is not supposed to be either an especially performant nor an uber stable framework. Other tools (and languages) might be more suited for that. Nevertheless, it tries to make available a set of functions which allow you to easily create APIs for your R code. The flexibility of Jug means that, in theory, you could built an extensive web framework with it (but I don’t especially recommend it).
To install the latest version use devtools
:
devtools::install_github("Bart6114/jug")
Or install the CRAN version:
install.packags("jug")
Everything starts with a Jug instance. This instance is created by simply calling jug()
:
library(jug)
jug()
## A Jug instance with 0 middlewares attached
Jug is made to work closely with the piping functionality of magrittr
(%>%
). The rest of the Jug instance is built up by piping the instance through various functions explained below.
In terms of middleware Jug somewhat follows the specification of middleware by Express
. In Jug, middleware is a function with access to the request (req
), response (res
) and error (err
) object.
Multiple middlewares can be defined. Order in which the middlewares are added matters. A request will start with being passed through the first middleware added (more specifically the functions specified in it - see next paragraph). It will continue to be passed through the added middlewares until a middleware does not return NULL
. Whatever will be passed by that middleware will be set as the response body.
Most middleware will accept a func
or ...
argument to which respectively a function or multiple functions can be passed. If multiple functions are passed; passing order will be respected. To each function the req
, res
and err
objects will be passed (and thus should accept them). The result of evaluating the function will be used as the response body (if the returned object is not null).
The use
function is a method insensitive middleware specifier. While it is method insensitive, it can be bound to a specific path. If the path
argument (accepts a regex string with grepl
setting perl=TRUE
) is set to NULL
it also becomes path insensitive and will process every request.
A path insensitive example:
jug() %>%
use(path = NULL, function(req, res, err){
"test 1,2,3!"
}) %>%
serve_it()
$ curl 127.0.0.1:8080/xyz
test 1,2,3!
The same example, but path sensitive:
jug() %>%
use(path = "/", function(req, res, err){
"test 1,2,3!"
}) %>%
serve_it()
$ curl 127.0.0.1:8080/xyz
curl: (52) Empty reply from server
$ curl 127.0.0.1:8080
test 1,2,3!
Note that in the above error / missing route handling is missing (the server will crash), more on that later.
In the same style ass the request method insensitive middleware, there is request method sensitive middleware available. More specifically, you can use the get
, post
, put
and delete
functions.
This type of middleware is bound to a path using the path
argument. If path
is set to NULL
it will bind to every request to the path, given that it is of the corresponding request type.
jug() %>%
get(path = "/", function(req, res, err){
"get test 1,2,3!"
}) %>%
serve_it()
$ curl 127.0.0.1:8080
get test 1,2,3!
Middlewares are meant to be chained, so to bind different functions to different paths:
jug() %>%
get(path = "/", function(req, res, err){
"get test 1,2,3 on path /"
}) %>%
get(path = "/my_path", function(req, res, err){
"get test 1,2,3 on path /my_path"
}) %>%
serve_it()
$ curl 127.0.0.1:8080
get test 1,2,3 on path /
$ curl 127.0.0.1:8080/my_path
get test 1,2,3 on path /my_path
By default all middleware convenience function bind to the http protocol. You can however access the jug server through websocket by using the websocket sensitive middleware function ws
. Below an example echo’ing the incoming message.
jug() %>%
ws("/echo_message", function(binary, message, res, err){
message
}) %>%
serve_it()
Opening a connection to ws://127.0.0.1:8080/echo_message
and sending e.g. the message test
to it will then return the value test
.
You can include middleware that has been defined elsewhere within your current R environment or in a different R file. To do this you can use a collector
Below a collector
is defined locally and include
d.
collected_mw<-
collector() %>%
get("/", function(req,res,err){
return("test")
})
res<-jug() %>%
include(collected_mw) %>%
serve_it()
However, it is also possible to include
a collector
that is defined in another .R file.
Let’s say below is the file my_middlewares.R
:
library(jug)
collected_mw<-
collector() %>%
get("/", function(req,res,err){
return("test2")
})
We can include it as follows:
res<-jug() %>%
include(collected_mw, "my_middlewares.R") %>%
serve_it()
A simple error handling middleware (simple_error_handler
) which catches unbound paths and func
evaluation errors. If no custom error handler is implemented I suggest you add this to your Jug instance.
jug() %>%
simple_error_handler() %>%
serve_it()
$ curl 127.0.0.1:8080
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Not found</title>
</head>
<body>
<p>No handler bound to path</p>
</body>
</html>
If you want to implement your own custom error handling just have a look at the jug::simple_error_handler()
function.
The main reason Jug was created is to easily allow access to your own custom R functions. The convenience function decorate
is built with this in mind.
If you decorate
your own function it will translate all arguments passed in the query string of the request as arguments to your function. It will also pass all headers to the function as arguments. Note that for the passed headers they will be capitalized and prefixed by HTTP_
as per httpuv
’s internals.
If your function does not accept a ...
argument, all query/header parameters that are not explicitly requested by your function are dropped. If your function requests a req
, res
or err
argument (or ...
) the corresponding objects will be passed.
say_hello<-function(name){paste("hello",name,"!")}
jug() %>%
get("/", decorate(say_hello)) %>%
serve_it()
$ curl 127.0.0.1:8080/?name=Bart
hello Bart !
The serve_static_file
middleware allows for serving static files.
jug() %>%
serve_static_files() %>%
serve_it()
The default root directory is the one returned by getwd()
but can be specified by providing a root_path
argument to the serve_static_files
middleware. It transforms a bare /
path to index.html
.
req
) objectThe req
object contains the request specifications. It has different attributes:
req$params
a list of the query parameters contained in the URLreq$path
the requested pathreq$method
the request methodreq$raw
the raw request object as passsed by httpuv
req$post_data
the post data (has some limitations for now…)req$protocol
either http
or websocket
It has the following functions attached to it:
req$get_header(key)
returns the value associated to the specified key in the request (no need to worry about the HTTP_
prefix)req$attach(key, value)
attach a variable to req$params
res
) objectThe res
object contains the response specifications. It has different attributes:
res$headers
a named list of the set headersres$status
the status of the response (defaults to 200)res$body
the body of the response (is automatically set by the content of the not NULL
returning middleware)It also has a set of functions:
res$set_header(key, value)
set a custom headerres$content_type(type)
set your own content type (MIME)res$set_status(status)
set the status of the responseres$text(body)
to explicitely set the body of the responseres$json(obj)
converts an object to JSON, sets it as the body and set the correct content typeerr
) objectThe err
object contains a list of errors, accessible through err$errrors
. You can add an error to this list by calling err$set(error)
. The error will be converted to a character.
Have a look at the simple_error_handler
middleware function see how you can implement a custom error handler.
The path parameter in the get
, post
, … functions is intepreted as a regex pattern.
If there are named capture groups in the path definition, they will be attached to the req$params
object. For example the pattern /test/(?<id>.*)/(?<id2>.*)
will result in the variables id
and id2
(with their respective values) being bound to the req$params
object.
If a path pattern is not started with a start of string ^
regex token or ended with an end of string token $
, these will be explicitely inserted at respectively the beginning and end of the path pattern specification. For example the path pattern /
will be converted to ^/$
.
Simple call serve_it()
at the end of your piping chain (see Hello World! example).