Web-frameworks: [R] Add plumber

Created on 9 Sep 2020  路  5Comments  路  Source: the-benchmarker/web-frameworks

plumber is the most popular API development tool in R. RestRserve has already been added in #3255.

This project compare performance based on 3 routes :

  • GET /, SHOULD return a successful status code and an empty string
  • GET /user/:id, SHOULD return a successful status code and the id
  • POST /user, SHOULD return a successful status code and an empty string

Gently ping authors: @trestletech, @schloerke, @cpsievert, @meztez

welcome r

Most helpful comment

@t-wojciech Thinking on this some more, I want to be sure we are actually testing plumber and not the filters or serializers.

I've added a test file below to compare how fast the core of plumber is, with and without filters and serializers. Routes that end in default test using filters and a serializer. The testing routes test the core plumber response time.

# plumber.R

# GET /, SHOULD return a successful status code and an empty string
# GET /user/:id, SHOULD return a successful status code and the id
# POST /user, SHOULD return a successful status code and an empty string

named_empty_headers <- setNames(list(), character(0))
#* @preempt __first__
#* @get /
function(res) {
  # https://github.com/rstudio/httpuv/issues/195
  res$headers <- named_empty_headers
  res
}

#* @serializer contentType list(type = "text/plain")
#* @get /default
function() {
  ""
}


#* @preempt __first__
#* @get /user/<id>
function(res, id) {
  # https://github.com/rstudio/httpuv/issues/195
  res$headers <- named_empty_headers
  res$body <- id
  res
}

#* @serializer contentType list(type = "text/plain")
#* @get /user/<id>/default
function(id) {
  id
}


#* @preempt __first__
#* @post /user
function(res) {
  # https://github.com/rstudio/httpuv/issues/195
  res$headers <- named_empty_headers
  res
}

#* @serializer contentType list(type = "text/plain")
#* @post /user/default
function() {
  ""
}

Show that there is a difference in the routes...

library(plumber)
# bring in `make_req()`
source("https://raw.githubusercontent.com/rstudio/plumber/4714945222c8ef5198f8f7a51ba65163ba8611e5/tests/testthat/helper-mock-request.R")

# parse plumber file
pr <- plumb("./plumber.R")

# test
microbenchmark::microbenchmark(
  "/" = pr$call(make_req("GET", "/")),
  "/default" = pr$call(make_req("GET", "/default")),

  "/user/123" = pr$call(make_req("GET", "/user/123")),
  "/user/123/default" = pr$call(make_req("GET", "/user/123/default")),

  "/user" = pr$call(make_req("POST", "/user")),
  "/user/default" = pr$call(make_req("POST", "/user/default"))
)
#> Unit: microseconds
#>               expr      min        lq      mean   median        uq      max neval cld
#>                  /  402.207  497.5255  569.6792  540.931  621.0600  925.413   100 a  
#>           /default 1030.028 1247.8415 1426.8818 1311.467 1501.7705 7194.458   100  bc
#>          /user/123  466.439  566.5340  634.3087  592.425  650.7055 1147.150   100 a  
#>  /user/123/default 1139.246 1290.8250 1488.4355 1352.736 1545.6525 7111.835   100   c
#>              /user  398.733  506.4655  578.7242  547.862  602.8700 1109.749   100 a  
#>      /user/default 1112.267 1162.3905 1310.6001 1244.106 1385.1605 2174.182   100  b 

...at least 50% faster if filters and serializers are not used.

I'll make a PR soon copying the style of https://github.com/the-benchmarker/web-frameworks/pull/3255

All 5 comments

Thanks @t-wojciech.

Waiting for explicit consent, or no opposition from any author 馃槢 to start working on it

@waghanza 馃憤


The dev version supports async. If installing from master (or >= v1.0.0), don't know if adding async support as another test would be interesting to compare

Something like:

#* @get /user/<id>
function(id) {
  promises::promise(~ {later::later(~ resolve(id), 0) })
}

Edit: 9/15 @t-wojciech

Simpler code: (No need to get later involved)

#* @get /user/<id>
function(id) {
  promises::promise_resolve(id)
}

plumber 1.0.0 in on CRAN now.

I was about to add this too... as RestRServe has already been added. I would definitely like to see the difference. But the benchmarks haven't been updated for over a month.

@t-wojciech Thinking on this some more, I want to be sure we are actually testing plumber and not the filters or serializers.

I've added a test file below to compare how fast the core of plumber is, with and without filters and serializers. Routes that end in default test using filters and a serializer. The testing routes test the core plumber response time.

# plumber.R

# GET /, SHOULD return a successful status code and an empty string
# GET /user/:id, SHOULD return a successful status code and the id
# POST /user, SHOULD return a successful status code and an empty string

named_empty_headers <- setNames(list(), character(0))
#* @preempt __first__
#* @get /
function(res) {
  # https://github.com/rstudio/httpuv/issues/195
  res$headers <- named_empty_headers
  res
}

#* @serializer contentType list(type = "text/plain")
#* @get /default
function() {
  ""
}


#* @preempt __first__
#* @get /user/<id>
function(res, id) {
  # https://github.com/rstudio/httpuv/issues/195
  res$headers <- named_empty_headers
  res$body <- id
  res
}

#* @serializer contentType list(type = "text/plain")
#* @get /user/<id>/default
function(id) {
  id
}


#* @preempt __first__
#* @post /user
function(res) {
  # https://github.com/rstudio/httpuv/issues/195
  res$headers <- named_empty_headers
  res
}

#* @serializer contentType list(type = "text/plain")
#* @post /user/default
function() {
  ""
}

Show that there is a difference in the routes...

library(plumber)
# bring in `make_req()`
source("https://raw.githubusercontent.com/rstudio/plumber/4714945222c8ef5198f8f7a51ba65163ba8611e5/tests/testthat/helper-mock-request.R")

# parse plumber file
pr <- plumb("./plumber.R")

# test
microbenchmark::microbenchmark(
  "/" = pr$call(make_req("GET", "/")),
  "/default" = pr$call(make_req("GET", "/default")),

  "/user/123" = pr$call(make_req("GET", "/user/123")),
  "/user/123/default" = pr$call(make_req("GET", "/user/123/default")),

  "/user" = pr$call(make_req("POST", "/user")),
  "/user/default" = pr$call(make_req("POST", "/user/default"))
)
#> Unit: microseconds
#>               expr      min        lq      mean   median        uq      max neval cld
#>                  /  402.207  497.5255  569.6792  540.931  621.0600  925.413   100 a  
#>           /default 1030.028 1247.8415 1426.8818 1311.467 1501.7705 7194.458   100  bc
#>          /user/123  466.439  566.5340  634.3087  592.425  650.7055 1147.150   100 a  
#>  /user/123/default 1139.246 1290.8250 1488.4355 1352.736 1545.6525 7111.835   100   c
#>              /user  398.733  506.4655  578.7242  547.862  602.8700 1109.749   100 a  
#>      /user/default 1112.267 1162.3905 1310.6001 1244.106 1385.1605 2174.182   100  b 

...at least 50% faster if filters and serializers are not used.

I'll make a PR soon copying the style of https://github.com/the-benchmarker/web-frameworks/pull/3255

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ohenepee picture ohenepee  路  12Comments

ansarizafar picture ansarizafar  路  11Comments

javidoweb picture javidoweb  路  7Comments

proyb6 picture proyb6  路  11Comments

sakthisaran65 picture sakthisaran65  路  5Comments