【发布时间】:2018-07-15 22:05:50
【问题描述】:
我正在尝试用 Haskell 编写一个非常基本的网络服务器。这是我的代码:
{-# LANGUAGE OverloadedStrings #-}
import Network (withSocketsDo, listenOn, PortID(..))
import Network.Socket (Socket, accept, close, setSocketOption, SocketOption(..))
import Network.Socket.ByteString (send, sendAll, recv)
import Control.Concurrent.Async (async)
import Control.Monad (forever)
import Data.ByteString.Char8 (unpack)
import Request
main = withSocketsDo $ do
sock <- listenOn $ PortNumber 3000
putStrLn "Listening on port 3000..."
forever $ do
(conn, _) <- accept sock
async $ handleAccept conn
handleAccept :: Socket -> IO ()
handleAccept sock = do
putStrLn $ "Connected!"
rawReq <- recv sock 4096
let req = parseRawRequest $ unpack rawReq -- returns Maybe Request
putStrLn $ show req
handleRequest sock req
handleRequest :: Socket -> Maybe Request -> IO ()
handleRequest sock Nothing = do
putStrLn "Closing..."
handleRequest sock req = do
sendAll sock "In handleRequest!" -- Doesn't appear until server is killed.
这是我预期会发生的:
- 启动服务器。
-
"Listening on port 3000..."打印在服务器端。 - 做
curl localhost:3000 -
"Connected!"在服务器端打印。 - 请求在服务器端打印。
-
"In handleRequest!"已打印。
实际会发生什么:
- 启动服务器。
-
"Listening on port 3000..."打印在服务器端。 - 做
curl localhost:3000 -
"Connected!"在服务器端打印。 - 请求在服务器端打印。
- 我耐心等待
- 我用
CTRL+C杀死了服务器 -
"In handleRequest!"打印客户端。
我怀疑这与recv 中可能的懒惰有关,尽管我随后立即使用该值(我将原始请求解析为Request 类型),因此理论上应该对其进行评估。
如果我将sendAll sock "Yadda yadda 放在handleAccept 的末尾,一切正常。当我将此行为转移到一个新函数handleRequest 时,事情就变得很奇怪了。
有什么想法吗?我是 Haskell 的新手,所以我很感激任何关于这个问题的 cmets,或者我的代码。
干杯。
编辑:
这太奇怪了!我“修复”了它,但我不知道为什么会这样。
这是我杀死服务器后才出现的那一行:
handleRequest sock req = do
sendAll sock "In handleRequest!" -- Doesn't appear until server is killed.
如果我在发送后有意关闭套接字,它会起作用:
handleRequest sock req = do
sendAll sock "In handleRequest!" -- Now appears without killing the server
close sock
所以它会在连接关闭时发送。这与之前的行为一致,因为连接会在服务器被终止时自动关闭。
现在是令人困惑的部分。如果我将其替换为:
handleRequest sock req = do
sendAll sock "In handleRequest!\n" -- Works perfect
这可以在不关闭连接的情况下工作!它符合我的预期,只需添加一个换行符。为什么会出现这种情况?
到底是什么?这是我的终端的打印问题,而不是代码? (OSX iTerm2)
编辑 2:
被要求提供我的Request 模块的代码:
import Data.List (isInfixOf)
import Data.List.Split (splitOn)
data RequestType = GET | PUT
deriving Show
data Request =
Request {
reqType :: RequestType,
path :: String,
options :: [(String, String)]
} deriving Show
-- Turn a raw HTTP request into a request
-- object.
parseRawRequest :: String -> Maybe Request
parseRawRequest rawReq =
Request <$> parseRawRequestType rawReq
<*> parseRawRequestPath rawReq
<*> parseRawRequestOps rawReq
-- Turn an (entire) raw HTTP request into just
-- the request type.
parseRawRequestType :: String -> Maybe RequestType
parseRawRequestType rawReq =
case typ of
"GET" -> Just GET
"PUT" -> Just PUT
_ -> Nothing
where typ = (head . words . head . lines) rawReq
-- Turn an (entire) raw HTTP request into just
-- the path.
parseRawRequestPath :: String -> Maybe String
parseRawRequestPath = Just . (!! 1) . words . head . lines
-- Turn an (entire) raw HTTP request into just
-- a lookup table of their options.
parseRawRequestOps :: String -> Maybe [(String, String)]
parseRawRequestOps rawReq = Just [("One", "Two")] -- Test impl
【问题讨论】:
-
我对 Hashell 的线索完全为零,但很明显它正在缓冲套接字输出,您需要在尝试读取响应之前刷新它。
-
能否包含请求导入的代码或让我们知道包的名称?
-
@therewillbecode 完成,虽然如果问题出在哪里我会感到惊讶,但谁知道呢!
-
@haz 包含所有代码不仅仅是关于错误在哪里,而是让那些试图帮助运行测试和实验的人无需重新创建代码。跨度>
标签: sockets haskell networking