在 R 闪亮的应用程序中接受 HTTP 请求

Posted

技术标签:

【中文标题】在 R 闪亮的应用程序中接受 HTTP 请求【英文标题】:Accept HTTP Request in R shiny application 【发布时间】:2014-10-07 11:33:22 【问题描述】:

我制作了一个闪亮的应用程序,它需要从另一台服务器获取其数据,即当闪亮的应用程序打开时,另一台服务器向闪亮的应用程序发送请求以打开应用程序并向其提供它的数据需要。

为了模拟这一点,当我在 Firefox 中打开应用程序时,我可以将以下内容发送到 R Shiny 应用程序:

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

这是一个简单的 get 请求,它发送调用的字符串: “变压器数据” 将内容“data/TransformerDataSampleForShing.json”添加到闪亮的应用程序中。

当我使用代码时它工作正常:

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

但是当我想做完全相同的事情时,除了对字符串“data/TransformerDataSampleForShiny.json”进行硬编码之外,我想从上面的 http 请求中接收该字符串。我该怎么做呢??我已经尝试了代码:

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

我也试过了:

....
jsonFile <- input$TransformerData

但这些都没有奏效。

所以主要问题是,我如何编写代码来接收 HTTP 请求?我想接收来自 HTTP GET 请求的字符串和/或来自 POST 请求的 JSON 文件。

只是为了澄清我不想发送帖子或从 R 获取请求。我想接收它们。我无法使用 httr 包或 httpRequest 包来接收

非常感谢!

【问题讨论】:

请查看我的related answer,它显示了如何直接在shiny 中处理POST 请求。 【参考方案1】:

@jdharrison 的回答是在 Shiny 中处理 GET 请求的一种方式。 不幸的是,他或她的声明

不幸的是,shiny 不处理 POST 请求。

严格来说,并非 100% 准确。

可以借助函数session$registerDataObj 在 Shiny 中处理 POST 请求。使用此功能的示例can be found in this example。基本上,通过调用registerDataObj 函数,它会返回一个唯一 请求URL,您可以向该URL 发起GETPOST 请求。但是,对于您的问题,我认为上面的示例不会很有帮助,因为:

    在此示例中没有明确使用 AJAX。相反,该示例利用registerDataObj 创建一个PNG 文件处理程序,并将URL 直接绑定到&lt;img&gt; 标记的src 属性。 它仍在使用GET 请求而不是POST

但是,您可以复用这个方便的函数来完美处理GETPOST。 考虑以下示例:

服务器.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>
')
    )
  )
))

现在,假设我输入了这些测试输入:

然后点击"Upload",向 Shiny 服务器提交一个 POST 请求,该服务器根据我们的 R 代码将浏览器的 POST 请求流作为响应转储给您。

例如,我得到:

------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--

显然,只要您适当地编写 POST 请求处理器,您不仅可以处理文本数据,还可以处理文件上传。虽然这可能不是微不足道的,但至少它是有道理的,完全可行的!

当然,您有一个明显的缺点,即您需要以某种方式将此唯一 请求 URL 传递给客户端,或者传递给将发起请求的服务器。但从技术上讲,有很多方法可以做到这一点!

【讨论】:

这很有趣,可能有助于解决我在通过github.com/forcedotcom/SalesforceCanvasJavascriptSDK 集成salesforce 和shiny 时遇到的一些问题,这需要接受salesforce 的POST 请求。 session$registerDataObj 我猜是不是可以为这个 POST 请求创建一个 URL 端点。 只要您能找到一种将 unique 请求处理程序 URL 发送到 Salesforce 的方法,我认为它会有所帮助。实际上,registerDataObj 函数的作者在我的楼下有他的立方体。我可以问他将来是否有任何关于处理POST 请求的更正式支持的考虑。根据源码阅读,registerDataObj 的实现目前可以被认为是权宜,因为它使用了一些最初用于处理下载的基础设施。 这个提交是github.com/rstudio/shiny/commit/…。似乎闪亮是硬编码的 GET 请求在这里 github.com/rstudio/shiny/blob/master/R/middleware.R#L191 。感谢您突出显示registerDataObj 我注意到?session 中有一些文档 没错,就是引入registerDataObj的commit。此外,staticHandler 用于处理静态文件(您放在www 目录中的文件)。因此,仅限制 GET 请求是有意义的。 这正是我想要的,但它超出了我的技能——无法理解它是如何工作的。假设我只想从“名称”字段访问文本(在本例中为“foo”)以进行进一步处理(无需向浏览器返回任何内容)?。【参考方案2】:

