Краткий обзор подхода:
«Поток управления» в ленивых программах отличается от того, к которому вы привыкли.Вещи не будут оцениваться до тех пор, пока они не будут выполнены, поэтому ваша программа всегда выполняет запрос с выводом.
В общем, вы можете сделать что-то строгое, используя оператор "bang" !
иBangPatterns
pragma.
Если вы используете его в этом случае (говоря !text <- hGetContents handle
), вы получите вывод заголовков, как только запрос будет завершен.К сожалению, hGetContents
не знает, когда прекратить ждать больше данных перед оператором print
, потому что handle
не закрыто.
Если вы дополнительно реструктурируете программу так, чтобы hClose handle
перед и оператором let
, и print
, то программа ведет себя так, как вы хотите.
В другом случае print
не оценивается, так как значение text
никогда не «завершается» при закрытии handle
.Так как это «ленивый», то print
затем ожидает hds
и lns
, которые, в свою очередь, ожидают text
, который ожидает hClose
... вот почему вы получили странное поведение;hClose
не проверялось до тех пор, пока сокет не потребовался для следующего запроса, поэтому до этого не было никакого вывода.
Обратите внимание, что простое строгое text
будет по-прежнему блокировать программу навсегда, оставляя ее"ждет", чтобы файл закрылся.Тем не менее, если файл закрыт, когда text
не является строгим, он всегда будет пустым и приведет к ошибке.Использование обоих вместе даст желаемый эффект.
Ваша программа с предлагаемыми изменениями:
Было сделано три изменения: я добавил прагму {-# LANGUAGE BangPatterns #-}
, один символ (* 1041)*) перед 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
Альтернативный подход:
Чтобы вообще избежать подобных проблем, вы можетепопробуйте использовать функцию hGetContents
из модуля System.IO.Strict
вместо System.IO
.
Последнее замечание:
Вместо явной рекурсии в acceptLoop
, я нахожу следующее main
более идиоматичным:
main = do
s <- listenOn (PortNumber 8080)
sequence_ $ repeat $ acceptLoop s
Делая это, вы можете удалить рекурсивный вызов из acceptLoop
.
Решение TomMD использует forever
из модуля Contol.Monad
, что тоже хорошо.