【问题标题】:Basic `listenOn` HTTP server "Recv failure: Connection reset by peer"基本`listenOn` HTTP服务器“接收失败:对等方重置连接”
【发布时间】:2016-03-20 12:59:48
【问题描述】:

在深入研究 Haskell 的 Network 库时,我正在根据来自 this link 的信息制作一个非常简单的 HTTP 服务器。

import Control.Concurrent
import Control.Monad
import Network
import System.IO

main = withSocketsDo $ listenOn (PortNumber 8080) >>= loop

loop :: Socket -> IO ()
loop sock = do
  (h,_,_) <- accept sock
  forkIO $ handleRequest h
  loop sock

handleRequest :: Handle -> IO ()
handleRequest h = do
  hPutStr h $ httpRequest "Pong!\n"
  hFlush h
  hClose h

httpRequest :: String -> String
httpRequest body = "HTTP/1.0 200 OK\r\n"
  ++ "Content-Length: " ++ (show.length) body ++ "\r\n"
  ++ "\r\n" ++ body ++ "\r\n"

但是,即使我设法得到了一些响应,手柄似乎很快就会意外关闭(有时?),因为 curl 告诉我:

$ curl localhost:8080
Pong!
curl: (56) Recv failure: Connection reset by peer

注意:有时我什至没有收到消息 (Pong!) 或者只是其中的一部分。有时,它可以工作......但如果我连续运行 100 个curls,我最终会重置一些连接。

为什么会重置连接?我尝试使用和不使用forkIO 都没有成功。我错过了一些关于 Haskell 中 IO 流的基本信息吗?谢谢!

操作系统:最近的 Ubuntu; GHC:7.8.4

--- 编辑:---

jozefg 发现问题出在请求内容的耗尽上!但是,我想将此内容发送回客户端,但在使用以下代码时它会挂起:

handleRequest :: Handle -> IO ()
handleRequest h = do
  contents <- getHandleContents h
  hPutStr h $ httpRequest contents
  hFlush h
  hClose h

getHandleContents :: Handle -> IO String
getHandleContents h = do
  iseof <- hIsEOF h
  if iseof
    then return []
    else do
      newLine <- hGetLine h
      nextLines <- getHandleContents h
      return $ newLine ++ '\n' : nextLines

此外,我没有成功使用hGetContents 排出全部内容。知道为什么吗?

【问题讨论】:

  • 回答我在编辑中的附加问题:直到 EOF 我才能尝试阅读,因为关闭连接是服务器的角色(因此,我们只会等待)。相反,如果客户端指定 Content-Length 标头,我们将读取指定的字节数以获取正文。这也解释了为什么hGetContents会挂起程序。
  • 你在写完所有东西之前就关闭了句柄。当您关闭句柄时,您也关闭了连接。删除 hClose h 使它在我的机器上 100% 的工作。 在实践中的解决方案是使用更高级别的 http 服务器库并让它担心这些事情。

标签: http haskell


【解决方案1】:

错误似乎是您没有完全读取客户端在发出 get-request 时发送的数据,如answer for Rust 中所述。提出的解决方案基本上是编写一个小循环,在您响应之前将标题从句柄中排出。 Haskell 版本是

drainHeaders :: Handle -> IO ()
drainHeaders h = do
  line <- hGetLine h
  if line == "\r" then return () else drainHeaders h

那么你的代码就可以写出来了

import Control.Concurrent
import Control.Exception (bracket)
import Control.Monad
import Network
import System.IO

main = withSocketsDo $
  bracket (listenOn (PortNumber 8080)) sClose loop

loop :: Socket -> IO ()
loop sock = do
  (handle, _host, _port) <- accept sock
  -- Handle is automatically closed now even in the face of async exns
  forkFinally (handleRequest handle) (const $ hClose handle)
  loop sock

drainHeaders :: Handle -> IO ()
drainHeaders h = do
  line <- hGetLine h -- Strips off a trailing \n
  if line == "\r" then return () else drainHeaders h

handleRequest :: Handle -> IO ()
handleRequest h = do
  drainHeaders h
  hPutStr h $ httpRequest "Pong!\n"
  hFlush h

httpRequest :: String -> String
httpRequest body =
  mconcat [ "HTTP/1.0 200 OK\r\nContent-Length: "
          , (show . length) body
          , "\r\n\r\n"
          , body
          , "\r\n" ]

我还冒昧地调整了代码,通过使用forkFinallybracket 来处理在面对异常时关闭的东西:我怀疑它是否 100% 完美,但现在有点更干净一点。

【讨论】:

  • 感谢这些不错的改进!所以drainHeaders 完成了这项工作,这对我来说是个好消息 :) 但我最初的目的实际上是将请求的内容发送回客户端。我将在问题中粘贴我用来读取句柄的代码。问题是我无法完成对句柄的读取,并且当我终止程序时,所有内容都打印得很混乱。
  • @blint 您不想采用当前正在执行的方法,因为套接字最初不会到达 eof:相反,用户将显式结束当前消息但保持句柄打开以便进一步沟通。这就是为什么我的代码显式检查仅包含\r\n 的行的原因。为了在您的代码中解决此问题,我认为您可以调整我的代码以将 line 值累积到列表中并在最后调用 unlines。 @blint
  • 我怀疑你可能会因为一些涉及惰性 IO 的可怕事情而出现这种交错行为,但我不确定
  • 我确实没有。在检查了the specs of HTTP 之后,我终于明白了原因:只有在提到内容长度时才应该读取正文。我想尝试阅读所有内容会使连接保持打开状态,直到超时。再次感谢,我会好好利用你的精确度!
猜你喜欢
  • 2018-02-19
  • 2013-02-12
  • 1970-01-01
  • 2013-08-25
  • 1970-01-01
  • 2020-02-10
  • 2013-01-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多