{-# LANGUAGE GADTs #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Symantic.HTTP.Server where
import Control.Arrow (first)
import Control.Applicative (Applicative(..))
import Control.Monad (Monad(..), unless, sequence, guard, (=<<))
import Control.Monad.Trans.Class (MonadTrans(..))
import Control.Monad.Trans.Except (ExceptT(..), runExceptT)
import Data.Bool
import Data.Either (Either(..))
import Data.Eq (Eq(..))
import Data.Function (($), (.), id)
import Data.Functor (Functor(..), (<$>))
import Data.Int (Int)
import Data.Maybe (Maybe(..), fromMaybe, catMaybes)
import Data.Monoid (Monoid(..))
import Data.Ord (Ord(..))
import Data.Proxy (Proxy(..))
import Data.Semigroup (Semigroup(..))
import Data.String (String, IsString(..))
import Data.Text (Text)
import System.IO (IO)
import Text.Show (Show(..))
import qualified Control.Monad.Classes as MC
import qualified Control.Monad.Trans.Cont as C
import qualified Control.Monad.Trans.Reader as R
import qualified Control.Monad.Trans.State.Strict as S
import qualified Control.Monad.Trans.Writer.Strict as W
import qualified Data.ByteString as BS
import qualified Data.ByteString.Base64 as BS64
import qualified Data.ByteString.Builder as BSB
import qualified Data.ByteString.Lazy as BSL
import qualified Data.List as List
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Text.Encoding as Text
import qualified Data.Word8 as Word8
import qualified Network.HTTP.Media as Media
import qualified Network.HTTP.Types as HTTP
import qualified Network.HTTP.Types.Header as HTTP
import qualified Network.Wai as Wai
import qualified Web.HttpApiData as Web
import Symantic.HTTP
newtype Server responses k = Server { unServer ::
S.StateT ServerState
(ServerCheckT [ServerErrorBody]
(ServerCheckT [ServerErrorHeader]
(ServerCheckT [ServerErrorQuery]
(ServerCheckT [ServerErrorContentType]
(ServerCheckT [ServerErrorAccept]
(ServerCheckT [ServerErrorBasicAuth]
(ServerCheckT [ServerErrorMethod]
(ServerCheckT [ServerErrorPath]
IO))))))))
(responses -> k)
} deriving (Functor)
server ::
Server responses (Response Server) ->
responses ->
Wai.Application
server (Server api) responses rq re = do
lrPath <- runServerChecks api $ ServerState rq
case lrPath of
Left err -> respondError HTTP.status404 [] err
Right lrMethod ->
case lrMethod of
Left err -> respondError HTTP.status405 [] err
Right lrBasicAuth ->
case lrBasicAuth of
Left err ->
case failError err of
[] -> respondError HTTP.status500 [] err
ServerErrorBasicAuth realm ba:_ ->
case ba of
BasicAuth_Unauthorized ->
respondError HTTP.status403 [] err
_ ->
respondError HTTP.status401
[ ( HTTP.hWWWAuthenticate
, "Basic realm=\""<>Web.toHeader realm<>"\""
) ] err
Right lrAccept ->
case lrAccept of
Left err -> respondError HTTP.status406 [] err
Right lrContentType ->
case lrContentType of
Left err -> respondError HTTP.status415 [] err
Right lrQuery ->
case lrQuery of
Left err -> respondError HTTP.status400 [] err
Right lrHeader ->
case lrHeader of
Left err -> respondError HTTP.status400 [] err
Right lrBody ->
case lrBody of
Left err -> respondError HTTP.status400 [] err
Right (app, _st) ->
app responses rq re
where
respondError ::
Show err =>
HTTP.Status ->
[(HTTP.HeaderName, HeaderValue)] ->
err -> IO Wai.ResponseReceived
respondError st hs err =
re $ Wai.responseLBS st
( (HTTP.hContentType, Media.renderHeader $ mediaType @PlainText)
: hs
) (fromString $ show err)
runServerChecks ::
S.StateT ServerState (ExceptT e1 (ExceptT e2 (ExceptT e3 (ExceptT e4 (ExceptT e5 (ExceptT e6 (ExceptT e7 (ExceptT e8 IO)))))))) a ->
ServerState -> IO (Either e8 (Either e7 (Either e6 (Either e5 (Either e4 (Either e3 (Either e2 (Either e1 (a, ServerState)))))))))
runServerChecks s st =
runExceptT $
runExceptT $
runExceptT $
runExceptT $
runExceptT $
runExceptT $
runExceptT $
runExceptT $
S.runStateT s st
type ServerCheckT e = ExceptT (Fail e)
type RouteResult e = Either (Fail e)
data Fail e
= Fail ServerState e
| FailFatal !ServerState !e
deriving (Show)
failState :: Fail e -> ServerState
failState (Fail st _) = st
failState (FailFatal st _) = st
failError :: Fail e -> e
failError (Fail _st e) = e
failError (FailFatal _st e) = e
instance Semigroup e => Semigroup (Fail e) where
Fail _ x <> Fail st y = Fail st (x<>y)
FailFatal _ x <> Fail st y = FailFatal st (x<>y)
Fail _ x <> FailFatal st y = FailFatal st (x<>y)
FailFatal _ x <> FailFatal st y = FailFatal st (x<>y)
newtype ServerState = ServerState
{ serverState_request :: Wai.Request
}
instance Show ServerState where
show _ = "ServerState"
instance Cat Server where
(<.>) ::
forall a b c repr.
repr ~ Server =>
repr a b -> repr b c -> repr a c
Server x <.> Server y = Server $
S.StateT $ \st -> do
xPath <- MC.exec @IO $ runServerChecks x st
case xPath of
Left xe -> MC.throw xe
Right xMethod ->
case xMethod of
Left xe -> do
yPath <- MC.exec @IO $ runServerChecks y (failState xe)
case yPath of
Left ye -> MC.throw ye
Right _yMethod -> MC.throw xe
Right xBasicAuth ->
case xBasicAuth of
Left xe -> do
yPath <- MC.exec @IO $ runServerChecks y (failState xe)
case yPath of
Left ye -> MC.throw ye
Right yMethod ->
case yMethod of
Left ye -> MC.throw ye
Right _yBasicAuth -> MC.throw xe
Right xAccept ->
case xAccept of
Left xe -> do
yPath <- MC.exec @IO $ runServerChecks y (failState xe)
case yPath of
Left ye -> MC.throw ye
Right yMethod ->
case yMethod of
Left ye -> MC.throw ye
Right yBasicAuth ->
case yBasicAuth of
Left ye -> MC.throw ye
Right _yAccept -> MC.throw xe
Right xContentType ->
case xContentType of
Left xe -> do
yPath <- MC.exec @IO $ runServerChecks y (failState xe)
case yPath of
Left ye -> MC.throw ye
Right yMethod ->
case yMethod of
Left ye -> MC.throw ye
Right yBasicAuth ->
case yBasicAuth of
Left ye -> MC.throw ye
Right yAccept ->
case yAccept of
Left ye -> MC.throw ye
Right _yQuery -> MC.throw xe
Right xQuery ->
case xQuery of
Left xe -> do
yPath <- MC.exec @IO $ runServerChecks y (failState xe)
case yPath of
Left ye -> MC.throw ye
Right yMethod ->
case yMethod of
Left ye -> MC.throw ye
Right yBasicAuth ->
case yBasicAuth of
Left ye -> MC.throw ye
Right yAccept ->
case yAccept of
Left ye -> MC.throw ye
Right yQuery ->
case yQuery of
Left ye -> MC.throw ye
Right _yHeader -> MC.throw xe
Right xHeader ->
case xHeader of
Left xe -> do
yPath <- MC.exec @IO $ runServerChecks y (failState xe)
case yPath of
Left ye -> MC.throw ye
Right yMethod ->
case yMethod of
Left ye -> MC.throw ye
Right yBasicAuth ->
case yBasicAuth of
Left ye -> MC.throw ye
Right yAccept ->
case yAccept of
Left ye -> MC.throw ye
Right yQuery ->
case yQuery of
Left ye -> MC.throw ye
Right yHeader ->
case yHeader of
Left ye -> MC.throw ye
Right _yBody -> MC.throw xe
Right xBody ->
case xBody of
Left xe -> do
yPath <- MC.exec @IO $ runServerChecks y (failState xe)
case yPath of
Left ye -> MC.throw ye
Right yMethod ->
case yMethod of
Left ye -> MC.throw ye
Right yBasicAuth ->
case yBasicAuth of
Left ye -> MC.throw ye
Right yAccept ->
case yAccept of
Left ye -> MC.throw ye
Right yQuery ->
case yQuery of
Left ye -> MC.throw ye
Right yHeader ->
case yHeader of
Left ye -> MC.throw ye
Right _yBody -> MC.throw xe
Right (a2b, st') ->
(first (. a2b)) <$> S.runStateT y st'
instance Alt Server where
Server x <!> Server y = Server $
S.StateT $ \st -> do
xPath <- MC.exec @IO $ runServerChecks x st
yPath <- MC.exec @IO $ runServerChecks y st
let fy = (first (\b2k (_a:!:b) -> b2k b) <$>)
case xPath of
Left xe | FailFatal{} <- xe -> MC.throw xe
| otherwise ->
case yPath of
Left ye -> MC.throw (xe<>ye)
Right yMethod ->
fy $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $
return $ Right yMethod
Right xMethod ->
case xMethod of
Left xe | FailFatal{} <- xe -> MC.throw xe
| otherwise ->
case yPath of
Left _ye -> MC.throw xe
Right yMethod ->
case yMethod of
Left ye -> MC.throw (xe<>ye)
Right yBasicAuth ->
fy $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $
return $ Right $ yBasicAuth
Right xBasicAuth ->
case xBasicAuth of
Left xe | FailFatal{} <- xe -> MC.throw xe
| otherwise ->
case yPath of
Left _ye -> MC.throw xe
Right yMethod ->
case yMethod of
Left _ye -> MC.throw xe
Right yBasicAuth ->
case yBasicAuth of
Left ye -> MC.throw (xe<>ye)
Right yAccept ->
fy $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $
return $ Right yAccept
Right xAccept ->
case xAccept of
Left xe | FailFatal{} <- xe -> MC.throw xe
| otherwise ->
case yPath of
Left _ye -> MC.throw xe
Right yMethod ->
case yMethod of
Left _ye -> MC.throw xe
Right yBasicAuth ->
case yBasicAuth of
Left _ye -> MC.throw xe
Right yAccept ->
case yAccept of
Left ye -> MC.throw (xe<>ye)
Right yContentType ->
fy $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $
return $ Right yContentType
Right xContentType ->
case xContentType of
Left xe | FailFatal{} <- xe -> MC.throw xe
| otherwise ->
case yPath of
Left _ye -> MC.throw xe
Right yMethod ->
case yMethod of
Left _ye -> MC.throw xe
Right yBasicAuth ->
case yBasicAuth of
Left _ye -> MC.throw xe
Right yAccept ->
case yAccept of
Left _ye -> MC.throw xe
Right yContentType ->
case yContentType of
Left ye -> MC.throw (xe<>ye)
Right yQuery ->
fy $ ExceptT $ ExceptT $ ExceptT $ ExceptT $
return $ Right yQuery
Right xQuery ->
case xQuery of
Left xe | FailFatal{} <- xe -> MC.throw xe
| otherwise ->
case yPath of
Left _ye -> MC.throw xe
Right yMethod ->
case yMethod of
Left _ye -> MC.throw xe
Right yBasicAuth ->
case yBasicAuth of
Left _ye -> MC.throw xe
Right yAccept ->
case yAccept of
Left _ye -> MC.throw xe
Right yContentType ->
case yContentType of
Left _ye -> MC.throw xe
Right yQuery ->
case yQuery of
Left ye -> MC.throw (xe<>ye)
Right yHeader ->
fy $ ExceptT $ ExceptT $ ExceptT $
return $ Right yHeader
Right xHeader ->
case xHeader of
Left xe | FailFatal{} <- xe -> MC.throw xe
| otherwise ->
case yPath of
Left _ye -> MC.throw xe
Right yMethod ->
case yMethod of
Left _ye -> MC.throw xe
Right yBasicAuth ->
case yBasicAuth of
Left _ye -> MC.throw xe
Right yAccept ->
case yAccept of
Left _ye -> MC.throw xe
Right yContentType ->
case yContentType of
Left _ye -> MC.throw xe
Right yQuery ->
case yQuery of
Left _ye -> MC.throw xe
Right yHeader ->
case yHeader of
Left ye -> MC.throw (xe<>ye)
Right yBody ->
fy $ ExceptT $ ExceptT $
return $ Right yBody
Right xBody ->
case xBody of
Left xe | FailFatal{} <- xe -> MC.throw xe
| otherwise ->
case yPath of
Left _ye -> MC.throw xe
Right yMethod ->
case yMethod of
Left _ye -> MC.throw xe
Right yBasicAuth ->
case yBasicAuth of
Left _ye -> MC.throw xe
Right yAccept ->
case yAccept of
Left _ye -> MC.throw xe
Right yContentType ->
case yContentType of
Left _ye -> MC.throw xe
Right yQuery ->
case yQuery of
Left _ye -> MC.throw xe
Right yHeader ->
case yHeader of
Left _ye -> MC.throw xe
Right yBody ->
case yBody of
Left ye -> MC.throw (xe<>ye)
Right yr ->
fy $ ExceptT $
return $ Right yr
Right xr ->
return $ first (\a2k (a:!:_b) -> a2k a) xr
instance Pro Server where
dimap a2b _b2a (Server r) = Server $ (\k b2k -> k (b2k . a2b)) <$> r
newtype ServerErrorPath = ServerErrorPath Text
deriving (Eq, Show)
instance HTTP_Path Server where
type PathConstraint Server a = Web.FromHttpApiData a
segment expSegment = Server $ do
st@ServerState
{ serverState_request = req
} <- S.get
case Wai.pathInfo req of
[] -> MC.throw $ Fail st [ServerErrorPath "empty path segment"]
[""] -> MC.throw $ Fail st [ServerErrorPath "trailing slash"]
curr:next
| curr /= expSegment ->
MC.throw $ Fail st [ServerErrorPath $ "expected: "<>expSegment<>" but got: "<>curr]
| otherwise -> do
S.put st
{ serverState_request = req{ Wai.pathInfo = next }
}
return id
capture' :: forall a k. PathConstraint Server a => Name -> Server (a -> k) k
capture' name = Server $ do
st@ServerState
{ serverState_request = req
} <- S.get
case Wai.pathInfo req of
[] -> MC.throw $ Fail st [ServerErrorPath "empty"]
[""] -> MC.throw $ Fail st [ServerErrorPath "trailing slash"]
curr:next ->
case Web.parseUrlPiece curr of
Left err -> MC.throw $ Fail st [ServerErrorPath $ "capture: "<>fromString name<>": "<>err]
Right a -> do
S.put st
{ serverState_request = req{ Wai.pathInfo = next }
}
return ($ a)
captureAll = Server $ do
req <- S.gets serverState_request
return ($ Wai.pathInfo req)
data ServerErrorMethod = ServerErrorMethod
deriving (Eq, Show)
instance HTTP_Version Server where
version exp = Server $ do
st <- S.get
let got = Wai.httpVersion $ serverState_request st
if got == exp
then return id
else MC.throw $ Fail st [ServerErrorMethod]
data ServerErrorAccept =
ServerErrorAccept
MediaTypes
(Maybe (Either BS.ByteString MediaType))
deriving (Eq, Show)
data ServerErrorContentType = ServerErrorContentType
deriving (Eq, Show)
newtype ServerErrorQuery = ServerErrorQuery Text
deriving (Show)
instance HTTP_Query Server where
type QueryConstraint Server a = Web.FromHttpApiData a
queryParams' name = Server $ do
st <- S.get
lift $ ExceptT $ ExceptT $ ExceptT $ return $
let qs = Wai.queryString $ serverState_request st in
let vals = catMaybes $ (<$> qs) $ \(n,v) ->
if n == name
then Web.parseQueryParam . Text.decodeUtf8 <$> v
else Nothing in
case sequence vals of
Left err -> Left $ Fail st [ServerErrorQuery err]
Right vs -> Right $ Right $ Right ($ vs)
data ServerErrorHeader = ServerErrorHeader
deriving (Eq, Show)
instance HTTP_Header Server where
header n = Server $ do
st <- S.get
lift $ ExceptT $ ExceptT $ return $
let hs = Wai.requestHeaders $ serverState_request st in
case List.lookup n hs of
Nothing -> Left $ Fail st [ServerErrorHeader]
Just v -> Right $ Right ($ v)
data ServerErrorBasicAuth =
ServerErrorBasicAuth BasicAuthRealm (BasicAuth ())
deriving (Show)
class ServerBasicAuth a where
serverBasicAuth ::
BasicAuthUser ->
BasicAuthPass ->
IO (BasicAuth a)
instance HTTP_BasicAuth Server where
type BasicAuthConstraint Server a = ServerBasicAuth a
type BasicAuthArgs Server a k = a -> k
basicAuth' realm = Server $ do
st <- S.get
let err e = MC.throw $ Fail st [ServerErrorBasicAuth realm e]
case decodeAuthorization $ serverState_request st of
Nothing -> err BasicAuth_BadPassword
Just (user, pass) -> do
MC.exec @IO (serverBasicAuth user pass) >>= \case
BasicAuth_BadPassword -> err BasicAuth_BadPassword
BasicAuth_NoSuchUser -> err BasicAuth_NoSuchUser
BasicAuth_Unauthorized -> err BasicAuth_Unauthorized
BasicAuth_Authorized a -> return ($ a)
where
decodeAuthorization :: Wai.Request -> Maybe (BasicAuthUser, BasicAuthPass)
decodeAuthorization req = do
hAuthorization <- List.lookup "Authorization" $ Wai.requestHeaders req
let (basic, rest) = BS.break Word8.isSpace hAuthorization
guard (BS.map Word8.toLower basic == "basic")
let decoded = BS64.decodeLenient (BS.dropWhile Word8.isSpace rest)
let (user, colon_pass) = BS.break (== Word8._colon) decoded
(_, pass) <- BS.uncons colon_pass
return (Text.decodeUtf8 user, Text.decodeUtf8 pass)
newtype ServerErrorBody = ServerErrorBody String
deriving (Eq, Show)
newtype ServerBodyArg (ts::[*]) a = ServerBodyArg a
instance HTTP_Body Server where
type BodyConstraint Server a ts = MimeTypes ts (MimeDecodable a)
type BodyArg Server a ts = ServerBodyArg ts a
body' ::
forall a ts k repr.
BodyConstraint repr a ts =>
repr ~ Server =>
repr (BodyArg repr a ts -> k) k
body'= Server $ do
st <- S.get
lift $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ do
let hs = Wai.requestHeaders $ serverState_request st
let reqContentType =
fromMaybe "application/octet-stream" $
List.lookup HTTP.hContentType hs
case matchContent @ts @(MimeDecodable a) reqContentType of
Nothing -> return $ Left $ Fail st [ServerErrorContentType]
Just (MimeType mt) -> do
bodyBS <- MC.exec @IO $ Wai.requestBody $ serverState_request st
return $ Right $ Right $ Right $
case mimeDecode mt $ BSL.fromStrict bodyBS of
Left err -> Left $ Fail st [ServerErrorBody err]
Right a -> Right ($ ServerBodyArg a)
newtype ServerBodyStreamArg as (ts::[*]) framing
= ServerBodyStreamArg as
instance HTTP_BodyStream Server where
type BodyStreamConstraint Server as ts framing =
( FramingDecode framing as
, MC.MonadExec IO (FramingMonad as)
, MimeTypes ts (MimeDecodable (FramingYield as))
)
type BodyStreamArg Server as ts framing =
ServerBodyStreamArg as ts framing
bodyStream' ::
forall as ts framing k repr.
BodyStreamConstraint repr as ts framing =>
repr ~ Server =>
repr (BodyStreamArg repr as ts framing -> k) k
bodyStream'= Server $ do
st <- S.get
lift $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ do
let hs = Wai.requestHeaders $ serverState_request st
let reqContentType =
fromMaybe "application/octet-stream" $
List.lookup HTTP.hContentType hs
case matchContent @ts @(MimeDecodable (FramingYield as)) reqContentType of
Nothing -> return $ Left $ Fail st [ServerErrorContentType]
Just (MimeType mt) -> do
let bodyBS = Wai.requestBody $ serverState_request st
return $ Right $ Right $ Right $
Right ($ ServerBodyStreamArg $
framingDecode (Proxy @framing) (mimeDecode mt) $
MC.exec @IO bodyBS
)
newtype ServerRes (ts::[*]) m a
= ServerResponse
{ unServerResponse :: m a
} deriving (Functor, Applicative, Monad)
type ServerResponse ts m = ServerRes ts
(R.ReaderT Wai.Request
(W.WriterT HTTP.ResponseHeaders
(W.WriterT HTTP.Status
(C.ContT Wai.Response m))))
instance MonadTrans (ServerRes ts) where
lift = ServerResponse
type instance MC.CanDo (ServerResponse ts m) eff = 'False
type instance MC.CanDo (C.ContT ts m) (MC.EffExec eff) = 'False
instance HTTP_Response Server where
type ResponseConstraint Server a ts = MimeTypes ts (MimeEncodable a)
type ResponseArgs Server a ts = ServerResponse ts IO a
type Response Server =
Wai.Request ->
(Wai.Response -> IO Wai.ResponseReceived) ->
IO Wai.ResponseReceived
response ::
forall a ts repr.
ResponseConstraint repr a ts =>
repr ~ Server =>
HTTP.Method ->
repr (ResponseArgs repr a ts)
(Response repr)
response expMethod = Server $ do
st@ServerState
{ serverState_request = req
} <- S.get
unless (List.null $ Wai.pathInfo req) $
MC.throw $ Fail st [ServerErrorPath "path is longer"]
let reqMethod = Wai.requestMethod $ serverState_request st
unless (reqMethod == expMethod
|| reqMethod == HTTP.methodHead
&& expMethod == HTTP.methodGet) $
MC.throw $ Fail st [ServerErrorMethod]
let reqHeaders = Wai.requestHeaders $ serverState_request st
MimeType reqAccept <- do
case List.lookup HTTP.hAccept reqHeaders of
Nothing ->
return $ NonEmpty.head $ mimeTypes @ts @(MimeEncodable a)
Just h ->
case matchAccept @ts @(MimeEncodable a) h of
Nothing -> MC.throw $ Fail st [ServerErrorAccept (mediaTypes @ts @(MimeEncodable a)) (Just (Left h))]
Just mt -> return mt
return $ \(ServerResponse k) rq re -> re =<< do
C.runContT (W.runWriterT $ W.runWriterT $ R.runReaderT k rq) $ \((a,hs),sta) ->
return $
Wai.responseLBS sta
((HTTP.hContentType, Media.renderHeader $ mediaTypeFor reqAccept):hs)
(if reqMethod == HTTP.methodHead
then ""
else mimeEncode reqAccept a)
newtype ServerResStream framing (ts::[*]) m as
= ServerResponseStream
{ unServerResponseStream :: m as
} deriving (Functor, Applicative, Monad)
instance MonadTrans (ServerResStream framing ts) where
lift = ServerResponseStream
type ServerResponseStream framing ts m = ServerResStream framing ts
(R.ReaderT Wai.Request
(W.WriterT HTTP.ResponseHeaders
(W.WriterT HTTP.Status
(C.ContT Wai.Response m))))
type instance MC.CanDo (ServerResponseStream framing ts m) eff = 'False
instance HTTP_ResponseStream Server where
type ResponseStreamConstraint Server as ts framing =
( FramingEncode framing as
, MimeTypes ts (MimeEncodable (FramingYield as))
)
type ResponseStreamArgs Server as ts framing =
ServerResponseStream framing ts IO as
type ResponseStream Server =
Wai.Request ->
(Wai.Response -> IO Wai.ResponseReceived) ->
IO Wai.ResponseReceived
responseStream ::
forall as ts framing repr.
ResponseStreamConstraint repr as ts framing =>
repr ~ Server =>
HTTP.Method ->
repr (ResponseStreamArgs repr as ts framing)
(ResponseStream repr)
responseStream expMethod = Server $ do
st@ServerState
{ serverState_request = req
} <- S.get
unless (List.null $ Wai.pathInfo req) $
MC.throw $ Fail st [ServerErrorPath "path is longer"]
let reqMethod = Wai.requestMethod $ serverState_request st
unless (reqMethod == expMethod
|| reqMethod == HTTP.methodHead
&& expMethod == HTTP.methodGet) $
MC.throw $ Fail st [ServerErrorMethod]
let reqHeaders = Wai.requestHeaders $ serverState_request st
MimeType reqAccept <- do
case List.lookup HTTP.hAccept reqHeaders of
Nothing ->
return $ NonEmpty.head $ mimeTypes @ts @(MimeEncodable (FramingYield as))
Just h ->
case matchAccept @ts @(MimeEncodable (FramingYield as)) h of
Nothing -> MC.throw $ Fail st [ServerErrorAccept (mediaTypes @ts @(MimeEncodable (FramingYield as))) (Just (Left h))]
Just mt -> return mt
return $ \(ServerResponseStream k) rq re -> re =<< do
C.runContT (W.runWriterT $ W.runWriterT $ R.runReaderT k rq) $ \((as,hs),sta) ->
return $
Wai.responseStream sta
( (HTTP.hContentType, Media.renderHeader $ mediaTypeFor reqAccept)
: hs
) $ \write flush ->
if reqMethod == HTTP.methodHead
then flush
else
let enc = framingEncode (Proxy @framing) (mimeEncode reqAccept) in
let go curr =
case curr of
Left _end -> flush
Right (bsl, next) -> do
unless (BSL.null bsl) $ do
write (BSB.lazyByteString bsl)
flush
enc next >>= go
in enc as >>= go
instance Semigroup HTTP.Status where
x <> y =
if rank (HTTP.statusCode x) < rank (HTTP.statusCode y)
then x
else y
where
rank :: Int -> Int
rank 404 = 0
rank 405 = 1
rank 401 = 2
rank 415 = 3
rank 406 = 4
rank 400 = 5
rank _ = 6
instance Monoid HTTP.Status where
mempty = HTTP.status200
mappend = (<>)