如何正确配置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(调整ALB超时),这是最快的解决路径
- 如果方案1不可行,再尝试方案2添加心跳机制
- 用方案3的verbose日志确认问题是否彻底解决
按照上面的方法调整后,Shiny应该就能正常收到Plumber的返回结果了!
内容的提问来源于stack exchange,提问作者vwrobel




