33

I have a shiny app that takes a JSON input file, runs it through a classifier and returns a classified JSON object. I want the app to be able to communicate with an API. I want the API to post a file to the Shiny App which will do its work and return a classified object. Basically I want the Shiny app to sit in the background until a file is posted and then do its work. I know that I can use GET from the httr package to get a file from a url. I can put this in the shiny.server file which is fine if I know the file name for the get command

However the filenames coming from the API will be different. So is there any way that I can make this dynamic according to the Post request that comes from the API.

ismirsehregal
  • 30,045
  • 5
  • 31
  • 78
user2987739
  • 713
  • 2
  • 7
  • 9
  • Just to clarify, are you trying to implement a RESTful API with Shiny: the client (say, your browser) send the input file in JSON format to Shiny, and let Shiny return the classified object to the client? – Xin Yin Aug 13 '14 at 13:08
  • Hi, Thats exactly what I'm trying to do – user2987739 Aug 13 '14 at 13:13
  • 4
    There are at least two ways you can do this. If you would prefer to implement it via an AJAX request, you can look at [this example here](http://shiny.rstudio.com/gallery/selectize-rendering-methods.html). Alternatively you can use custom [input binding](http://shiny.rstudio.com/articles/building-inputs.html) and [output binding](http://shiny.rstudio.com/articles/building-outputs.html). – Xin Yin Aug 13 '14 at 13:16
  • Thanks I'll check these out now – user2987739 Aug 13 '14 at 13:22
  • 2
    The AJAX example I gave you is not very straightforward. For example it didn't demonstrate how you can construct an AJAX request in javascript. Instead it directly binds the AJAX URL to a selectize.js control. A more explicit example of using AJAX requests in Shiny can be found in [this answer](http://stackoverflow.com/questions/23635552/shiny-datatable-with-child-rows-using-ajax/25165727#25165727) although there's room for some tiny improvement. – Xin Yin Aug 13 '14 at 13:26
  • Hi Xin, thanks for your help. In the end I found it easier to wrap my Shiny app in a function ran it through command-line from my API – user2987739 Aug 19 '14 at 09:05
  • 1
    Just here to link to a related question: [Accept HTTP Request in R shiny application](http://stackoverflow.com/q/25297489/320399). – blong May 27 '16 at 03:55

2 Answers2

11

If you do not have to use Shiny, you can use openCPU. OpenCPU provides each of your R packages as REST service automatically. I work with OpenCPU and it works fine! It is the easiest way to use R from another program.

Guybrush
  • 710
  • 7
  • 12
5

By now library(plumber) needs to be mentioned as an alternative in this context, however the following example is showing how to handle POST requests directly in shiny.

It is based on Joe Cheng's gist here, which suggests to add an attribute "http_methods_supported" to the UI and use httpResponse to answer the requests.

The below code starts a shiny app in a background R process (This is done only to have a single single file MRE - of course, you can put the app in a separate file and remove the r_bg-line). After the app is launched the parent process sends the iris data.frame to the UI.

In the UI function the req$PATH_INFO is checked (see uiPattern = ".*"), then the numerical columns are multiplied by 10 (query_params$factor) and send back as a json string.

library(shiny)
library(jsonlite)
library(callr)
library(datasets)

ui <- function(req) {
  # The `req` object is a Rook environment
  # See https://github.com/jeffreyhorner/Rook#the-environment
  if (identical(req$REQUEST_METHOD, "GET")) {
    fluidPage(
      h1("Accepting POST requests from Shiny")
    )
  } else if (identical(req$REQUEST_METHOD, "POST")) {
    # Handle the POST
    query_params <- parseQueryString(req$QUERY_STRING)
    body_bytes <- req$rook.input$read(-1)
    if(req$PATH_INFO == "/iris"){
      postedIris <- jsonlite::fromJSON(rawToChar(body_bytes))
      modifiedIris <- postedIris[sapply(iris, class) == "numeric"]*as.numeric(query_params$factor)
      httpResponse(
        status = 200L,
        content_type = "application/json",
        content = jsonlite::toJSON(modifiedIris, dataframe = "columns")
      )
    } else {
      httpResponse(
        status = 200L,
        content_type = "application/json",
        content = '{"status": "ok"}'
      )
    }
  }
}
attr(ui, "http_methods_supported") <- c("GET", "POST")

server <- function(input, output, session) {}

app <- shinyApp(ui, server, uiPattern = ".*")
# shiny::runApp(app, port = 80, launch.browser = FALSE, host = "0.0.0.0")
shiny_process <- r_bg(function(x){ shiny::runApp(x, port = 80, launch.browser = FALSE, host = "0.0.0.0") }, args = list(x = app))

library(httr)
r <- POST(url = "http://127.0.0.1/iris?factor=10", body = iris, encode = "json", verbose())
recievedIris <- as.data.frame(fromJSON(rawToChar(r$content)))
print(recievedIris)
shiny_process$kill()

Please also check this related PR which is providing further examples (also showing how to use session$registerDataObj) and is aiming at a better description of the httpResponse function.

ismirsehregal
  • 30,045
  • 5
  • 31
  • 78