首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >http-导管:使用gzip传输数据

http-导管:使用gzip传输数据
EN

Stack Overflow用户
提问于 2014-02-15 04:26:28
回答 1查看 380关注 0票数 0

我打算使用http-conduit通过HTTP/HTTPS获取大量数据。为了有效地做到这一点,我想使用Accept-Encoding: deflate,gzip头来允许服务器(如果支持的话)以压缩的方式传输数据。

但是,我想从其中获取的一些服务器似乎在没有返回gzip数据的情况下使用Content-Encoding: gzip头进行了错误的响应。

因此,我需要处理这些个案:

  • 服务器不支持压缩->返回普通响应体
  • 服务器返回压缩/压缩的内容->返回解压缩响应体
  • 服务器说(在响应头中,它返回gzip内容,但是gzip解码失败->返回普通响应体。)

在第三种情况下,可以(在这种特定情况下)安全地假定明文、未压缩的数据看起来不像gzip数据,因此响应标头说它是gzip && un失败完全等同于数据没有压缩。

我如何使用http-conduit来完成这个任务?

注意:这个问题故意没有表现出研究的努力,因为它已经被立即以问答式的方式回答了。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2014-02-15 04:26:28

为了使这个答案更简洁,我们将使用代码&一些我以前的文章中的概念:

  • simpleHttpWithManager来自here
  • here中容忍gzip/压缩解码

为了避免冗余,我将首先解释基本步骤,然后提供一个完整的示例。

首先,我们将处理发送标头。注意,wile http-types包含hContentEncodinghAcceptEncoding不是预定义的。此外,这是一项琐碎的工作。

发送请求后,我们需要检查是否存在Content-Encoding。如果没有,我们将假定未压缩的明文,否则我们需要检查它是gzip还是deflate。在这个上下文中,到底是哪一个并不重要,因为zlib支持按头自动检测。

对于这个非常简单的示例,我们只是假设,如果服务器返回一个既不存在也不存在的Content-Encoding值,那么响应就不会被压缩。由于我们不允许(通过Accept-Encoding)其他压缩,如sdch,服务器将违反HTTP标准的行为方式。

如果我们检测到压缩编码,我们尝试解压缩并返回它。如果失败了,或者数据根本没有被压缩,我们将返回普通响应体。

下面是一个例子:

代码语言:javascript
复制
{-# LANGUAGE OverloadedStrings #-}
import Network.HTTP.Conduit
import Network.Connection
import Codec.Compression.Zlib.Internal
import Data.Maybe
import Data.Either
import Network.HTTP.Types
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Lazy.Char8 as LB

myurl :: String
myurl = "http://stackoverflow.com"

hAcceptEncoding :: HeaderName
hAcceptEncoding = "Accept-Encoding"

-- | The Accept-Encoding HTTP header value for allowing gzip or deflated responses
gzipDeflateEncoding :: ByteString
gzipDeflateEncoding = "gzip,deflate"

-- HTTP header list that allows gzipped/deflated response
compressionEnabledHeaders :: RequestHeaders
compressionEnabledHeaders = [(hAcceptEncoding, gzipDeflateEncoding)]

-- | Give an encoding string and a HTTP response object,
--   Checks if the Content-Encoding header value of the response object
--   is equal to the given encoding. Returns false if no ContentEncoding
--   header exists in the given response, or if the value does not match
--   the encoding parameter.
hasResponseEncoding :: ByteString -> Response b -> Bool
hasResponseEncoding encoding response =
    let responseEncoding = lookup hContentEncoding headers
        headers = responseHeaders response
    in maybe False (== encoding) responseEncoding

-- | Convert the custom error format from zlib to a Either
decompressStreamToEither :: DecompressStream -> Either String LB.ByteString
decompressStreamToEither (StreamError _ errmsg) = Left errmsg
decompressStreamToEither stream@(StreamChunk _ _) = Right $ fromDecompressStream stream
decompressStreamToEither StreamEnd = Right $ ""

-- | Decompress with explicit error handling
safeDecompress :: LB.ByteString -> Either String LB.ByteString
safeDecompress bstr = decompressStreamToEither $ decompressWithErrors gzipOrZlibFormat defaultDecompressParams bstr

-- | Decompress gzip, if it fails, return uncompressed String
decompressIfPossible :: LB.ByteString -> LB.ByteString
decompressIfPossible bstr =
    let conv (Left a) = bstr
        conv (Right a) = a
    in (conv . safeDecompress) bstr

-- | Tolerantly decompress response body. As some HTTP servers set the header incorrectly,
--   just return the plain response text if the compression fails
decompressResponseBody :: Response LB.ByteString -> LB.ByteString
decompressResponseBody res
    | hasResponseEncoding "gzip" res = decompressIfPossible $ responseBody res
    | hasResponseEncoding "deflate" res = decompressIfPossible $ responseBody res
    | otherwise = responseBody res

-- | Download like with simpleHttp, but using an existing manager for the task
--   and automatically requesting & handling gzipped data
simpleHttpWithAutoGzip :: Manager -> String -> IO LB.ByteString
simpleHttpWithAutoGzip manager url = do req <- parseUrl url
                                        let req' = req {requestHeaders = compressionEnabledHeaders}
                                        fmap decompressResponseBody $ httpLbs req' manager

-- Example usage
main :: IO ()
main = do manager <- newManager conduitManagerSettings -- Create a simple manager
          content <- simpleHttpWithAutoGzip manager "http://stackoverflow.com"
          -- Print the uncompressed content
          print $ content
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/21793393

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档