Проблемы с монадой IO при попытке внедрить WAI HTTP Server + Fallback Proxy

Что я пытаюсь сделать, так это создать несколько умный обратный прокси-сервер, который должен обрабатывать некоторые запросы самостоятельно и перенаправлять другие на выбранный сервер. Чтобы сделать это сложным, я изо всех сил стараюсь сделать это на Haskell, в котором я новичок.

Вот код, который я придумал до сих пор:

{-# LANGUAGE OverloadedStrings #-}

import Control.Applicative
import Data.ByteString
import Network.HTTP.ReverseProxy
import Network.HTTP.Types
import Network.Wai
import Network.Wai.Handler.Warp
import Network.Wai.Middleware.RequestLogger
import qualified Network.HTTP.Client as HC

helloApp :: Application
helloApp req respond =
  respond $ responseLBS status200 [("Content-Type", "text/plain")] "Hello"

proxyStubApp :: Application
proxyStubApp req respond =
  respond $ responseLBS status200 [("Content-Type", "text/plain")] "You've hit the stub"

proxyApp :: IO Application
proxyApp = do
  manager <- HC.newManager HC.defaultManagerSettings
  return $ waiProxyTo (const $ return $ WPRProxyDest ProxyDest { pdHost = "localhost", pdPort = 9393 }) defaultOnExc manager

app :: Application
app req respond =
  serve req respond
    where serve = lookupServeFunction req

lookupServeFunction :: Request -> Application
lookupServeFunction req
  | isInfixOf "sample_path" (rawPathInfo req) = proxyStubApp
  | otherwise                                 = helloApp

main = run 3011 =<< (logStdoutDev <$> return app)

Это работает нормально, но когда я меняю proxyStubApp на фактическое proxyApp, я вынужден добавлять IO повсюду. В частности, он добавляется к app, в результате чего у меня появляется следующее сообщение об ошибке компиляции:

Couldn't match expected type ‘Request -> t5 -> t4’
            with actual type ‘IO Application’
The equation(s) for ‘app’ have two arguments,
but its type ‘IO Application’ has none

Кажется, я понимаю, почему это происходит, но у меня нет идей, как с этим справиться :( Или я делаю что-то совершенно не так?

Благодарю вас!

P.S. Вот зависимости, если вы хотите скомпилировать это самостоятельно: wai warp http-types text bytestring wai-extra time http-reverse-proxy http-client


person SkyWriter    schedule 28.12.2015    source источник


Ответы (1)


IO в IO Application несколько избыточно. Обратите внимание, что

type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived

поэтому, расширяя аргументы proxyApp (то, что вы уже делаете в proxyStubApp), вы получаете

proxyApp :: Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived
proxyApp req continuation = do
     manager <- HC.newManager HC.defaultManagerSettings
     waiProxyTo (...) req respond

Это работает, потому что в любом случае

proxyApp :: IO Application
proxyApp = do
   manager <- HC.newManager ...
   ...        

и

proxyApp :: Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived
proxyApp req continuation = do
   manager <- HC.newManager ...
   ...

действие ввода-вывода HC.newManager ... "выполняется в пределах IO".

Вы можете найти концептуально более понятным построить Application в IO и передать его в какое-то другое место, и я не буду с вами спорить. Однако я хочу отметить, что вы выбираете Application на основе Request, так что в каком-то смысле вы находитесь в гипотетической монаде HTTP при выборе, поэтому подпись lookupServeFunction Request -> Application имеет для меня больше смысла.

Если вы хотите сохранить подпись этого типа для proxyApp, lookupServeFunction и app также должны быть в IO, а main придется изменить соответствующим образом, например.

myApp <- app
...

Как сказал haoformayor, вообще легче работать без внешнего IO слоя.


Вы также можете упростить main.

fmap logStdoutDev (return app)

такой же как

return (logStdoutDev app)

и запустите 3011 =‹‹ return (приложение logStdoutDev)

такой же как

run 3011 (logStdoutDev app)

Возможно, вы захотите установить hlint, который поможет вам их обнаружить.

person ibotty    schedule 28.12.2015
comment
Я вижу, как IO Application может иметь смысл. Представьте, это Application, которое возвращается в результате действия ввода-вывода, что очень похоже на мой случай: waiProxyTo здесь своего рода генератор приложений, который зависит от manager, который, в свою очередь, каким-то образом является результатом ввода-вывода. Разве это не так? - person SkyWriter; 28.12.2015
comment
Я думаю, что суть в том, что a -> IO b изоморфен IO (a -> IO b). Любое значение одного из этих типов может быть преобразовано в другой. Если у вас есть wibble :: a -> IO b и wobble :: IO (a -> IO b), return wibble поможет вам двигаться вперед, а (wobble >>=) . flip id — назад. (И из этих двух a -> IO b обычно легче работать, например, в таких ситуациях, как эта, хотя это субъективное предпочтение. Я говорю, что чем меньше монадических одеял вам нужно вылезти, чтобы выглянуть из головы, тем лучше.) - person hao; 28.12.2015
comment
@haoformayor, значит ли это, что proxyApp :: IO Application действительно должно совпадать с proxyApp :: Application? Удаление IO расстраивает компилятор в строке, где я вызываю HC.newManager и связываю его со следующим сообщением Couldn't match type ‘IO b0’ with ‘Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived’. Expected type: IO HC.Manager -> (HC.Manager -> IO b0) -> Application. Actual type: IO HC.Manager -> (HC.Manager -> IO b0) -> IO b0... Интересно, сколько времени потребуется, чтобы начать понимать это :-) - person SkyWriter; 28.12.2015
comment
Возможно, мой ответ неясен. Я попытаюсь прояснить это через мгновение. - person ibotty; 28.12.2015