简体   繁体   English

流字节串作为WAI HTTP服务器响应主体

[英]Streaming bytestring as WAI HTTP server response body

I have a value body :: BS.ByteString (ResourceT IO) () , from a function based on BS.readFile . 我有一个基于BS.readFile的函数的值body :: BS.ByteString (ResourceT IO) () I want to stream that value as the response body from a Wai Application . 我想将该值作为Wai Application的响应主体进行流式处理。 There's a helper, streamingResponse that takes a value of the type Stream (Of ByteString) IO r . 有一个帮助器streamingResponse ,它采用Stream (Of ByteString) IO r类型的值。 I'm able to convert my BS.ByteString (ResourceT IO) () to Stream (Of ByteString) (ResourceT IO) () through the use of BS.toChunks , but it contains an extra ResourceT monad layer. 我能到我的转换BS.ByteString (ResourceT IO) ()Stream (Of ByteString) (ResourceT IO) ()通过使用BS.toChunks ,但它包含一个额外的ResourceT单子层。 Passing the body to streamingResponse gives me: body传递给streamingResponse给我:

Couldn't match type ‘ResourceT IO’ with ‘IO’
  Expected type: Stream (Of ByteString) IO ()
    Actual type: Stream (Of ByteString) (ResourceT IO) ()

I've tried various things like wrapping things in runResourceT, binding and hoisting values etc. but really have no idea how to proceed. 我已经尝试过各种方法,例如将它们包装在runResourceT中,绑定和提升值等,但实际上不知道如何进行。 Here 's the line in the full project if extra context is required. 这里的在整个项目的行,如果需要额外的上下文。

Update0 更新0

hoist runResourceT body seems to type check. hoist runResourceT body似乎要进行类型检查。 Someone also referred me to a Haskell Pipes thread , which may be a very related problem, and possible hint toward a solution. 有人还向我介绍了Haskell Pipes 线程 ,这可能是一个非常相关的问题,并且可能提示解决方案。

Instead of readFile , would withFile + hSetBinaryMode + Data.ByteString.Streaming.fromHandle be enough? 代替readFilewithFile + hSetBinaryMode + Data.ByteString.Streaming.fromHandle是否足够?

fromHandle produces a ByteString IO () that can be converted to a Stream (Of ByteString) IO () that streamingResponse or streamingBody can accept. fromHandle产生一个ByteString IO () ,可以将其转换为streamingResponsestreamingBody可以接受的Stream (Of ByteString) IO ()

There's the issue of where to put the withFile bracketing operation. 存在将withFile包围操作放在何处的问题。 According to the WAI documentation , you can wrap with it the result of your Application -building function: 根据WAI文档 ,您可以将Application构建功能的结果包装在一起:

Note that, since WAI 3.0, this type is structured in continuation passing style to allow for proper safe resource handling. 请注意,从WAI 3.0开始,此类型以连续传递样式构造,以允许正确安全地处理资源。 This was handled in the past via other means (eg, ResourceT). 过去是通过其他方式(例如ResourceT)来处理的。

[...] [...]

In order to allocate resources in an exception-safe manner, you can use the bracket pattern outside of the call to responseStream. 为了以异常安全的方式分配资源,可以在对responseStream的调用之外使用括号模式。


Note: the documentation of streaming-bytestring says that fromHandle closes the handle automatically when EOF is reached. 注意: streaming- fromHandle文档指出,到达EOF时fromHandle自动关闭句柄。 Looking at the implementation, that doesn't seem to be the case . 看一下实现, 情况似乎并非如此 You need withFile to properly close the handle. 您需要withFile才能正确关闭句柄。

If we want to allow Stream s that live in ResourceT , we can do without the functions from streaming-wai (that only work for Stream s based on IO ) and instead build on top of functions like responseStream from network-wai : 如果我们想允许StreamResourceT ,则可以不使用stream-wai的功能(仅适用于基于IO Stream ),而可以在诸如network-wai的 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 has type StreamingBody , which is actually a type synonym for a function (Builder -> IO ()) -> IO () -> IO () that takes a write callback and a flush callback as parameters, and uses them to write the response using some source of data that is in scope. streamingBody具有类型StreamingBody ,这实际上是一个功能的类型的同义词(Builder -> IO ()) -> IO () -> IO ()需要一个写入回调和冲洗回调作为参数,并使用它们来写使用范围内的某些数据源进行响应。 (Note that these callbacks are provided by WAI , not by the user.) (请注意,这些回调是由WAI提供的 ,而不是由用户提供的。)

In our case, the source of data is a Stream that lives in ResourceT . 在我们的例子中,数据源是StreamResourceTStream We need to lift the write and flush callbacks (that live in IO ) using liftIO , an also remember to invoke runResourceT to return a plain IO action at the end. 我们需要使用liftIO来提升write和flush回调(位于IO ),还要记住调用runResourceT以在最后返回简单的IO操作。


What if we wanted to flush the response only after the accumulated length of the emitted bytestrings reached some limit? 如果我们想冲洗后, 发出的字节串的累计长度达到一定极限的反应如何?

We would need a function (not implemented here) to create a division each time the limit is reached: 每次达到限制时,我们将需要一个函数(此处未实现)来创建除法:

breaks' :: Monad m 
        => Int 
        -> Stream (Of ByteString) m r 
        -> Stream (Stream (Of ByteString) m) m r
breaks' breakSize = undefined

And then we could intercalate the flushing action between each group using intercalates , before writing the stream: 然后,我们可以在编写流之前使用intercalates在每组之间插入刷新动作:

streamingBodyFrom :: Stream (Of ByteString) (ResourceT IO) () 
                  -> Int 
                  -> StreamingBody
streamingBodyFrom stream breakSize writeBuilder flush =
    let writer a = liftIO (writeBuilder (byteString a))
        flusher = liftIO flush
        broken = breaks' breakSize stream
     in runResourceT . S.mapM_ writer . S.intercalates flusher $ broken

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM