Code Monkey home page Code Monkey logo

plumber's Introduction

plumber

R build status CRAN RStudio mirror downloads codecov RStudio community

Plumber allows you to create a web API by merely decorating your existing R source code with roxygen2-like comments. Take a look at an example.

# plumber.R

#* Echo back the input
#* @param msg The message to echo
#* @get /echo
function(msg="") {
  list(msg = paste0("The message is: '", msg, "'"))
}

#* Plot a histogram
#* @serializer png
#* @get /plot
function() {
  rand <- rnorm(100)
  hist(rand)
}

#* Return the sum of two numbers
#* @param a The first number to add
#* @param b The second number to add
#* @post /sum
function(a, b) {
  as.numeric(a) + as.numeric(b)
}

These comments allow plumber to make your R functions available as API endpoints. You can use either #* as the prefix or #', but we recommend the former since #' will collide with roxygen2.

library(plumber)
# 'plumber.R' is the location of the file shown above
pr("plumber.R") %>%
  pr_run(port=8000)

You can visit this URL using a browser or a terminal to run your R function and get the results. For instance http://localhost:8000/plot will show you a histogram, and http://localhost:8000/echo?msg=hello will echo back the 'hello' message you provided.

Here we're using curl via a Mac/Linux terminal.

$ curl "http://localhost:8000/echo"
 {"msg":["The message is: ''"]}
$ curl "http://localhost:8000/echo?msg=hello"
 {"msg":["The message is: 'hello'"]}

As you might have guessed, the request's query string parameters are forwarded to the R function as arguments (as character strings).

$ curl --data "a=4&b=3" "http://localhost:8000/sum"
 [7]

You can also send your data as JSON:

$ curl -H "Content-Type: application/json" --data '{"a":4, "b":5}' http://localhost:8000/sum
 [9]

Installation

You can install the latest stable version from CRAN using the following command:

install.packages("plumber")

If you want to try out the latest development version, you can install it from GitHub.

remotes::install_github("rstudio/plumber")
library(plumber)

Cheat Sheet

Hosting

If you're just getting started with hosting cloud servers, the DigitalOcean integration included in plumber will be the best way to get started. You'll be able to get a server hosting your custom API in just two R commands. To deploy to DigitalOcean, check out the plumber companion package plumberDeploy.

Posit Connect is a commercial publishing platform that enables R developers to easily publish a variety of R content types, including Plumber APIs. Additional documentation is available at https://www.rplumber.io/articles/hosting.html#rstudio-connect-1.

A couple of other approaches to hosting plumber are also made available:

Related Projects

  • OpenCPU - A server designed for hosting R APIs with an eye towards scientific research.
  • jug - (development discontinued) an R package similar to Plumber but uses a more programmatic approach to constructing the API.

plumber's People

Contributors

antoine-sachet avatar blairj09 avatar eitsupi avatar fvd avatar howardbaek avatar jcheng5 avatar jdtrat avatar josiahparry avatar m-muecke avatar maxheld83 avatar meztez avatar mhairi avatar mtoto avatar muschellij2 avatar pachadotdev avatar randyzwitch avatar robertdj avatar s-fleck avatar sambaala avatar schloerke avatar scottmmjackson avatar shapenaji avatar shrektan avatar svdwoude avatar thiyagu-p avatar trestletech avatar tylergrantsmith avatar vfulco avatar vspinu avatar wkmor1 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

plumber's Issues

Return CSV

What is the best way to return CSV?

The JSON is great, but I'm running into issues where the bandwidth is an issue. Thoughts?

Thanks!

Enable CORS in Plumber

I am trying to make a POST request through my chrome browser, but its showing error of preflight request.
Please suggest a way to enable CORS at Plumber side.

Inconsistent plumbr vs plumber naming

Hey Jeff, just a heads up that it looks like you advertised on twitter a URL of http://plumbr.trestletech.com/ and in the tagline description of this repo you're also using the same url, but it's a 404 because of plumber vs plumbr.

