Почему печать не заставляет всю ленивую ценность IO?

Я использую учебник http-клиента, чтобы получить тело ответа, используя соединение 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 ответ

Решение

Это не связано с ленью, но с разницей между Response L.ByteString вы получаете с простым модулем, а Response BodyReader Вы получаете с модулем TLS.

Вы заметили, что BodyReader является IO ByteString, Но, в частности, это действие, которое может повторяться каждый раз со следующим фрагментом байтов. Из протокола следует, что он никогда не отправляет пустую строку, кроме случаев, когда она находится в конце файла. (BodyReader можно было бы назвать ChunkGetter). bip ниже, как то, что вы написали: после извлечения BodyReader / IO ByteString от Response, он выполняет его, чтобы получить первый фрагмент, и печатает его. Но не повторяйте действие, чтобы получить больше - поэтому в этом случае мы просто видим первые пару глав Бытия. То, что вам нужно, это петля для выпуска кусков, как в bop ниже, что приводит к тому, что вся Библия короля Джеймса проливается в консоль

{-# 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,

Итак, чтобы взять простой пример, в соответствующем http материале для streaming а также streaming-bytestring библиотеки, withHTTP дает ответ типа Response (ByteString IO ()), ByteString IO () это тип потока байтов, возникающих в IO, как следует из его названия; ByteString Identity () было бы эквивалентно ленивому байтовой строке (фактически чистый список кусков.) ByteString IO () будет в этом случае представлять весь поток до Апокалипсиса. Так и с импортом

 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

вы просто "печатаете" их напрямую. Если вы хотите посмотреть только немного из середины KJV, вы можете вместо этого сделать то, что вы сделали бы с ленивой строкой байтов, и закончить:

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

Тогда вы увидите что-то об Аврааме.

withHTTP за streaming-bytestring просто скрывает рекурсивный цикл, который нам нужно было использовать BodyReader материал из http-client непосредственно. То же самое, например, с withHTTP вы найдете в pipes-http, который представляет поток кусков байтовой строки как Producer ByteString IO () и то же самое с http-conduit, Во всех этих случаях, когда вы получаете доступ к потоку байтов, вы обрабатываете его способами, типичными для структуры потокового ввода-вывода без рукописной рекурсии. Все они используют BodyReader от http-client сделать это, и это было основной целью библиотеки.

Другие вопросы по тегам