为流发送错误长度时 HTTP 管理器共享状态损坏

HTTP manager shared state corruption when sending wrong length for stream

给定一个共享的 HTTP 管理器,似乎如果 requestBody 是类型 requestBodySource 并且如果为请求正文提供了错误的长度,那么后续请求将在同一个 HTTP 管理器上崩溃约 20 秒。似乎与共享状态的交互有关,GivesPopper 可能是导致此问题的原因。这是一个重现它的示例代码 - 我们使用 requestb.in 发送错误长度的上传,然后尝试在 requestb.in 上读取另一个有效的 URL。

{-# LANGUAGE OverloadedStrings #-}

import           Data.Conduit.Binary (sourceFile)
import           Network.HTTP.Conduit 
import           Network.HTTP.Types
import qualified Data.ByteString.Lazy as LBS
import System.IO
import Control.Monad.Trans.Resource (runResourceT)
import Control.Concurrent.Async (async,waitCatch)
import Control.Exception (displayException)

main :: IO ()
main = do
  {- Set up a ResourceT region with an available HTTP manager. -}
  httpmgr <- newManager tlsManagerSettings
  httpmgr2 <- newManager tlsManagerSettings
  let file ="out" -- some byte contents with length > 1
  lenb <- System.IO.withFile file ReadMode hFileSize
  let inbytes = sourceFile file 
  initReq <- parseUrl "http://requestb.in/saxbx3sa"
  putreq <- async $ runResourceT $ do
    let req = initReq { method = "POST",
      -- let us send wrong length in requestBodySource
      requestBody = (requestBodySource (fromIntegral $ lenb - 1) inbytes)}
    resp <- httpLbs req httpmgr 
    return (statusCode . responseStatus $ resp)
  putreqRes <- waitCatch putreq
  case putreqRes of
    Left e -> print $ displayException $ e
    Right r -> print $ r
  getreq <- async $ runResourceT $ do
    -- Let us do a GET on a different resource to see if it works
    initReq <- parseUrl "http://requestb.in/1l15sz21"
    let req = initReq { method = "GET"}
    resp <- httpLbs req httpmgr 
    return (statusCode . responseStatus $ resp)
  getreqRes <- waitCatch getreq
  case getreqRes of
    Left e -> print $ displayException $ e
    Right r -> print $ r

输出 - 第一个错误的上传作为 HTTP 200 完成,随后的 GET 请求立即导致 HTTP 400 错误:

 *Main> main
    200
    "StatusCodeException (Status {statusCode = 400, statusMessage = \"Bad Request\"})
 [(\"Date\",\"Wed, 29 Jun 2016 11:54:59 GMT\"),(\"Content-Type\",\"text/html\"),
(\"Content-Length\",\"177\"),(\"Connection\",\"close\"),(\"Server\",\"-nginx\"),
(\"CF-RAY\",\"-\"),(\"X-Response-Body-Start\",\"<html>\r\n<head><title>400 Bad 
Request</title></head>\r\n<body bgcolor=\\"white\\">\r\n<center><h1>400 Bad 
Request</h1></center>\r\n<hr><center>cloudflare-
nginx</center>\r\n</body>\r\n</html>\r\n\"),(\"X-Request-URL\",\"GET 
http://requestb.in:80/saxbx3sa\")] (CJ {expose = []})"

GET 请求使用不同的 http 管理器将 return HTTP 200。因此,http 管理器中的共享状态似乎是这里的问题。

有没有人观察到它?我经历了 HTTP Manager 的 github 个问题,但还没有看到这方面的报道。在错误的流媒体长度上,行为不应该像这里发生的那样破坏 HTTP 管理器。

我还为 requestBodySource 模拟了一个源文件,其中长度是正确的,但由于模拟故障(模拟网络问题),源中途中止。在这种情况下,没有错误。因此,似乎我们只有一种情况,即在没有任何失败的情况下发送错误的长度会导致某种共享状态在这里损坏,并在 25 秒内被释放。

如果有人对这里发生的事情有任何见解,那将非常有帮助。我有一个强制执行正确的流媒体长度的解决方法。但是,我想了解发生了什么,这样我就可以避免 运行 在生产中出现这种情况。

这是 http-client as reported here 的问题。它留给调用者来确保传递的内容长度是正确的。它是与服务器的共享连接,似乎处于错误状态。根据实际长度与预期长度,下一个请求的开头可能会被视为上一个请求主体的结尾,从而导致下一个请求被服务器误解。

这已通过 pull request 修复并合并到主干中。解决方案是添加一个简单的长度验证。