Rob*_*b N 14 io haskell lazy-evaluation
我刚开始学习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
Run Code Online (Sandbox Code Playgroud)
谢谢,罗布
所有的答案都很有帮助.下面的代码可以工作,但不会像建议的那样使用字节串.后续问题:可以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
Run Code Online (Sandbox Code Playgroud)
Tho*_*son 10
您的问题是使用hGetContents将获取句柄上的所有内容,直到套接字关闭.您可以通过尝试解析输入的最后一行来跟随此调用,直到连接终止时才会知道该行.
解决方案:获取所需数据(或可用数据),然后终止连接.
这晚,我累了,但这里是我知道的解决方案不是最优的(读:丑陋的罪过):您可以移动到字节串(应该这样做无论如何),并使用hGetNonBlocking或hGetSome代替hGetContents.或者,您可以hGetLine(阻止)持续,直到解析成功满足您:
import Network
import System.IO
import Network.HTTP.Headers
import Control.Monad
import qualified Data.ByteString.Char8 as B
import Data.ByteString (hGetSome)
acceptLoop :: Socket -> IO ()
acceptLoop s = do
(handle, hostname, _) <- accept s
putStrLn ("Accepted connection from " ++ hostname)
printHeaders handle B.empty
hClose handle
where
printHeaders h s = do
t <- hGetSome h 4096
let str = B.append s t -- inefficient!
loop = printHeaders h str
case (parseHeaders . tail . lines) (B.unpack str) of
Left _ -> loop
Right x
| length x < 3 -> loop
| otherwise -> print x
main :: IO ()
main = do
hSetBuffering stdin NoBuffering
s <- listenOn (PortNumber 8080)
forever $ acceptLoop s
Run Code Online (Sandbox Code Playgroud)
懒惰程序中的"控制流"与您习惯的不同.事情不会被评估,直到它们为什么你的程序始终是输出后面的请求.
一般来说,你可以使用"bang"运算符!和BangPatternspragma 来制作严格的东西.
如果您在这种情况下使用它(通过说!text <- hGetContents handle),一旦请求完成,您将获得标题的输出.不幸的是,hGetContents不知道何时在print声明之前停止等待更多数据,因为handle没有关闭.
如果您另外重构程序以使其具有hClose handle 前两个let语句print,那么程序的行为就像您想要的那样.
在另一种情况下,print不进行评估,因为text关闭时永远不会"完成" handle.因为它是"懒",print然后等待hds和lns,后者又等待text,它在等待hClose......这就是为什么你越来越怪异的行为; hClose直到下一个请求需要套接字才被评估,这就是为什么在此之前没有输出.
请注意,只是make textstrict仍会永远阻止程序,让它"等待"文件关闭.但是,如果文件在text非严格时关闭,它将始终为空,并导致错误.同时使用它们将获得所需的效果.
进行了三项更改:我添加了{-# LANGUAGE BangPatterns #-}编译指示,!前面有一个单个字符()text,并向上移动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
Run Code Online (Sandbox Code Playgroud)
要完全回避这样的问题,您可以尝试使用模块中的hGetContents函数System.IO.Strict而不是System.IO.
而不是显式递归acceptLoop,我发现以下main是更惯用的:
main = do
s <- listenOn (PortNumber 8080)
sequence_ $ repeat $ acceptLoop s
Run Code Online (Sandbox Code Playgroud)
这样做,您可以从中删除递归调用acceptLoop.
TomMD的解决方案使用forever了该Contol.Monad模块,这也很好.
| 归档时间: |
|
| 查看次数: |
3445 次 |
| 最近记录: |