Jug

Bart Smeets

2016-07-28

generated using Jug version 0.1.3

Hello World!

library(jug)

jug() %>%
  get("/", function(req, res, err){
    "Hello World!"
  }) %>%
  simple_error_handler_json() %>%
  serve_it()
Serving the jug at http://127.0.0.1:8080

What is Jug?

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 web framework. Other tools (and languages) might be more suited for that. It’s main focus is to easily allow you to create APIs for your R code. However, the flexibility of Jug means that, in theory, you could built an extensive web framework with it.

Getting started

To install the latest version use devtools:

devtools::install_github("Bart6114/jug")

Or install the CRAN version:

install.packags("jug")

Load the library:

library(jug)

The Jug instance

Everything starts with a Jug instance. This instance is created by simply calling jug():

jug()
## A Jug instance with 0 middlewares attached

Jug is made to work closely with the piping functionality of magrittr (%>%). The configuration of the Jug instance is set up by piping the instance through the various functions explained below.

Middleware

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. The 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 (note: if a value is set using e.g. res$json("foo") the body will not be 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; the order in which they are passed will be respected when processing a request. To each function the req, res and err objects will be passed (and they thus should accept them).

Method insensitive middleware

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!

It is however possible to specify a method to bind to using use (check out ?use), this way you can process request methods for which no prespecified middlewares exist.

Note that in the above example errors / missing route handling is missing (the server might crash / not respond), more on that later.

Method sensitive middleware

In the same style as 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 method.

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

Websocket protocol

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.

Please note that websocket support is experimental at this stage.

Including elsewhere defined middleware

In order to make you code more modular, you can include elsewhere defined middleware into your Jug instance. To do this you can use a combination of the collector() and include() functions.

Below a collector is defined locally (in the same R script) and included.

 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()

Predefined middleware

Error handling

A simple error handling middleware (simple_error_handler / simple_error_handler_json) which catches unbound paths and func evaluation errors. If you do not implement a custom error handler, I suggest you add either of these to your Jug instance. The simple_error_handler returns an HTML error page while the simple_error_handler_json returns a JSON message.

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 code of these simple error handling middlewares.

Please note that generally you would like the error handler middleware to be attached to the Jug instance after all other middleware has been specified.

Easily using your own functions

The main reason Jug was created is to easily allow access to your own custom R functions. The convenience function decorate is built especially for this purpose.

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.

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()

If in the above, you pass a parameter name through either the query string or as a header in the GET request, it will return as in the example below.

$ curl 127.0.0.1:8080/?name=Bart
hello Bart !

Static file server

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.

I do not recommend using Jug to serve static files, except for in a testing phase.

The request, response and error objects

Request (req) object

The req object contains the request specifications. It has different attributes:

It has the following functions attached to it:

Response (res) object

The res object contains the response specifications. It has different attributes:

It also has a set of functions:

Error (err) object

The 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.

Refer to the “Error handling” paragraph for more details.

URL dispatching

The path parameter in the get, post, … functions are processed as being regex patterns.

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 ^/$.

Starting the Jug instance

Simple call serve_it() at the end of your piping chain (see Hello World! example).

Further examples

Minimal CRUD TODO app