2

We are using RPlumber to host an API, and our developers asked that the API endpoints provide data in a CSV format, rather than JSON. To handle this, we have the following:

r_endpoints.R

#* @get /test-endpoint-1
testEndpoint <- function(res) {
  mydata <- data.frame(a = c(1,2,3), b = c(3,4,5))
  print('mydata')
  print(mydata)
  con <- textConnection("val","w")
  print(paste0('con: ', con))
  write.csv(x = mydata, con, row.names = FALSE)
  close(con)

  print('res and res.body')
  print(res);  
  res$body <- paste(val, collapse="\n")
  print(res$body)
  return(res)
}

#* @get /test-endpoint-2
testEndpoint2 <- function() {
  mydata <- data.frame(a = c(1,2,3), b = c(3,4,5))
  return(mydata)
}

run_api.r

library(plumber)
pr <- plumber::plumb("r_endpoints.R")
pr$run(host = "0.0.0.0", port = 8004)

test-endpoint-2 returns the data in a JSON format, whereas test-endpoint-1 returns the data in a CSV format. When these endpoints are run locally on my mac, and when I hit the endpoints, I receive the following correct output:

enter image description here

To host the API, we've installed R + the libraries + pm2 on a Linode Ubuntu 16.04 server, and installed all (I think all) of the dependencies. When we try to hit the endpoints as hosted on the server, we receive:

enter image description here

Here are the print statements that I've added to test-endpoint-1 to help with debugging:

[1] "mydata"
  a b
1 1 3
2 2 4
3 3 5
[1] "con: 3"
[1] "res and res.body"
<PlumberResponse>
  Public:
    body: NULL
    clone: function (deep = FALSE) 
    headers: list
    initialize: function (serializer = serializer_json()) 
    removeCookie: function (name, path, http = FALSE, secure = FALSE, same_site = FALSE, 
    serializer: function (val, req, res, errorHandler) 
    setCookie: function (name, value, path, expiration = FALSE, http = FALSE, 
    setHeader: function (name, value) 
    status: 200
    toResponse: function () 
[1] "\"a\",\"b\"\n1,3\n2,4\n3,5"

These are the correct print statements - the same that we get locally. For some reason, the server will not allow us to return in a CSV format in the same way that my local machine allows, and I have no idea why this is the case, or how to fix it.

Edit

After updating the plumber library on my local machine, I now receive the error An exception occurred. on my local machine as well. It seems, in the newer version of plumber, that the snippet of code I use to convert the API endpoint output to a CSV file:

  ...
  con <- textConnection("val","w")
  write.csv(x = mydata, con, row.names = FALSE)
  close(con)

  res$body <- paste(val, collapse="\n")
  return(res)

no longer works.

Edit 2

Here's my own stackoverflow post from nearly 3 years ago on how to return the data as a CSV... seems to no longer work.

Edit 3

Using @serialize csv does "work", but when I hit the endpoint, the data is downloaded as a CSV onto my local machine, whereas it would be better for the data to simply be returned in a CSV format from the API, but not automatically downloaded into a CSV file...

halfer
  • 19,824
  • 17
  • 99
  • 186
Canovice
  • 9,012
  • 22
  • 93
  • 211
  • I'm looking into adding `#* @serializer csv` above the endpoints that should return CSV, but I am then receiving the error `Error in parseBlock(lineNum, file) : No such @serializer registered: csv` – Canovice Oct 15 '20 at 17:22
  • 1
    What are the response headers? You can inspect them in the browser. – Bruno Tremblay Oct 15 '20 at 18:26
  • 1
    This is more of a machine configuration, plumber is returning with the correct headers for csv. If you want it to open up in the browser, you either change the response headers or change how your system treat csv. – Bruno Tremblay Oct 15 '20 at 18:34

3 Answers3

3

Maybe look into this for inspiration, here I'm modifying responses content-type headers to text/plain. text/plain should display in the browser I believe.

#* @get /json
#* @serializer unboxedJSON
function() {
  dostuff()
}

#* @get /csv
#* @serializer csv list(type="text/plain; charset=UTF-8")
function() {
  dostuff()
}


dostuff <- function() {
  mtcars
}
Bruno Tremblay
  • 756
  • 4
  • 9
  • This is great, thanks. So the only change you made was adding `list(type="text/plain; charset=UTF-8")` to the serializer.. I wonder what the default was? – Canovice Oct 15 '20 at 18:42
  • 1
    you don't need to convert to csv anymore, the serializer will do that for you. Just return a data.frame. – Bruno Tremblay Oct 15 '20 at 18:44
  • Do you know if it is possible to "toggle" between CSV and JSON using the serializers? it would be great if these functions could take a `type` parameter that can switch between CSV and JSON output – Canovice Oct 15 '20 at 19:11
2

This ugly code works

EDIT : added an enum spec for swagger UI

library(plumber)

#* @get /iris
function(type, res) {
  if (type == "csv") {
    res$serializer <- serializer_csv(type = "text/plain; charset=UTF-8")
  }
  iris
}

#* @plumber
function(pr) {
  pr_set_api_spec(pr, function(spec) {
    spec$paths$`/iris`$get$parameters[[1]]$schema$enum = c("json", "csv")
    spec
  })
}
Bruno Tremblay
  • 756
  • 4
  • 9
0

The An exception occurred issue is actually from httpuv and is fixed in the latest GitHub version of the package (see https://github.com/rstudio/httpuv/pull/289). Installing httpuv from GitHub (remotes::install_github("rstudio/httpuv")) and running the API again should resolve the issue.

blairj09
  • 11
  • 1
  • 1