我想让warp运行一个进程,然后对该进程的输出进行响应。假定输出大于服务器的RAM。加载整个输出,然后响应不是一种选择。我以为我可以使用类似的方法来完成此任务
withProcessWait_ (setStdout createSource "cat largefile") (pure . responseSource ok200 [] . getStdout)
但是responseSource
使用ConduitT i (Flush Builder) IO ()
,而createSource
使用ConduitT i ByteString m ()
。我不知道如何将ByteString
管道转换为Flush Builder
管道。
因此,我设计了一种似乎可行的解决方案,但遗憾的是它的定义不太简单:
responseProcess :: Status -> ResponseHeaders -> ProcessConfig in out err -> Response
responseProcess s hs cfg = responseStream s hs $ \send flush ->
withProcessWait_ (setStdout createPipe cfg) $ \p@(getStdout -> h) ->
let loop = do
bs <- hGetSome h defaultChunkSize
unless (BS.null bs) (send (byteString bs) *> flush *> loop)
in loop *> hClose h
。即使我可以用mkStreamSpec
包裹起来进行修饰来修饰它,这也是必要的吗?还是我缺少一种更简单的方法?
编辑:对解决方案的评论:
intersperseC
让我一起使用Chunk
和Flush
。这样就解决了Flush Builder
/ ByteString
的转换问题。我尚未对其进行测试,但是它看起来不错,并且我相信它已经被使用了。
但是,我发现了
withProcessWait_ (setStdout createSource "cat largefile") $ \p ->
responseSource ok200 [] (getStdout p .| mapC (Chunk . byteString) .| intersperseC Flush)
太早关闭进程句柄。因此,我需要自己管理管道:使用createPipe
而不是createSource
。但这意味着我需要在最后调用hClose
,这意味着我需要一个返回IO ()
的响应处理程序; (responseRaw
除外)是唯一的responseStream
,它使用StreamingBody
作为管道的替代。因此,我得出结论,我需要原始的解决方案,并且Conduit不能用于流式处理。如果不正确,请随时纠正。
答案 0 :(得分:0)
responseSource
具有类型
responseSource ::状态-> ResponseHeaders->源IO(刷新 生成器)->响应
并且Flush
的定义是
数据刷新a =块a |冲洗
也就是说,类型Flush Builder
的值可以是Builder
或指示 warp 刷新输出流的命令。
Builder
来自binary软件包。它基本上是一个字节块的表示形式,已针对有效串联进行了优化。可以使用fromByteString
函数从ByteString
构造它。
知道了这一点,并使用导管中的mapC
,我们可以定义此适配器:
adapter :: :: Monad m => ConduitT ByteString (Flush Builder) m ()
adapter = mapC (Chunk . fromByteString)
但是有一个问题,适配器从不刷新。但是我们可以通过intersperseC
来插入命令:
adapter :: :: Monad m => ConduitT ByteString (Flush Builder) m ()
adapter = mapC (Chunk . fromByteString) .| intersperseC Flush
如果我们不想在每个块之后都刷新怎么办?也许我们可以使用chunksOfCE
对字节块进行分组,然后再将其转换为Flush
值。