You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何结合ResourceT使用Web.Scotty流处理S3文件?

问题分析与解决方案

核心问题是S3响应的Body只能被消费一次,且代码中存在ResourceT作用域与Monad栈整合的问题:

  1. 重复消费Body导致连接关闭
    你先通过sinkBody打印Body内容,此时HTTP连接的响应流已被读取完毕,S3端会关闭连接。之后再尝试在stream回调中读取同一个Body,必然触发ConnectionClosed异常——因为Body资源已耗尽,连接不存在了。

  2. ResourceT嵌套与作用域错误
    代码中嵌套了两层runResourceT,这不仅冗余,还可能导致资源提前释放。Amazonka.send本身就在ResourceT monad中运行,外层单个runResourceT已足够管理请求的资源生命周期。

修正后的代码示例

import qualified Data.Conduit as CC
import Network.HTTP.Types.Status (ok200)
import Web.Scotty
import Amazonka
import Amazonka.S3.GetObject

main :: IO ()
main = do
    env <- newEnv discover
    scotty 3000 (app env)

app :: Env -> ScottyM ()
app env = do
    get "/stream-file" $ do
        -- 设置响应状态码与内容类型(根据实际文件调整)
        status ok200
        setHeader "Content-Type" "application/octet-stream"
        
        -- 在stream回调内处理S3请求与流式传输
        stream $ \send flush -> do
            runResourceT $ do
                resp <- send env (newGetObject "bucket" "file")
                let body = resp ^. getObjectResponse_body
                -- 将S3 Body流式转发给客户端
                body `sinkBody` (CC.map fromByteString .| CC.mapM_ (liftIO . send) >> liftIO flush)

关键调整说明

  • 避免重复消费Body:移除单独打印Body的代码,让Body仅被流式传输到客户端一次。若需调试,可在流式传输的同时打印(生产环境建议移除)。
  • 正确管理ResourceT作用域:将runResourceT移至stream回调内部,确保S3请求的资源(含HTTP连接)在整个流式传输过程中保持有效,不会提前释放。
  • 明确响应头:添加必要的响应头,让客户端正确识别返回内容。

额外提示

如果需要在流式传输时记录日志(比如打印内容),可修改Conduit管道同时处理发送与打印:

body `sinkBody` (CC.map fromByteString .| CC.mapM_ (\bs -> liftIO (send bs) >> liftIO (print bs)) >> liftIO flush)

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 20:22:52