【问题标题】:Piping an http stream through a haskell conduit通过 haskell 管道传输 http 流
【发布时间】:2016-04-30 02:07:43
【问题描述】:

我正在尝试创建一个管道,该管道将通过管道源从 HTTP 流式传输数据。这是我目前所拥有的:

import qualified Network.HTTP.Client.Conduit as CC

getStream :: String -> IO (ConduitM () BS.ByteString IO ())
getStream url = do
  req <- parseUrl url
  return $  CC.withResponse req $ \res -> do
    responseBody res $= (awaitForever $ \bytes -> liftIO $ do
      putStrLn $ "Got " ++ show (BS.length bytes) ++ " but will ignore    them")

但我得到了

No instance for (Control.Monad.Reader.Class.MonadReader env0 IO) …
      arising from a use of ‘CC.withResponse’
    In the expression: CC.withResponse req
    In the second argument of ‘($)’, namely
      ‘CC.withResponse req
       $ \ res
           -> do { responseBody res $= (awaitForever $ \ bytes -> ...) }’
    In a stmt of a 'do' block:
      return
      $ CC.withResponse req
        $ \ res
            -> do { responseBody res $= (awaitForever $ \ bytes -> ...) }

为什么会出现MonadReader?这对我来说没有任何意义。

【问题讨论】:

    标签: haskell conduit http-conduit


    【解决方案1】:

    the example in the Network.HTTP.Conduit docs 的这种变体怎么样:

    {-# LANGUAGE OverloadedStrings #-}
    
    module Lib2 () where
    
    import Data.Conduit (($$+-), awaitForever)
    import qualified Network.HTTP.Client.Conduit as CC
    import Network.HTTP.Conduit (http, tlsManagerSettings, newManager)
    import Control.Monad.IO.Class (liftIO)
    import Control.Monad.Trans.Resource (runResourceT)
    import Data.Conduit.Binary (sinkFile) -- Exported from the package conduit-extra
    
    main2 :: IO ()
    main2 = do
           request <- CC.parseUrl "http://google.com/"
           manager <- newManager tlsManagerSettings
           runResourceT $ do
               response <- http request manager
               CC.responseBody response $$+- (awaitForever $ \x -> liftIO $ putStrLn "Chunk")
    

    原答案

    getStream 的返回类型错误。尝试删除类型签名并使用FlexibleContexts,例如:

    {-# LANGUAGE OverloadedStrings, FlexibleContexts #-}
    
    module Lib () where
    
    import Data.Conduit
    import qualified Data.ByteString as BS
    import qualified Network.HTTP.Client.Conduit as CC
    import Control.Monad.IO.Class
    
    getStream url = do
      req <- CC.parseUrl url
      CC.withResponse req $ \res -> do
       CC.responseBody res $= (awaitForever $ \x -> liftIO $ putStrLn "Got a chunk")
    

    然后:t getStream 报告:

    getStream
      :: (monad-control-1.0.0.4:Control.Monad.Trans.Control.MonadBaseControl
            IO (ConduitM a c m),
          mtl-2.2.1:Control.Monad.Reader.Class.MonadReader env m, MonadIO m,
          CC.HasHttpManager env,
          exceptions-0.8.0.2:Control.Monad.Catch.MonadThrow m) =>
         String -> ConduitM a c m ()
    

    这表明返回类型的格式为ConduitM ...,而不是IO ...

    这也显示了MonadReader 如何进入图片... monad m 必须通过阅读器环境访问 HTTP 管理器,如以下约束所示:

    CC.HasHttpManager env
    MonadReader env m
    

    这就是说m 有一个env 类型的阅读器环境,它本身有一种访问HTTP 管理器的方法。

    特别是,m 不能只是简单的 IO monad,这是错误消息所抱怨的。

    在 cmets 中回答问题

    以下是如何从 HTTP 响应创建 Producer 的示例:

    {-# LANGUAGE OverloadedStrings #-}
    
    module Lib3 () where
    
    import qualified Data.ByteString as BS
    import qualified Network.HTTP.Client.Conduit as CC
    import           Network.HTTP.Conduit (http, tlsManagerSettings, newManager)
    import qualified Network.HTTP.Client          as Client (httpLbs, responseOpen, responseClose)
    import           Data.Conduit (Producer, addCleanup)
    import           Data.Conduit (awaitForever, await, ($$))
    import qualified Network.HTTP.Client.Conduit  as HCC
    
    import Control.Monad.IO.Class (liftIO, MonadIO)
    
    getStream url = do
      request <- CC.parseUrl url
      manager <- newManager tlsManagerSettings
      response <- Client.responseOpen request manager
      let producer :: Producer IO BS.ByteString
          producer = HCC.bodyReaderSource $ CC.responseBody response
          cleanup _ = do liftIO $ putStrLn "(cleaning up)"; Client.responseClose response
          producerWithCleanup = addCleanup cleanup producer
      return $ response { CC.responseBody = producerWithCleanup }
    
    test = do
      res <- getStream "http://google.com"
      let producer = CC.responseBody res
          consumer = awaitForever $ \_ -> liftIO $ putStrLn "Got a chunk"
      producer $$ consumer
    

    【讨论】:

    • 哇,效果比我预期的要好。只有一个问题:似乎使用Source 流的唯一方法是提供一个接收器作为参数。有没有办法实际返回Source 流,例如。包裹在IO。如果不是为什么?此外,我不太明白为什么需要 Resouce monad。 (很抱歉,如果看起来很明显,我仍在努力绕开Conduit
    • 答案已更新 - 见最后。在http 函数中,ResourceT 用于调用响应的终结器。但是,终结器也被添加到Producer 管道中(例如producerWithCleanup)所以我不知道是否真的需要使用ResourceT - 即使消费者没有消耗所有的终结器似乎也会被调用的块。
    • 更多关于 ResourceT 使用的信息在这里:github.com/snoyberg/http-client/issues/194
    猜你喜欢
    • 2013-07-16
    • 1970-01-01
    • 2016-09-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-30
    • 2018-06-04
    相关资源
    最近更新 更多