为什么不打印强制整个惰性IO值?

时间:2017-01-05 00:02:36

标签: haskell conduit haskell-pipes http-conduit lazy-io

我使用http-client教程使用TLS连接获取响应正文。由于我可以观察print调用withResponse,为什么不会print强制对以下片段中的输出进行完整响应?

withResponse request manager $ \response -> do
    putStrLn $ "The status code was: " ++
    body <- (responseBody response)
    print body

我需要写这个:

response <- httpLbs request manager

putStrLn $ "The status code was: " ++
           show (statusCode $ responseStatus response)
print $ responseBody response

我要打印的身体是一个懒惰的ByteString。我还不确定是否应该print打印整个值。

instance Show ByteString where
    showsPrec p ps r = showsPrec p (unpackChars ps) r

1 个答案:

答案 0 :(得分:4)

这与懒惰无关,但是您使用Simple模块获得的Response L.ByteString与使用TLS模块获得的Response BodyReader之间存在差异。

您注意到BodyReaderIO ByteString。但特别是它是一个可以重复的动作,每次都使用 next 大块的字节。它遵循协议,它永远不会发送空字节串,除非它在文件末尾。 (BodyReader可能被称为ChunkGetter)。下面的bip就像你写的那样:从BodyReader中提取IO ByteString / Response之后,它会执行它以获取第一个块,然后打印它。但是并没有重复这个动作来获得更多 - 所以在这种情况下我们只看到创世记的前几章。你需要的是一个耗尽块的循环,如下面的bop,这导致整个King James Bible溢出到控制台。

{-# LANGUAGE OverloadedStrings #-} 
import Network.HTTP.Client
import Network.HTTP.Client.TLS
import qualified Data.ByteString.Char8 as B

main = bip
-- main = bop

bip = do 
  manager <- newManager tlsManagerSettings
  request <- parseRequest "https://raw.githubusercontent.com/michaelt/kjv/master/kjv.txt"
  withResponse request manager $ \response -> do
      putStrLn "The status code was: "  
      print (responseStatus response)
      chunk  <- responseBody response
      B.putStrLn chunk

bop = do 
  manager <- newManager tlsManagerSettings
  request <- parseRequest "https://raw.githubusercontent.com/michaelt/kjv/master/kjv.txt"
  withResponse request manager $ \response -> do
      putStrLn "The status code was: " 
      print (responseStatus response)
      let loop = do 
            chunk <- responseBody response
            if B.null chunk 
              then return () 
              else B.putStr chunk  >> loop 
      loop

循环继续返回以获取更多块,直到它获得一个空字符串(表示eof),因此在终端中它会打印到启示录的末尾。

这种行为很简单,但技术性稍差。您只能通过手写递归使用BodyReader。但http-client库的目的是使http-conduit之类的东西成为可能。 withResponse的结果类型为Response (ConduitM i ByteString m ())ConduitM i ByteString m ()是字节流的管道类型;这个字节流将包含整个文件。

http-client / http-conduit材料的原始形式中,Response包含这样的管道; BodyReader部分后来被考虑到http-client,因此可以被pipes等不同的流媒体库使用。

举一个简单的例子,在streamingstreaming-bytestring库的相应http材料中,withHTTP会为您提供Response (ByteString IO ())类型的回复。 ByteString IO ()是IO中出现的字节流的类型,顾名思义; ByteString Identity ()相当于一个惰性字节串(实际上是一个纯粹的块列表。)ByteString IO ()在这种情况下将表示直到Apocalypse的整个字节流。所以用进口

 import qualified Data.ByteString.Streaming.HTTP as Bytes -- streaming-utils
 import qualified Data.ByteString.Streaming.Char8 as Bytes -- streaming-bytestring

该程序与惰性字节串程序相同:

bap = do 
    manager <- newManager tlsManagerSettings
    request <- parseRequest "https://raw.githubusercontent.com/michaelt/kjv/master/kjv.txt"
    Bytes.withHTTP request manager $ \response -> do 
        putStrLn "The status code was: "
        print (responseStatus response)
        Bytes.putStrLn $ responseBody response

确实它稍微简单一点,因为你没有从IO`中提取字节:

        lazy_bytes <- responseStatus response
        Lazy.putStrLn lazy_bytes

但是写一下

        Bytes.putStrLn $ responseBody response

你只是&#34;打印&#34;他们直接。如果你想从KJV的中间稍微查看一下,你可以用懒惰的字节串做你想做的事情,然后结束:

        Bytes.putStrLn $ Bytes.take 1000 $ Bytes.drop 50000 $ responseBody response

然后你会看到有关亚伯拉罕的事情。

withHTTP的{​​{1}}只是隐藏了我们需要直接使用streaming-bytestring BodyReader材料所需的递归循环。它是相同的,例如使用pipes-http中找到的http-client,它表示字节串块的流为withHTTP,与Producer ByteString IO ()相同。在所有这些情况下,一旦您掌握了字节流,就可以通过流式IO框架的典型方式处理它,而无需手写递归。所有人都使用http-conduit中的BodyReader来执行此操作,这是图书馆的主要目的。