Ï

plumber

Turn R code into web APIs

Jeff Allen / @trestleJeff
http://plumber.trestletech.com

You've got a website...

You've got R code...

buildModel <- function(myData){
  mcmcTheKmeansLogarithm(myData)
}
forecastSales <- function(date){
  linearModel(sales, date)
}

You want to combine them...

Plumber can help!

  • Convert existing R code to web APIs
  • Just add comments to your existing code
  • Leverage R from existing websites and APIs

Outline

  • Decorating
  • Running
  • Routing
  • Filtering

Outline

  • Decorating
  • Running
  • Routing
  • Filtering

Decorating



makePlot <- function(){
  par(mar = rep(2, 4)) # margins
  dates <- seq(as.Date("2015-10-01"),
      as.Date("2015-10-31"), by=1)
  sold <- 1:31

  plot(dates, sold, type="b", main="Sales")
}
          

Decorating

#* @get /salesgraph
#* @png
makePlot <- function(){
  par(mar = rep(2, 4)) # margins
  dates <- seq(as.Date("2015-10-01"),
      as.Date("2015-10-31"), by=1)
  sold <- 1:31

  plot(dates, sold, type="b", main="Sales")
}
          

Decorating

sales <- NULL
#* @post /addsale
function(qty, item, id){
  sales <<- rbind(sales, data.frame(
    id = id,
    time = Sys.time(),
    item = item,
    qty = qty
  ))
  id
}

Automatic Parsing

GET plumber.trestletech.com/sale?qty=2
#* @get /sale
function(qty){
  qty == "2" # TRUE
}

POST Forms too!

POST plumber.trestletech.com/ HTTP/1.1

qty=3
#* @post /
function(qty){
  qty == "3" # TRUE
}

You always get req and res

#* @get /
function(req, res){
  ip <- req$REMOTE_ADDR
  res$setHeader("Last-Modified",
        "Fri, 16 Oct 2015 12:45:26 GMT")
  # ...
}

Outline

  • Decorating
  • Running
  • Routing
  • Filtering

Running

library(plumber)

pr <- plumber::plumb("myfile.R")
pr$run(port=8000)

Demo

  • Decorating
  • Running
  • Routing
  • Filtering

Static Routes

#* @post /transaction
function(item, qty, id){
  sales <<- rbind(sales, list(time=Sys.time(),
                   item=item, qty=qty, id=id))
}
POST myplumber.com/transaction HTTP/1.1

item=black-hat&qty=3&id=429

Variable Routes

#* @get /transaction/<id>
function(id){
  id <- as.integer(id)
  sales[sales$id == id, ]
}
GET myplumber.com/transaction/429 HTTP/1.1

[{"id":429,"time":"2015-10-16 20:52:06",
    "item":"blue-hat","qty":"3"}]

Typed Variable Routes

#* @get /transaction/<id:int>
function(id){
  sales[sales$id == id, ]
}
GET myplumber.com/transaction/429 HTTP/1.1

[{"id":429,"time":"2015-10-16 20:52:06",
    "item":"blue-hat","qty":"3"}]

Complex Routes

#* @get /hats/<color>/size/<size:int>
function(color, size){
  hats[hats$color == color
        & hats$size == size]
}

Outline

  • Decorating
  • Running
  • Routing
  • Filtering

Endpoints

[Always serve things]
#* @get /transaction/<id>
function(id){
  id <- as.integer(id)
  sales[sales$id == id, ]
}

Special Endpoints

[ Serve Images ]
#* @get /transaction/plot
#* @png
function(id){
  plot(sales$date, sales$qty,
    main="Qty/Purchase Over Time",
    xlab="Date", ylab="Qty")
}
Returns a PNG image of your graph

Filters

[ 1. Passively do things ]
#* @filter logger
function(req){
  print(paste0(date(), " - ",
          req$REMOTE_ADDR, " - ",
          req$REQUEST_METHOD, " ",
          req$PATH_INFO))
  forward()
}

Filters

[ 2. Change things on the way by ]
#* @filter setuser
function(req){
  un <- req$HTTP_COOKIE
  # Make req$username available to endpoints
  req$username <- un

  forward()
}

Filters

[ 3. Can serve a response ]
#* @filter nochrome
function(req){
    if (!grepl("Chrome", req$HTTP_USER_AGENT)){
      forward()
    } else {
      res$status <- 400
      res$body <- "NOT WELCOME HERE!"
    }
}

Extras

  • Debug your web app on a REPL!
  • Static asset server
  • Helpers to serve HTML, Markdown, and RMD
  • Composable API, if annotations aren't your thing

Go Forward and make your dreams come true!

  • Website integration
  • Internal analytics APIs
  • Webhooks (GitHub, Google Drive, Slack, ...)
  • Include a web-accessible demo in your packages
  • Complete website hosted in R