您可以使用session$clientData 接收 GET 请求。一个例子运行如下

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)

并导航到

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

有关公开 POST 请求的方法,请参阅 @Xin Yin 回答。

【讨论】:

您关于shiny doesn't handle POST requests 的陈述是错误。您实际上可以通过一些技巧在 Shiny 中创建 POST 请求处理程序。 @XinYin 我会把你推荐给groups.google.com/forum/#!searchin/shiny-discuss/canvas/…,Joe Cheng 认为 Shiny 目前不处理 POST 请求。 @jdharrison 感谢您的参考。我同意目前您无法使用文档中的函数处理 POST 请求。但是,正如我的回答所暗示的,您可以使用一些技巧来实现 POST 请求处理程序。它当然不干净也不优雅,因为 (1) 您需要手动解析 POST 数据 (2) 您没有可​​以提交 POST 请求的一致且不变的 URL。但至少您可以收到一个 POST 请求并获取其中的所有数据以进行下游处理。 不错!尽管如此,这仍然返回一个 MIME 类型的 text/html 文档。有没有办法输出 text/csv 等文本以触发大多数浏览器开始下载?【参考方案3】:

激动人心的更新:截至 2017 年 1 月,it was announced on RStudio Conf 将在未来的版本中内置到闪亮中(从 15:00 开始观看)。

截至 2017 年 5 月,此 API 功能仍未发布,但我希望它会尽快发布。

【讨论】:

2020 年 6 月,@DeanAttali 这终于内置到闪亮中了吗? 我不这么认为。我认为他们可能已经停止研究它,因为 plumbr 变得更加突出(plumbr 是一种从 R 代码创建 API 的方法)【参考方案4】:

不确定这是否有帮助,但是当我想将 React-Django 堆栈换成 Shiny-Django 堆栈时,这对我来说是个问题 - 这样做的好处是 Shiny 不适合创建真正复杂的 UI,只使用 R 代码 - 但问题在于确保应用程序中有完整的 CRUD 功能以及整个帖子到闪亮的问题

我发现将我的所有数据保存在 Django 应用程序中的效果非常好,由 Django Rest Framework 管理所有安全性并让 Shiny 应用程序调用它,例如:

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"))

点击 actionButton 或类似的东西后可能触发的发布请求

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

如果您想让 Shiny 不需要滚动自己的身份验证,这似乎也是一个不错的解决方案。您可以非常轻松地管理登录/注销应用程序的所有方面,因为整个事情只是 django rest 框架中数据的前端。我也采用了这种方法,因为我想避免那些看起来很酷但在部署到 RConnect 时往往会导致困难的 ShinyAuth 类型包(至少对我而言),并且 django rest 框架在安全性方面非常好。

【讨论】:

以上是关于在 R 闪亮的应用程序中接受 HTTP 请求的主要内容,如果未能解决你的问题,请参考以下文章

r - 空 textInput() 导致传单闪亮应用程序出错

R:快速启动的 GUI(闪亮的替代品?)

在 R 闪亮的模块中使用 actionButton + insertUI 创建多个输入

在闪亮的应用程序中重定向

如何在闪亮的 R 应用程序中使用传单添加控制输入?

在R闪亮的网络应用程序中获取RSS提要