I see there was a commit "Renamed to plumbr." so I suppose you intended to call it plumbr and ended up going with plumber? Anyway, just thought you should fix the URLs

Plumber just returning [true] for the initial example

Hi,

I am testing the suggested base example on RStudio in a a OSX Yosemite, but when accessing the API:
$ curl "http://localhost:8000/mean"

I keep getting the same response(via curl and browser):
[true]

Am I doing anything wrong?

Instructions for pm2 hosting setup are not working

I followed the directions on hosting plumber with pm2 here, however pm2 I get javascript errors as follows:

When R -e "library(plumber); r<-plumb('plumb-test.R');r\$run(port=8000)" was used I got

R -e "library(plumber);r <-plumb('plumb-test.R');r\$run(port=8000)"
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: Unexpected string
    at Object.exports.runInThisContext (vm.js:76:16)
    at Module._compile (module.js:542:28)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Function._load (/usr/lib/node_modules/pm2/node_modules/pmx/lib/transaction.js:62:21)
    at Object.<anonymous> (/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js:52:21)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)

When I saved the R code as a script and tried R -f plumb-start.R, I got:

ReferenceError: R is not defined
    at Object.<anonymous> (/usr/local/plumber/hello/plumb-hello:2:1)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Function._load (/usr/lib/node_modules/pm2/node_modules/pmx/lib/transaction.js:62:21)
    at Object.<anonymous> (/usr/lib/node_modules/pm2/lib/ProcessContainerFork.js:52:21)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)

Am I setting up something incorrectly? Or is this something that pm2 does not support? I'm using version 2.1.5 of pm2.

I have successfully been able to run the script as root... I'm not sure what I'm doing wrong.

Thanks.

Adding PDF support

Hi,

I try to send a pdf with my API in R and Plumber (Plumber is a very good idea :).

*pdf doesn't work.

I have to modify jsonSerializer in that way to have it working :

jsonSerializer` <- function(val, req, res, errorHandler){
tryCatch({

res$setHeader("Content-Type", "application/pdf")
res$body <- val

return(res$toResponse())

}, error=function(e){
errorHandler(req, res, e)
})
}

.globals$serializers[["json"]] <- `jsonSerializer

Do you have another (cleaner ;) way to do that ?
Thanks

let a few services continuously running (daemon) using plumber.

What would be the recommended way to create a daemon using plumber ?

## if plum is a script which contain all the commands
plum.R
## running from terminal using & should work
Rscript plum.R &

issues
Issue is what if I press this twice, and there are two conflicting scripts running on the same port.
What is someone else does the same.

Possible to check all plumber services running (given a port)?

Multiple cookie set incorrectly

Cookies are supposed to be one-per-line

Set-Cookie: cookie2=vale1; Expiration=...
Set-Cookie: cookie2=vale1; Expiration=...

Currently we're trying to set them all on one line which is invalid.

Not sure how this will fit into httpuv's notion that headers fit into a list.

Auto generate client

Given that we have the full API specification, we could auto generate client code/packages for R, JavaScript, etc.

Up-time and advice for running plumber on Windows

This is not an issue but a general question on the stability of this on a Windows Server.

I don't have any experience with this package, other than getting the examples to work, but it seems like an easy and good way to host an API (with low volume). My basic concern is that I have to restart the process all the time.

Does any one have experience with running plumber on a Windows (Virtual Server) and have some valuable advice to share? A Linux Server is not a possibility at my company.

Using a key

Is there a way to implement an api key (like for example something you build the package with)?

Extracting a table as result

Hi,

I have an R code that connects with an API, gets content in the form of a table, and I transform that data, and modify, create columns, add filters, bind it with other content, and another table is produced towards the end. I am hoping to convert my code into an API and go further from there. I tried the Get method, and I keep failing. Since I can not paste the whole code, I am going share a summary of that code. Please try and help me out.

segment<- NULL #this has to be a variable for the API Get/Post 

toCall<- APIfunction("api credentials", segment) # my api to retrieve data, which currently works

