我刚开始学习Haskell。下面是一些以强制性风格编写的代码,它实现了一个简单的服务器 - 它打印出HTTP请求头。除了我需要在Haskell中重新思考它,使用惰性列表和更高阶函数之外,我还想清楚地看到它为什么不能按照我的意图行事。它总是一个落后 - 我用一个请求命中它,没有任何反应,再次点击它,它打印第一个请求,第三次点击它,它打印第二个请求,等等。为什么?什么是对此代码的最小更改,以便在请求进入时立即打印?
import Network
import System.IO
import Network.HTTP.Headers
acceptLoop :: Socket -> IO ()
acceptLoop s = do
(handle, hostname, _) <- accept s
putStrLn ("Accepted connection from " ++ hostname)
text <- hGetContents handle
let lns = lines text
hds = tail lns
print $ parseHeaders hds
hClose handle
acceptLoop s
main :: IO ()
main = do
s <- listenOn (PortNumber 8080)
acceptLoop s
感谢, 罗布
所有答案都有帮助。下面的代码可以工作,但不会像建议的那样使用字节串。后续问题:可以使用标准库中的某些函数替换ioTakeWhile
,可能在Control.Monad中吗?
ioTakeWhile :: (a -> Bool) -> [IO a] -> IO [a]
ioTakeWhile pred actions = do
x <- head actions
if pred x
then (ioTakeWhile pred (tail actions)) >>= \xs -> return (x:xs)
else return []
acceptLoop :: Socket -> IO ()
acceptLoop s = do
(handle, hostname, _) <- accept s
putStrLn ("Accepted connection from " ++ hostname)
let lineActions = repeat (hGetLine handle)
lines <- ioTakeWhile (/= "\r") lineActions
print lines
hClose handle
答案 0 :(得分:10)
您的问题是使用hGetContents
将获取句柄上的所有内容,直到套接字关闭。您可以通过尝试解析输入的最后一行来跟随此调用,直到连接终止时才会知道该行。
解决方案:获取所需数据(或可用数据),然后终止连接。
已经很晚了,我已经累了但这里有一个解决方案,我知道它不是最优的(读作:丑陋的罪):你可以转到字节串(无论如何应该这样做)并使用hGetNonBlocking或{{1而不是hGetSome
。或者,您可以hGetContents
(阻止),直到解析成功满足您的要求:
hGetLine
答案 1 :(得分:6)
懒惰程序中的“控制流程”与您习惯的不同。事情将不会被评估,直到它们为什么你的程序始终是输出后面的请求。
一般情况下,您可以使用“bang”运算符!
和BangPatterns
pragma来制定严格的内容。
如果您在这种情况下使用它(通过说!text <- hGetContents handle
),一旦请求完成,您将获得标题的输出。遗憾的是,hGetContents
不知道何时在print
语句之前停止等待更多数据,因为handle
未关闭。
如果您另外重构程序,使 在另一种情况下, 请注意,简单地使 进行了三项更改:我在 要完全回避这类问题,您可以尝试使用 我发现以下 执行此操作,您可以从 TomMD的解决方案使用hClose handle
{/ 1}}语句和let
之前的print
,那么程序的行为就像您想要的那样。< / p>
print
未被评估,因为text
的值在handle
关闭时永远不会“完成”。由于它是“懒惰”,print
正等待hds
和lns
,而text
又等待hClose
,等待hClose
。这就是为什么你得到了奇怪的行为;在下一个请求需要套接字之前,text
未被评估,这就是为什么在此之前没有输出。text
严格仍然会永久阻止程序,让它“等待”文件关闭。但是,如果文件在{-# LANGUAGE BangPatterns #-}
非严格时关闭,则它将始终为空,并导致错误。同时使用它们将获得所需的效果。
您的程序包含建议的更改:
!
前面添加了text
个pragma,一个字符(hClose handle
),并将{-# LANGUAGE BangPatterns #-}
import Network
import System.IO
import Network.HTTP.Headers
acceptLoop :: Socket -> IO ()
acceptLoop s = do
(handle, hostname, _) <- accept s
putStrLn ("Accepted connection from " ++ hostname)
!text <- hGetContents handle
hClose handle
let lns = lines text
hds = tail lns
print $ parseHeaders hds
acceptLoop s
main :: IO ()
main = do
s <- listenOn (PortNumber 8080)
acceptLoop s
向上移了几行。< / p>
hGetContents
另一种方法:
System.IO.Strict
模块中的System.IO
功能代替acceptLoop
。
最后一点:
main
更加惯用,而不是main = do
s <- listenOn (PortNumber 8080)
sequence_ $ repeat $ acceptLoop s
中的显式递归:acceptLoop
forever
删除递归调用。Contol.Monad
模块中的{{1}},这也很好。
答案 2 :(得分:3)
您应该对消息何时完成有一些概念。您需要从片段中的输入句柄中读取,直到您意识到您已收到完整的消息。然后假设之后的所有内容都是下一条消息。消息可能不会立即出现,也可能成组出来。
例如,消息可能始终是固定长度。或以\n\n
终止(我相信这是HTTP请求的情况)
[我可能会回来并发布代码以获得此建议,但如果我不这样做,只需尝试调整TomMD的代码,这是朝着正确方向迈出的一步]