Трубы и ответвления 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
если у вас есть более двух ветвей, то вам придется гнездо Either
s, но это просто ограничение того, как ArrowChoice
строительство.
Я не запускал ваш код, но я думаю, что заметил проблему.
routeRequest'' = runProxyK $ routeRequest''' <-< unitU
routeRequest'''
запрашивает данные от unitU, которому нечего предоставить, поэтому он зависает.
:t runProxy $ unitU >-> printD
будет вводить check, но ничего не работает.
похоже, что данные отправляются на неправильный уровень трансформатора монады, данные, которые текут в routeRequest
должно течь в routeRequest'''
. Данные, поступающие на неправильный уровень трансформатора монады, - это то, что, вероятно в результате вам нужно оставить подпись типа, чтобы получить все, чтобы ввести проверку. С подписью типа routeRequest
ждет ()
идя вверх по течению и, держу пари, без подписи типа разрешено быть полиморфным.
в определении routeRequest
вы можете "закрыть трубу", я думаю, что это то, что называется, используя unitD, который запретит вашу конструкцию, даже когда routeRequest'''
не имеет подписи типа.