df<- as.data.frame.list(toCall)

#This followed by some lines to transform data, remove unwanted columns/rows etc
#input from the above dataframe goes into the check function below
CheckFunction<- function1

CorrectFunction<- function2 #takes input from above function

rbind.results
structure the output
print(head(output))

I tried to write a Get query and call the final output and I get a 404 error.
I know this is not much to go with, but my hands are tied. Please help! Thank you!

plumb() a package

Is there a way to plumb an entire package? Basically creating the api in the package rather than an ad-hoc script plumber <- plumb(pkgname)

ggplot2 gives error, is it supported?

I have the following post_serv.R file.

# post_serv.R

library(ggplot2)
library(magrittr)

df <- data.frame(x = rnorm(10, 0, 1), y = rnorm(10, 0, 1))

#' @png
#' @get /plot
get_plot <- function(){
  ggplot() + geom_point(data=df, aes(x,y), alpha = 0.5)
  # plot(df$x, df$y, main="Recent Values")
}

This then gets run from Rstudio using plumber::plumb('~/Desktop/post_serv.R')$run(port=8000)

Then when I access via the browser I get this error:

{"error":["500 - Internal server error"],"message":["cannot open the connection"]}

When I use the standard plot function, everything seems to work just fine. Is ggplot2 not supported?

Add arguments to res/req

