Трубы и ответвления Haskell
я пытаюсь реализовать простой веб-сервер с Haskell и библиотеки трубы. Теперь я понимаю, что циклические или алмазные топологии невозможны с трубами, однако я думал, что я пытаюсь это сделать. Моя желаемая топология такова:
-GET--> handleGET >-> packRequest >-> socketWriteD
|
socketReadS >-> parseRequest >-routeRequest
|
-POST-> handlePOST >-> packRequest >-> socketWriteD
я HTTPRequest RequestLine Headers Message и HTTPResponse StatusLine Headers Message типы, которые используются в цепочке. socketReadS берет байты из сокета и пересылает их в parseRequest, который использует Attoparsec для анализа байтов в HTTPRequest объект. Затем я хотел бы, чтобы канал разветвлялся по крайней мере дважды и, возможно, больше в зависимости от того, сколько HTTP-методов я реализую. Каждый handle<method> функция должна получать HTTPRequest объекты вверх и вперед HTTPResponse объекты packRequest, который просто упаковывает объекты HTTPResponse в ByteString готов к отправке с socketWriteS.
следующий код typechecks, если я позволю GHC вывести тип для routeRequest''' (мой, кажется, немного не то). Однако ничего не кажется выполнение после parseRequest. Кто-нибудь может помочь мне понять почему?
код
у меня есть следующий код routeRequest который должен обрабатывать ветвление.
routeRequest''' ::
(Monad m, Proxy p1, Proxy p2, Proxy p3)
=> () -> Consumer p1 HTTPRequest (Pipe p2 HTTPRequest HTTPRequest (Pipe p3 HTTPRequest HTTPRequest m)) r
routeRequest''' () = runIdentityP . hoist (runIdentityP . hoist runIdentityP) $ forever $ do
httpReq <- request ()
let method = getMethod httpReq
let (URI uri) = getURI httpReq
case method of
GET -> lift $ respond httpReq
POST -> lift $ lift $ respond httpReq
routeRequest'' = runProxyK $ routeRequest''' <-< unitU
routeRequest' socket = runProxyK $ raiseK (p4 socket <-< handleGET) <-< routeRequest''
routeRequest socket = (p4 socket <-< handlePOST) <-< (routeRequest' socket)
handleGET и handlePOST реализованы как таковые:
handleGET :: Proxy p => () -> p () HTTPRequest r ByteString IO r
handleGET () = runIdentityP $ do
httpReq <- request ()
let (URI uri) = getURI httpReq
lift $ Prelude.putStrLn "GET"
respond $ B.append (B.pack "GET ") uri
handlePOST :: Proxy p => () -> p () HTTPRequest r ByteString IO r
handlePOST () = runIdentityP $ do
httpReq <- request ()
let (URI uri) = getURI httpReq
lift $ Prelude.putStrLn "POST"
respond $ B.append (B.pack "POST ") uri
у меня есть следующие сокращения для прокси:
p1 socket = socketReadS 32 socket
p2 = parseRequestProxy
p4 socket = socketWriteD socket
наконец, я запускаю все это так:
main = serveFork (Host "127.0.0.1") "8080" $
(socket, remoteAddr) -> do
ret <- runProxy $ runEitherK $ p1 socket >-> printD >-> p2 >-> printD >-> routeRequest socket
Prelude.putStrLn $ show ret
тип подписи parseRequestProxy is это:
parseRequestProxy
:: (Monad m, Proxy p) =>
()
-> Pipe
(EitherP Control.Proxy.Attoparsec.Types.BadInput p)
ByteString
HTTPRequest
m
r
редактировать
вот репозиторий с исходным кодом. Будьте осторожны, он не был приукрашен так использовать на свой страх и риск. https://bitbucket.org/Dwilson1234/haskell-web-server/overview
2 ответов
я был неправ, когда изначально сказал, что вы не можете справиться с алмазными топологиями. Позже я обнаружил разумный способ сделать это, используя ArrowChoice - подобный интерфейс и включил решение в pipes-3.2.0 в форме leftD и rightD комбинаторы. Я объясню, как это работает:
вместо вложенности прокси-трансформаторов вы обертываете результат с помощью Left или Right
routeRequest ::
(Monad m, Proxy p)
=> () -> Pipe p HTTPRequest (Either HTTPRequest HTTPRequest) m r
routeRequest () = runIdentityP $ forever $ do
httpReq <- request ()
let method = getMethod httpReq
let (URI uri) = getURI httpReq
respond $ case method of
GET -> Left httpReq
POST -> Right httpReq
затем вы можете выборочно применить каждый обработчик к каждой ветви, а затем объединить ветви:
routeRequest >-> leftD handleGET >-> rightD handlePOST >-> mapD (either id id)
:: (Monad m, Proxy p) => () -> Pipe p HTTPRequest ByteString IO r
если у вас есть более двух ветвей, то вам придется гнездо Eithers, но это просто ограничение того, как ArrowChoice строительство.
Я не запускал ваш код, но я думаю, что заметил проблему.
routeRequest'' = runProxyK $ routeRequest''' <-< unitU
routeRequest''' запрашивает данные от unitU, которому нечего предоставить, поэтому он зависает.
:t runProxy $ unitU >-> printD
будет вводить check, но ничего не работает.
похоже, что данные отправляются на неправильный уровень трансформатора монады, данные, которые текут в routeRequest должно течь в routeRequest'''. Данные, поступающие на неправильный уровень трансформатора монады, - это то, что, вероятно в результате вам нужно оставить подпись типа, чтобы получить все, чтобы ввести проверку. С подписью типа routeRequest ждет () идя вверх по течению и, держу пари, без подписи типа разрешено быть полиморфным.
в определении routeRequest вы можете "закрыть трубу", я думаю, что это то, что называется, используя unitD, который запретит вашу конструкцию, даже когда routeRequest''' не имеет подписи типа.