31

I have a shiny app that takes an input file in Json 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
  • 21,473
  • 3
  • 20
  • 58
user2987739
  • 683
  • 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

10

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
  • 682
  • 7
  • 12
2

This 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. 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) 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
  • 21,473
  • 3
  • 20
  • 58