Rather than the querystring parser(s) being special cases, make them filters that modify this special res/req$arguments object. Then use that object when injecting params into endpoints. Allows for custom additions to arguments to include, e.g. user, or session (which could also be attached to the req. But gives more options.

Error: object of type 'closure' is not subsettable

Thanks for this cool library - exactly what I've been looking for. However - I've got a bit of an issue, I get the following error message:

{"error":["500 - Internal server error"],"message":["object of type 'closure' is not subsettable"]}

Or more detailed:

simpleError in data$PriceEUR: object of type 'closure' is not subsettable

Here's my code:

#' @get /predict
doPredict <- function(from, to, price, ndtf, type = "response") {
  getTree <- function(from, to) {
    varname <- paste0(from, "_", to);
    return(eval(parse(text=varname)));
  }

    tree <- getTree(from, to);

    # Creates a data frame from the parameters
    PriceEUR <- c(as.integer(price));
    NumDaysToFlight <- c(as.integer(ndtf));
    s  <- data.frame(PriceEUR, NumDaysToFlight);

    # Return the predicted value
    value <- predict(tree,1,s,type = type)
    return(value);
}

When I run this code locally - it works. Return type of the prediction call is an integer.

However, when called from the webserver, I get the error mentioned above. Am I doing something wrong or did I hit an issue?

Use attributes instead of comments

I suspect the implementation would be much simpler if instead of:

#' @get /mean
normalMean <- function(samples=10){
  data <- rnorm(samples)
  mean(data)
}

you did

normalMean <- function(samples=10){
  data <- rnorm(samples)
  mean(data)
}
expose(normalMean) <- "GET"

where you have

`expose<-` <- function(x, value) { 
  class(x) <- "api"
  attr(x, "method") <- "GET"
  x
}

or similar

Timeout with a long request

Hi !

We are generating a long report in PDF that takes around 3 minutes and we expose it via Plumber.
The HTTP connection close around 1 minutes 40 seconds (100 seconds).

So we have the logical error message because plumber try to send data to a closed connection:
ERROR: [on_request_read] connection reset by peer

I think it can be related to #12 and by the way rstudio/httpuv#49

There is a way to fix that in Plumber? May be by sending manually in Plumber something like that:

Keep-Alive: timeout=300
Connection: Keep-Alive

But in any case we want to avoid #12

Best regards,
Emmanuel

Allowing 'source' to structure code

When a plumber server offers lots of functionality, the plumbed file is bound to get very large. It would be very helpful in terms of readability and maintainability if the code could be spread among several files, which are then just sourced into one big file.

data.table syntax

If data.table syntax is used in a file, plumb("myfile.R") will fail.

here is an example "myfile.R"

library(data.table)
x <- as.data.table(BOD)
x[, demand]

#* @get /test
function() x

Now, when I try to plumb it I get an error

> p <- plumb("myfile.R")
Error in `[.data.frame`(x, i, j) : object 'demand' not found

\" in JSON string - how to remove?

Hi, I'm trying to return JSON-LD from a GET.

No matter what gsub substitution I try , I can't seem to remove the slash quotes from the output.

so the output looks like the below:

["{ "@graph" : [ { "@id" : "http://example.com/dept/10", "http://example.com/dept/deptno" : { "@type" : "http://www.w3.org/2001/XMLSchema#integer", "@value" : "10" }, "http://example.com/dept/location" : "NEW YORK" }, { "@id" : "http://example.com/emp/7369", "http://example.com/emp/department" : { "@id" : "http://example.com/dept/10" }, "http://example.com/emp/name" : "SMITH", "http://example.com/emp/role" : { "@id" : "http://example.com/emp/general-office" } }, { "@id" : "http://example.com/emp/7370", "http://example.com/emp/department" : { "@id" : "http://example.com/dept/10" }, "http://example.com/emp/name" : "DOE", "http://example.com/emp/role" : { "@id" : "http://example.com/emp/engineering" } } ]}"]

Is there a way to format the string similar as the cat() function does where slashes from a character string are removed from the output?

Weird amount of slowness?

So I've been working on a project at https://github.com/Ironholds/sharder (it should be self-contained and locally runnable). You launch it and then run:

readLines("http://localhost:8000/mirror?https=0")

It's exceedingly slow to produce a response, so I filled it with little debugging flags (producing periods, printing timestamps, all the rest) and...none of them are slow. They churn through incredibly quickly. It does everything in main() in a fraction of a second. But for some reason it's incredibly slow to actually return and send out a response - as proof, I typed out this entire comment in less time than it took to run.

Any ideas?

https

Do you expect that this would work in the same way over https?

json example in README isn't working

Starting server to listen on port 8000

<simpleError in (function (a, b) {    as.numeric(a) + as.numeric(b)})(): argument "a" is missing, with no default>

Warning in li["message"] <- err :
  number of items to replace is not a multiple of replacement length

I believe the CRAN version is outdated.

Support for 'batch' mode, i.e. multiple lines of input data

I'm looking for a way to be able to pass in multiple lines of data in the form of

'curl --data "a=1&b=2\na=3&b=4\n..."

My specific use case would benefit a lot from being able to read in vectors of data in this way and then converting to a data frame within the function I am exposing as an API. To simplify, you can think of me simply passing in the contents of a .csv file.

You can see my attempt at reverse engineering your package here: https://github.com/FrankPortman/plumber.

I went into query-string.R and created a slightly modified parseQS function called parseQS2. I'm trying to mimic the exact format that parseQS returns, but in my case it's returning vectors of length > 1 in each sublist, as opposed to just 1 like you have. I then change the function in queryStringFilter from using parseQS to using parseQS2. However, it seems like when I pass in multiple lines of data, it is correctly doing the parseQS2 step (at least it looks that way from the traceback) but somewhere along the way some variables get dropped.

#* @post /test
function(a, b) {

  return(list(sum(as.numeric(a) + as.numeric(b)),
  as.numeric(a),
  as.numeric(b)))

}
curl --data "a=2&b=15 \n a=1&b=4 \n a=1&b=2" "http://localhost:1234/test"
[[4],[2],[2]]

It looks like only the first a and the last b is being evaluated.

curl --data "a=2&b=15 \n a=1&b=4" "http://localhost:1234/test"
[[6],[2],[4]

Before I dive deeper into this myself, are there any functions of specific pieces of code I would have to look into modifying in order to do something like this?

P.S. - I thought about approaching this problem from the other side (taking in only one line of named arguments but somehow encapsulating all the 'rows' I want to pass) but this approach seems cleaner if I can get it to work. Not sure if this is the best way of approaching my use case but if there is any interest from you/others on this functionality I'd be more than happy to discuss with you how this can potentially fit in with the package's architecture and help write tests for it so it can be merged eventually. For now, I'm just treating it as a one-off case for myself and am not too concerned with error handling so feel free to suggest something hacky.

Request of a -generated- list of Endpoints via the API

Hi,

This is a fantastic package. I'm using it for some dashboarding functionality, and what I'd like to offer the users is some documentation in the form of a list of Endpoint, i.e. a programmatic way to tell the users what functionality is present on the server currently running.

I can do this via a manually constructed list, which I then will have to maintain, and which may contain errors. So, how do I programmatically make a list of available Endpoints, the result of which I can serve to users?

Take care,
JFJanssen

Support sub-routers

Plumber routers should be able to stack on one another by giving a prefix.

e.g.

prAcct <- plumb("accounts.R")
prSales <- plumb("sales.R")
pr$addRouter(prAcct, "/account")
pr$addRouter(prSales, "/sales")

Single process per R script/endpoint?

I have installed the package and am using the pm2 process manager and as far as I can tell it's just running a single R process no matter how many GET requests I make. Is there any way of spinning up a new R process for each new request?

Thanks!

Should the order in a .R file be preserved with regard to endpoints/filters

Currently, all filters would be evaluated before any endpoints unless an endpoint explicitly overrides that behavior. In things like Express/Restify, that's not true. You add filters and endpoints and the order is preserved. That seems like it might be more intuitive than the current behavior, but then introspection later is more confusing.

jsonlite auto_unbox

I am trying to send back a JSON response like this:

{"status_code":101,"status_message":"Invalid object"}

Here is my code:

return (list(status_code=101, status_message="Invalid object"))

The response I get is:

{"status_code":[101],"status_message":["Invalid object"]}

I'd like to NOT have the brackets (array).

It seems like this is in the call to jsonlite::toJSON

library(jsonlite)
ret <- list(status_code=101, status_message="Invalid object"))
toJSON(ret)
{"status_code":[101],"status_message":["Invalid object"]} 
toJSON(ret, auto_unbox = TRUE)
{"status_code":101,"status_message":"Invalid object"} 

I tried defining a new serializer, but that seems like a hack.

Is there a way around this?

Thanks!

Support parsing JSON on POST

As far as I can tell, the only way to POST values to a plumber API is using & separated strings. ie:

 curl -i -X POST http://plumber.tres.tl/append/append -d "val=50" 

and there is no support yet for JSON formatted data, ie.:

curl -H "Content-Type: application/json" -X POST  http://plumber.tres.tl/append/append -d '{"val":50}'

The function postBodyFilter filters using parseQS that splits on & and = and does not yet check for JSON formats (perhaps based on an initial { and ending }.

XML Output instead of JSON

Hi,

I have formatted the output in XML and like to return the output as XML instead of JSON.

plumber now defaults to JSON. Is there any way to do that?

error when query string is too large

I'm developing an API that receives a JSON file, runs an algorithm and returns a JSON file as an output. Here is a small app that shows the problem I'm having.

myfile.R

#* @post /test2
test2 <- function(param){
list(param)
}
R -e "library(plumber); pr <- plumb('myfile.R'); pr\$run(port=1000)"

The point is that by changing the size of the query string, it gets a point where the request stops working. Here is what I'm getting as a result

A Query with 1000 characters

library(httr)

body = list(param = paste0(rep(0:9, 100), collapse=''))

r = POST(url, query = body, verbose())
content(r)

# > content(r)
# [[1]]
# [[1]][[1]]
# [1] "012345678901234567890123456789012345678901234567890123

A Query with 1000 characters doesn't work. The problem, that makes it hard to debug is It does work if I run the request locally.

body = list(param = paste0(rep(0:9, 500), collapse=''))

r = POST(url, query = body, verbose())
content(r)

# $error
# $error[[1]]
# [1] "404 - Resource Not Found"

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.