You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何正确配置httr::POST超时?Shiny调用长耗时API异常排查

解决Shiny调用长时间运行Plumber API无响应的问题

我之前碰到过几乎一模一样的场景,大概率是中间网络层的空闲超时机制搞的鬼,不是httr或者Shiny本身的问题。咱们一步步拆解问题和解决:

核心原因分析

你的API要跑20分钟,这段时间里没有任何数据从API传输到Shiny端。如果你的Plumber API部署在AWS负载均衡器(比如ALB)后面,默认的空闲连接超时是60秒——意思是只要60秒内连接上没有数据流动,负载均衡器就会主动断开连接。这时候Shiny的httr请求还在原地等待,但实际连接已经失效了,所以既收不到API的最终响应,也不会触发你设置的timeout(2400)(因为总时长还没到阈值),直接导致Shiny一直卡在忙碌状态。

另外,shinyapps.io的网络代理也可能存在类似的空闲超时限制,但AWS ALB的问题在这类场景里更常见。

解决方案

方案1:调整AWS负载均衡器的空闲超时时间

这是最直接高效的解决办法:

  • 登录AWS控制台,找到你的负载均衡器
  • 进入「属性」页面,找到「空闲超时」设置项
  • 将超时时间调整为大于1200秒(20分钟),AWS ALB最长支持4000秒(约66分钟)
  • 保存设置后重新测试请求

方案2:给Plumber API添加心跳,保持连接活跃

如果没法调整负载均衡器的设置,或者想让请求更健壮,可以让Plumber在长时间计算过程中定期发送心跳数据,避免连接被判定为空闲:
修改你的Plumber代码:

#' Get data from a data base, does a long calculation and returns the result
#' 
#' @export
#' 
#' @post /calculate 
calculate <- function(req, res) {
  print("calculate started")
  
  # 开启流式响应,允许逐步发送数据
  plumber::res_set_chunked(res, TRUE)
  
  # 模拟长时间计算,每隔50秒发送一次心跳(空数据)
  for (i in 1:24) { # 24*50=1200秒,刚好覆盖20分钟计算
    Sys.sleep(50)
    # 发送空换行符,维持连接活跃状态
    plumber::res_write(res, "\n")
  }
  
  res <- 1
  print("calculate ended")
  
  # 发送最终计算结果
  return(res)
}

这样每隔50秒就会向Shiny端发送一个空行,让负载均衡器认为连接一直有数据传输,不会主动断开。

方案3:调试httr请求,确认连接状态

可以给httr的POST请求添加verbose配置,查看请求的详细过程,确认连接是否被断开:

observeEvent(input$button_clicked, { 
  print("calculate called") 
  res_calculate <- httr::POST( 
    url = "http://myapi.amazonaws.com", 
    path = "calculate", 
    timeout(2400),
    httr::verbose() # 添加调试日志,查看请求全流程
  ) 
  print("calculate returned")
  print(httr::content(res_calculate)) # 打印响应内容,验证结果
})

运行后查看Shiny的日志,如果连接被断开,你会看到类似「Connection closed」的提示,能帮你精准定位问题。

方案4:显式指定Plumber的响应格式

有时候Plumber自动转换响应可能出现隐性问题,显式指定响应格式可以避免这类情况:

calculate <- function() { 
  print("calculate started") 
  Sys.sleep(1200) 
  res <- 1 
  print("calculate ended") 
  plumber::render_json(res) # 显式返回JSON格式响应
}

验证步骤

  1. 优先尝试方案1(调整ALB超时),这是最快的解决路径
  2. 如果方案1不可行,再尝试方案2添加心跳机制
  3. 用方案3的verbose日志确认问题是否彻底解决

按照上面的方法调整后,Shiny应该就能正常收到Plumber的返回结果了!

内容的提问来源于stack exchange,提问作者vwrobel

火山引擎 最新活动