{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE Rank2Types #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE ScopedTypeVariables #-} module Hack2.Handler.Warp ( run , runWithConfig , runWithWarpSettings , ServerConfig(..) , hackAppToWaiApp ) where import Prelude () import Air.Env hiding (def, Default) import qualified Network.Wai as Wai import Hack2 import Data.Default (def, Default) import qualified Network.HTTP.Types as HTTPTypes import qualified Data.CaseInsensitive as CaseInsensitive import Data.ByteString.Char8 (ByteString, pack) import qualified Data.ByteString.Char8 as B import Data.Enumerator (Enumerator, Iteratee (..), ($$), joinI, run_, Enumeratee, Step, (=$)) import qualified Data.Enumerator.List as EL import Blaze.ByteString.Builder (Builder, fromByteString, fromLazyByteString) import qualified Network.Wai.Handler.Warp as Warp import qualified Safe as Safe {- { requestMethod :: RequestMethod , scriptName :: ByteString , pathInfo :: ByteString , queryString :: ByteString , serverName :: ByteString , serverPort :: Int , httpHeaders :: [(ByteString, ByteString)] , hackVersion :: (Int, Int, Int) , hackUrlScheme :: HackUrlScheme , hackInput :: HackEnumerator , hackErrors :: HackErrors , hackHeaders :: [(ByteString, ByteString)] -} requestToEnv :: Wai.Request -> Env requestToEnv request = def { requestMethod = request.Wai.requestMethod.show.upper.Safe.readDef GET , pathInfo = request.Wai.rawPathInfo , queryString = request.Wai.rawQueryString.B.dropWhile (is '?') , serverName = request.Wai.serverName , serverPort = request.Wai.serverPort , httpHeaders = request.Wai.requestHeaders.map caseInsensitiveHeaderToHeader , hackUrlScheme = if request.Wai.isSecure then HTTPS else HTTP , hackHeaders = [("RemoteHost", request.Wai.remoteHost.show.pack)] } caseInsensitiveHeaderToHeader :: (CaseInsensitive.CI ByteString, ByteString) -> (ByteString, ByteString) caseInsensitiveHeaderToHeader (x, y) = (x.CaseInsensitive.original, y) headerToCaseInsensitiveHeader :: (ByteString, ByteString) -> (CaseInsensitive.CI ByteString, ByteString) headerToCaseInsensitiveHeader (x, y) = (x.CaseInsensitive.mk, y) statusToStatusHeader :: Int -> HTTPTypes.Status statusToStatusHeader 200 = HTTPTypes.status200 statusToStatusHeader 201 = HTTPTypes.status201 statusToStatusHeader 206 = HTTPTypes.status206 statusToStatusHeader 301 = HTTPTypes.status301 statusToStatusHeader 302 = HTTPTypes.status302 statusToStatusHeader 303 = HTTPTypes.status303 statusToStatusHeader 304 = HTTPTypes.status304 statusToStatusHeader 400 = HTTPTypes.status400 statusToStatusHeader 401 = HTTPTypes.status401 statusToStatusHeader 403 = HTTPTypes.status403 statusToStatusHeader 404 = HTTPTypes.status404 statusToStatusHeader 405 = HTTPTypes.status405 statusToStatusHeader 412 = HTTPTypes.status412 statusToStatusHeader 416 = HTTPTypes.status416 statusToStatusHeader 500 = HTTPTypes.status500 statusToStatusHeader 501 = HTTPTypes.status501 statusToStatusHeader _ = HTTPTypes.statusNotImplemented hackAppToWaiApp :: Application -> Wai.Application hackAppToWaiApp app request = do response <- io - app - requestToEnv request let wai_response_enumerator = hackResponseToWaiResponseEnumerator response return - Wai.ResponseEnumerator wai_response_enumerator hackResponseToWaiResponseEnumerator :: (forall a. Response -> Wai.ResponseEnumerator a) hackResponseToWaiResponseEnumerator response f = let s = response.status.statusToStatusHeader h = response.headers.map headerToCaseInsensitiveHeader -- wai response enumerator expect the callback (iteratee) to acts on builder. -- type ResponseEnumerator a = -- (H.Status -> H.ResponseHeaders -> Iteratee Builder IO a) -> IO a server_iteratee :: Iteratee Builder IO a server_iteratee = f s h -- in Builder, fromByteString :: S.ByteString -> Builder -- in Enumerator.List, map :: Monad m => (ao -> ai) -- -> Enumeratee ao ai m b -- type Enumeratee ao ai m b = Step ai m b -> Iteratee ao m (Step ai m b) -- builder_enumeratee :: Enumeratee ByteString Builder IO a builder_enumeratee :: Step Builder IO a -> Iteratee ByteString IO (Step Builder IO a) builder_enumeratee = EL.map fromByteString {- -- make fromByteString act as the preprocesser of server_iteratee, -- by joining -- ($$) :: Monad m -- => (Step a m b -> Iteratee a' m b') -- -> Iteratee a m b -- -> Iteratee a' m b' -- ($$) = (==<<) -- this is similar to :: readFile >>= putStrLn, in a Monad context bytestring_to_builder_layered_iteratee :: Iteratee ByteString IO (Step Builder IO a) bytestring_to_builder_layered_iteratee = builder_enumeratee $$ server_iteratee -- iteratee is needs to be flattened -- joinI :: Monad m => Iteratee a m (Step a' m b) -- -> Iteratee a m b -- joinI outer = outer >>= check where -- check (Continue k) = k EOF >>== \s -> case s of -- Continue _ -> error "joinI: divergent iteratee" -- _ -> check s -- check (Yield x _) = return x -- check (Error e) = throwError e flattened_server_iteratee :: Iteratee ByteString IO a flattened_server_iteratee = joinI bytestring_to_builder_layered_iteratee -} flattened_server_iteratee :: Iteratee ByteString IO a flattened_server_iteratee = builder_enumeratee =$ server_iteratee final_iteratee_taking_input_from_hack_enumerator :: Iteratee ByteString IO a final_iteratee_taking_input_from_hack_enumerator = response.body.unHackEnumerator $$ flattened_server_iteratee in run_ final_iteratee_taking_input_from_hack_enumerator data ServerConfig = ServerConfig { port :: Int } deriving (Show, Eq) instance Default ServerConfig where def = ServerConfig { port = 3000 } runWithWarpSettings :: Warp.Settings -> Application -> IO () runWithWarpSettings setting app = do Warp.runSettings setting (hackAppToWaiApp app) runWithConfig :: ServerConfig -> Application -> IO () runWithConfig config app = let setting = Warp.defaultSettings {Warp.settingsPort = config.port} in runWithWarpSettings setting app run :: Application -> IO () run = runWithConfig def