rstudio / plumber Goto Github PK
View Code? Open in Web Editor NEWTurn your R code into a web API.
Home Page: https://www.rplumber.io
License: Other
Turn your R code into a web API.
Home Page: https://www.rplumber.io
License: Other
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?
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 }
.
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 the case of the coming-soon filters-example.R
, sending the request: http://localhost:8000/about?username generates a 500 with "subscript out of bounds."
Given that we have the full API specification, we could auto generate client code/packages for R, JavaScript, etc.
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)
Allows filters/endpoints to override the serializer.
Update CRAN with the latest version
I think it's a bug in httpuv. Come up with a workaround and report it if so.
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
.
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.
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 source
d into one big file.
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.
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.
#* @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"
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.
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.
Just wondering, as I'm trying to come up with a (relatively) secure authentication scheme.
Add include_html
, include_md
, include_rmd
, etc. to bring in files of other types to serve via HTML serializer.
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")
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
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?
At least for the major ones, let someone specify an endpoint's serializer via @json
.
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
Not html/plain text. This makes talking to the API simpler
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
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!
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?
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.
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!
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.
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?
Do you expect that this would work in the same way over https?
Support something like @get /cars/:id
where id
because a parameter passed in to the function.
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
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?
I'd love the ability to define a root path for the API, and have all plumbed endpoints be relative to that root path.
After installing rapier
via devtools
it failed after a first run claiming that the httpuv
dependency wasn't installed. No more complaints after installing it manually. Maybe check that httpuv
is installed when installing rapier
?
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!
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.
Is there a way to implement an api key (like for example something you build the package with)?
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?
I think just a null serializer? Generate HTML from an endpoint...
Hi,
I try to send a pdf with my API in R and Plumber (Plumber is a very good idea :).
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
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)?
thanks
a la http://flask.pocoo.org/docs/0.10/quickstart/#variable-rules
We'd need to move away from :varname
syntax towards a Flask-like <varname:int>
style, but I think that would be worth it for numeric typing.
Types to support
int
-> as.integer
double
/numeric
-> as.numeric
bool
-> custom formatter that does intelligent things with "0", "1", "f", "true", etc.Is it possible to access attributes such as the user's IP address or user agent in plumber code?
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!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.