如何结合ResourceT使用Web.Scotty流处理S3文件?
问题分析与解决方案
核心问题是S3响应的Body只能被消费一次,且代码中存在ResourceT作用域与Monad栈整合的问题:
重复消费Body导致连接关闭
你先通过sinkBody打印Body内容,此时HTTP连接的响应流已被读取完毕,S3端会关闭连接。之后再尝试在stream回调中读取同一个Body,必然触发ConnectionClosed异常——因为Body资源已耗尽,连接不存在了。ResourceT嵌套与作用域错误
代码中嵌套了两层runResourceT,这不仅冗余,还可能导致资源提前释放。Amazonka.send本身就在ResourceTmonad中运行,外层单个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
相关产品推荐
相关产品推荐

