25

I have a shiny app that I have made that needs to get its data from another server, i.e. the other server when the shiny app is opened sends a request to the shiny app to open the app and feed it the data that it needs.

To simulate this I can send the following to the R shiny app when I open the app in firefox:

 http://localhost:3838/benchmark-module/?transformerData=data/TransformerDataSampleForShiny.json

This is a simple get request that sends the sting called : "Transformer Data" with contents "data/TransformerDataSampleForShing.json" to the shiny app.

When I use the code it works fine:

#(Abridged code, I am only showing the start of the code)
 shinyServer(function(input, output) {
 jsonFile <- "data/TransformerDataSampleForShiny.json"
 JSONdata <- fromJSON(jsonFile)

but when I want to do the exact same thing except rather than hard coding the string "data/TransformerDataSampleForShiny.json" i want to receive that string from the http request above. How do I do this?? I have tried the code:

shinyServer(function(input, output) {
jsonFile <- input$transformerData
JSONdata <- fromJSON(jsonFile)

and I have also tried:

....
jsonFile <- input$TransformerData

but none of these have worked.

SO the main question is, is how do I code to recieve HTTP requests? I would like to receive strings from HTTP GET requests and or JSON files from POST requests.

Just to clarify I DONT want to send post or get requests from R. I want to receive them. I can't use the httr package or the httpRequest package for receiving

Thanks so much!

James Willcox
  • 631
  • 1
  • 10
  • 15
  • 1
    Please check my [related answer](https://stackoverflow.com/a/71064046/9841389), which is showing how to handle POST requests directly in [tag:shiny]. – ismirsehregal Feb 15 '22 at 07:09

4 Answers4

27

@jdharrison's answer is one way how you can handle GET requests in Shiny. Unfortunately, his or her statement that

shiny doesnt handle POST requests unfortunately.

is not, strictly speaking, 100% accurate.

You can handle POST requests in Shiny with the help of the function session$registerDataObj. An example of using this function can be found in this example. Basically, by calling registerDataObj function, it returns a unique request URL to which you can initiate either GET or POST requests. However, I wouldn't consider the example above very helpful in the context of your question because:

  1. There is no explicit use of AJAX in this example. Rather, the example exploits registerDataObj to create a PNG file handler, and it directly binds the URL to the src property of <img> tag.
  2. It is still using GET request not POST.

But, you can multiplex this handy function to handle both GET and POST perfectly fine. Consider the following example:

server.R

library(shiny)

shinyServer(function(input, output, session) {
  api_url <- session$registerDataObj( 
    name   = 'api', # an arbitrary but unique name for the data object
    data   = list(), # you can bind some data here, which is the data argument for the
                     # filter function below.
    filter = function(data, req) {
      print(ls(req))  # you can inspect what variables are encapsulated in this req
                      # environment
      if (req$REQUEST_METHOD == "GET") {
        # handle GET requests
        query <- parseQueryString(req$QUERY_STRING)
        # say:
        # name <- query$name
        # etc...
      } 

      if (req$REQUEST_METHOD == "POST") {
        # handle POST requests here

        reqInput <- req$rook.input

        # read a chuck of size 2^16 bytes, should suffice for our test
        buf <- reqInput$read(2^16)

        # simply dump the HTTP request (input) stream back to client
        shiny:::httpResponse(
          200, 'text/plain', buf
        )
      }          
    }
  )

  # because the API entry is UNIQUE, we need to send it to the client
  # we can create a custom pipeline to convey this message
  session$sendCustomMessage("api_url", list(url=api_url))

})

ui.R

library(shiny)

shinyUI(fluidPage(
  singleton(tags$head(HTML(
    '
  <script type="text/javascript">
    $(document).ready(function() {
      // creates a handler for our special message type
      Shiny.addCustomMessageHandler("api_url", function(message) {
        // set up the the submit URL of the form
        $("#form1").attr("action", "/" + message.url);
        $("#submitbtn").click(function() { $("#form1").submit(); });
      });
    })
  </script>
'
  ))),
  tabsetPanel(
    tabPanel('POST request example',
             # create a raw HTML form
             HTML('
<form enctype="multipart/form-data" method="post" action="" id="form1">
    <span>Name:</span>
    <input type="text" name="name" /> <br />
    <span>Passcode: </span> <br />
    <input type="password" name="passcode" /><br />
    <span>Avatar:</span>
    <input name="file" type="file" /> <br />
    <input type="button" value="Upload" id="submitbtn" />
</form>
')
    )
  )
))

Now, say I enter these test input:

Some test input

Then hit "Upload", you submit a POST request to the Shiny server, which, based on our R code, will dump your browser's POST request stream to you as response.

For example, I get:

------WebKitFormBoundary5Z0hAYXQXBHPTLHs
Content-Disposition: form-data; name="name"

foo
------WebKitFormBoundary5Z0hAYXQXBHPTLHs
Content-Disposition: form-data; name="passcode"

bar
------WebKitFormBoundary5Z0hAYXQXBHPTLHs
Content-Disposition: form-data; name="file"; filename="conductor.png"
Content-Type: image/png

‰PNG


IHDR  X   ¦   5Š_       pHYs  a  a¨?§i  ÕiTXtXML:com.adobe.xmp     <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.1.2">
   <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
      <rdf:Description rdf:about=""
            xmlns:tiff="http://ns.adobe.com/tiff/1.0/">
         <tiff:Compression>5</tiff:Compression>
         <tiff:PhotometricInterpretation>2</tiff:PhotometricInterpretation>
         <tiff:Orientation>1</tiff:Orientation>
      </rdf:Description>
   </rdf:RDF>
</x:xmpmeta>
# here I removed the binary file content
------WebKitFormBoundary5Z0hAYXQXBHPTLHs--

Clearly you can handle not only text data, but also file uploads as long as you write a POST request processor appropriately. Although this may not be trivial, but at least it is plausible and totally doable!

Of course, you have the obvious drawback that somehow you need communicate this unique request URL to the client, or to the server which will initiate the request. But technically there are many ways you can do that!

Xin Yin
  • 2,896
  • 21
  • 20
  • 1
    This is interesting and may help solve some issues I was having with integrating salesforce and shiny via https://github.com/forcedotcom/SalesforceCanvasJavascriptSDK which requires the acceptance of a POST request from salesforce. `session$registerDataObj` would I'm guessing enable the creation of a URL endpoint for this POST request. – jdharrison Aug 14 '14 at 04:01
  • 1
    As long as you can figure out a way to send the *unique* request handler URL to Salesforce, I think it will help. Actually, the author of the `registerDataObj` function has his cube downstairs of mine. I can ask him if there is any future consideration about more formal support of handling `POST` requests. Based on source reading, the implementation of `registerDataObj` can be currently considered as *expedient* as it uses some infrastructure originally used for handling downloads. – Xin Yin Aug 14 '14 at 04:09
  • 1
    Was that this commit https://github.com/rstudio/shiny/commit/d9be6f1d2e10c5a4f037c92913e48ee21f420863 . It seems like shiny is hard coded for `GET` requests here https://github.com/rstudio/shiny/blob/master/R/middleware.R#L191 . Thanks for highlighting `registerDataObj` I notice there is some documentation in `?session` – jdharrison Aug 14 '14 at 04:35
  • Yes, it is the commit that introduced `registerDataObj`. Also, the `staticHandler` is used for handling static files (files you put in the `www` directory). So it makes sense to restrict `GET` request only. – Xin Yin Aug 14 '14 at 04:43
  • This should be the accepted answer, because people may see the first answer and not continue to this one – DeanAttali Aug 08 '16 at 17:22
  • @daattali I have added a reference to Xin Yin's answer in my post. – jdharrison Aug 11 '16 at 23:02
  • 1
    this is just what I want but it is beyond my skills - can't understand how it works. Suppose I just want to access the text (in this case "foo") from the field "Name" for further processing (no need to return anything to the browser)?. – Steve Powell May 07 '17 at 18:08
  • @XinYin, could you please take a look at my question [here](https://stackoverflow.com/q/62754133/10841085)? Thank you for posting your solution to OP's question, I was looking for a way to implement this for a while so it's an immense help. However, I can't seem to make it work for file inputs and I'm in the dark as to what could be causing the issue as I'm very new to web development. I think it might have to do with the file input value not being persistent. – user51462 Jul 06 '20 at 10:41
16

You can receive GET requests using session$clientData. An example run the following

library(shiny)
runApp(list(
  ui = bootstrapPage(
    textOutput('text')
  ),
  server = function(input, output, session) {
    output$text <- renderText({
      query <- parseQueryString(session$clientData$url_search)
      paste(names(query), query, sep = "=", collapse=", ")
    })
  }
), port = 5678, launch.browser = FALSE)

and navigate to

http://127.0.0.1:5678/?transformerData=data/TransformerDataSampleForShiny.json

See @Xin Yin answer for a method to expose POST requests.

jdharrison
  • 30,085
  • 4
  • 77
  • 89
  • 4
    Your statement about `shiny doesn't handle POST requests` is *WRONG*. You can actually create POST request handler in Shiny with some tricks. – Xin Yin Aug 14 '14 at 02:39
  • @XinYin I will refer you to https://groups.google.com/forum/#!searchin/shiny-discuss/canvas/shiny-discuss/tWOIKnkb8No/6ypqPwAS6GIJ where in Joe Chengs opinion Shiny currently doesn't handle POST requests. – jdharrison Aug 14 '14 at 03:45
  • @jdharrison Thanks for the reference. I agree that currently there's no way you can handle POST requests using the functions in the documentation. However, there are some tricks that you can somehow use to implement a POST request handler, as my answer suggests. It is of course not clean nor elegant because (1) you need to manually parse the POST data (2) you don't have a consistent and invariable URL to which a POST request can be submitted. But at least you could receive a POST request and get all the data in there for downstream processing. – Xin Yin Aug 14 '14 at 03:49
  • Nice! Still though this return a MIME type text/html document. Is there a way to output text such as text/csv in order to trigger most browser to start a download? – Matt Bannert Sep 29 '15 at 20:36
2

Exciting update: As of Jan 2017, it was announced on RStudio Conf that this will be built into shiny in a future version (start watching at minute 15:00).

As of May 2017, this API feature is still not released, but I'm hoping it'll come soon.

DeanAttali
  • 25,268
  • 10
  • 92
  • 118
  • June 2020, @DeanAttali was this finally built into shiny? – Alfonso Jun 16 '20 at 13:14
  • 1
    I don't think so. I think they may have stopped working on it because plumbr became more prominent (plumbr is a way to create API from R code) – DeanAttali Jun 19 '20 at 17:09
  • You can review https://gist.github.com/jcheng5/2aaff19e67079840350d08361fe7fb20 if you want to handle session-independent responses. If you want to develop an API, I highly recommend using plumbr instead, but if you just need some session-independent dynamically generated content this can work. – Erik A Mar 07 '23 at 11:51
0

Not sure if this helps, but this came up as an issue for me when I wanted to swap out React-Django stack to a Shiny-Django stack - the advantage of this is Shiny is just no nice to work in to create really intricate UI, just using R Code - but the issue with this to make sure there is full CRUD functionality in the app and that whole post to shiny problem

I have found it works really nicely just to hold all my data in a Django app, with Django Rest Framework managing all the security and have the Shiny App call out to it, something like:

DJANGO_BASE_URL = "your-url-to-django-app"
DJANGO_API_KEY= "authToken for django, something like 'Token TJDU473C738383...'" 

APIResults <- httr::GET(paste0(DJANGO_BASE_URL, "accounts-api/v1/current-user/"),
                            add_headers(Authorization = DJANGO_API_KEY))
gCurrentUser <- fromJSON(content(APIResults, "text"))

Post requests that might be triggered after hitting an actionButton or something will be something like

   url <- "your-post-url"
    body <- list(formField = "blah")
     r <- POST(url, body = body, encode = "json")

This also seems to be nice solution if you want to have Shiny not to need to roll its own auth. You can manage all aspects of login/logout to the app pretty easily as the whole thing is just a front end for the data in django rest framework. I also took this approach because I wanted to avoid those ShinyAuth type packages which seem cool but tend to lead to difficulty when deploying to RConnect (for me at least) and the django rest framework is really nice when it comes to security.

Jamie
  • 165
  • 1
  • 3
  • 11