我有一个基于BS.readFile
的函数的值body :: BS.ByteString (ResourceT IO) ()
。我想将该值作为Wai Application
的响应主体进行传输。有一个帮助器,streamingResponse
采用Stream (Of ByteString) IO r
类型的值。我可以通过使用BS.toChunks
将BS.ByteString (ResourceT IO) ()
转换为Stream (Of ByteString) (ResourceT IO) ()
,但它包含额外的ResourceT
monad图层。将body
传递给streamingResponse
会让我:
Couldn't match type ‘ResourceT IO’ with ‘IO’
Expected type: Stream (Of ByteString) IO ()
Actual type: Stream (Of ByteString) (ResourceT IO) ()
我尝试了各种各样的东西,比如在runResourceT中包装东西,绑定和提升值等但是真的不知道如何继续。如果需要额外的上下文,Here是整个项目中的一行。
hoist runResourceT body
似乎打字检查。有人还向我推荐了一个Haskell Pipes thread,这可能是一个非常相关的问题,并且可能暗示解决方案。
答案 0 :(得分:1)
而不是readFile
,withFile
+ hSetBinaryMode
+ Data.ByteString.Streaming.fromHandle
是否足够?
fromHandle
生成ByteString IO ()
,可以转换为Stream (Of ByteString) IO ()
或streamingResponse
可以接受的streamingBody
。
存在将withFile
包围操作放在何处的问题。根据{{3}},您可以用它包含Application
- 构建函数的结果:
请注意,自WAI 3.0以来,此类型的结构是连续的 传递样式以允许适当的安全资源处理。这是 过去通过其他方式处理(例如,ResourceT)。
[...]
为了以异常安全的方式分配资源,您可以 在对responseStream的调用之外使用括号模式。
注意: streaming-bytestring 的文档说fromHandle
在达到EOF时会自动关闭句柄。看一下实施,WAI documentation。您需要withFile
才能正确关闭手柄。
答案 1 :(得分:1)
如果我们想要允许Stream
中的ResourceT
,我们可以不使用streaming-wai中的函数(仅适用于基于{Stream
的{{1}} 1}})而是建立在network-wai的IO
之类的函数之上:
responseStream
import Control.Monad.Trans.Resource
import Network.Wai
import Streaming
import qualified Streaming.Prelude as S
import Data.ByteString.Builder (byteString, Builder)
streamingResponseR :: Stream (Of ByteString) (ResourceT IO) r
-> Status
-> ResponseHeaders
-> Response
streamingResponseR stream status headers =
responseStream status headers streamingBody
where
streamingBody writeBuilder flush =
let writer a =
do liftIO (writeBuilder (byteString a))
-- flushes for every produced bytestring, perhaps not optimal
liftIO flush
in runResourceT $ void $ S.effects $ S.for stream writer
具有类型StreamingBody
,它实际上是函数streamingBody
的类型同义词,它将写回调和刷新回调作为参数,并使用它们来编写响应范围内的一些数据源。 (请注意,这些回调由WAI 提供,而不是由用户提供。)
在我们的案例中,数据来源是(Builder -> IO ()) -> IO () -> IO ()
,位于Stream
。我们需要使用ResourceT
解除写入和刷新回调(存在于IO
中),并且还记得调用liftIO
以在最后返回纯runResourceT
个动作。
如果我们想在发出的字节串的累计长度达到某个限制之后才清除响应 ,该怎么办?
每次达到限制时,我们需要一个函数(此处未实现)来创建一个除法:
IO
然后我们可以在编写流之前使用intercalates
插入每个组之间的刷新操作:
breaks' :: Monad m
=> Int
-> Stream (Of ByteString) m r
-> Stream (Stream (Of ByteString) m) m r
breaks' breakSize = undefined