1 {-# LANGUAGE GADTs #-} -- for 'Router' and 'RouterUnion'
2 {-# LANGUAGE ConstraintKinds #-}
3 {-# LANGUAGE DataKinds #-} -- for 'BinTree'
4 {-# LANGUAGE DefaultSignatures #-}
5 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
6 {-# LANGUAGE OverloadedStrings #-}
7 {-# LANGUAGE UndecidableInstances #-} -- for nested type family application,
8 -- eg. in 'BodyStreamConstraint'
9 {-# OPTIONS_GHC -fno-warn-orphans #-}
10 -- | See <https://hackage.haskell.org/package/symantic-http-demo symantic-http-demo>
11 -- for an example of how to use this module.
12 module Symantic.HTTP.Server where
14 import Control.Applicative (Applicative(..))
15 import Control.Arrow (first)
16 import Control.Monad (Monad(..), unless, sequence, guard, (=<<))
17 import Control.Monad.Trans.Class (MonadTrans(..))
18 import Control.Monad.Trans.Except (ExceptT(..), runExceptT)
20 import Data.Either (Either(..))
21 import Data.Eq (Eq(..))
22 import Data.Function (($), (.), id, const)
23 import Data.Functor (Functor(..), (<$>))
25 import Data.Kind (Type)
26 import Data.Maybe (Maybe(..), fromMaybe, catMaybes)
27 import Data.Monoid (Monoid(..))
28 import Data.Ord (Ord(..))
29 import Data.Proxy (Proxy(..))
30 import Data.Semigroup (Semigroup(..))
31 import Data.String (String, IsString(..))
32 import Data.Text (Text)
34 import Text.Show (Show(..))
35 import qualified Control.Monad.Classes as MC
36 import qualified Control.Monad.Trans.Cont as C
37 import qualified Control.Monad.Trans.Reader as R
38 import qualified Control.Monad.Trans.State.Strict as S
39 import qualified Control.Monad.Trans.Writer.Strict as W
40 import qualified Data.ByteString as BS
41 import qualified Data.ByteString.Base64 as BS64
42 import qualified Data.ByteString.Builder as BSB
43 import qualified Data.ByteString.Lazy as BSL
44 import qualified Data.List as List
45 import qualified Data.List.NonEmpty as NonEmpty
46 import qualified Data.Map.Merge.Strict as Map
47 import qualified Data.Map.Strict as Map
48 import qualified Data.Text as Text
49 import qualified Data.Text.Encoding as Text
50 import qualified Data.Word8 as Word8
51 import qualified Network.HTTP.Media as Media
52 import qualified Network.HTTP.Types as HTTP
53 import qualified Network.HTTP.Types.Header as HTTP
54 import qualified Network.Wai as Wai
55 import qualified Web.HttpApiData as Web
60 -- | (@'Server' handlers k@) is a recipe to produce an 'Wai.Application'
61 -- from given ('handlers') (one per number of alternative routes),
62 -- separated by (':!:').
64 -- 'Server' is analogous to a scanf using the API as a format customized for HTTP routing.
66 -- The multiple 'ServerCheckT' monad transformers are there
67 -- to prioritize the errors according to the type of check raising them,
68 -- instead of the order of the combinators within an actual API specification.
69 newtype Server handlers k = Server { unServer ::
71 (ServerCheckT [ServerErrorBody] -- 8th check, 400 error
72 (ServerCheckT [ServerErrorHeader] -- 7th check, 400 error
73 (ServerCheckT [ServerErrorQuery] -- 6th check, 400 error
74 (ServerCheckT [ServerErrorContentType] -- 5th check, 415 error
75 (ServerCheckT [ServerErrorAccept] -- 4th check, 406 error
76 (ServerCheckT [ServerErrorBasicAuth] -- 3rd check, 401 or 403 error
77 (ServerCheckT [ServerErrorMethod] -- 2nd check, 405 error
78 (ServerCheckT [ServerErrorPath] -- 1st check, 404 error
83 -- | (@'server' api handlers@) returns an 'Wai.Application'
84 -- ready to be given to @Warp.run 80@.
86 Router Server handlers (Response Server) ->
89 server api handlers rq re = do
90 lrPath <- runServerChecks (unServer $ unTrans $ router api) $ ServerState rq
92 Left err -> respondError HTTP.status404 [] err
95 Left err -> respondError HTTP.status405 [] err
100 [] -> respondError HTTP.status500 [] err
101 ServerErrorBasicAuth realm ba:_ ->
103 BasicAuth_Unauthorized ->
104 respondError HTTP.status403 [] err
106 respondError HTTP.status401
107 [ ( HTTP.hWWWAuthenticate
108 , "Basic realm=\""<>Web.toHeader realm<>"\""
112 Left err -> respondError HTTP.status406 [] err
113 Right lrContentType ->
114 case lrContentType of
115 Left err -> respondError HTTP.status415 [] err
118 Left err -> respondError HTTP.status400 [] err
121 Left err -> respondError HTTP.status400 [] err
124 Left err -> respondError HTTP.status400 [] err
131 [(HTTP.HeaderName, HeaderValue)] ->
132 err -> IO Wai.ResponseReceived
133 respondError st hs err =
134 -- Trace.trace (show err) $
135 re $ Wai.responseLBS st
136 ( (HTTP.hContentType, Media.renderHeader $ mediaType @PlainText)
138 ) (fromString $ show err) -- TODO: see what to return in the body
140 -- | Unwrap the 'ExceptT' constructors to reach the 'Either' constructors.
142 S.StateT ServerState (ExceptT e1 (ExceptT e2 (ExceptT e3 (ExceptT e4 (ExceptT e5 (ExceptT e6 (ExceptT e7 (ExceptT e8 IO)))))))) a ->
143 ServerState -> IO (Either e8 (Either e7 (Either e6 (Either e5 (Either e4 (Either e3 (Either e2 (Either e1 (a, ServerState)))))))))
144 runServerChecks s st =
155 -- ** Type 'ServerCheckT'
156 type ServerCheckT e = ExceptT (Fail e)
158 -- *** Type 'RouteResult'
159 type RouteResult e = Either (Fail e)
163 = Fail ServerState e -- ^ Keep trying other paths. 404, 405 or 406.
164 | FailFatal !ServerState !e -- ^ Don't try other paths.
166 failState :: Fail e -> ServerState
167 failState (Fail st _) = st
168 failState (FailFatal st _) = st
169 failError :: Fail e -> e
170 failError (Fail _st e) = e
171 failError (FailFatal _st e) = e
172 instance Semigroup e => Semigroup (Fail e) where
173 Fail _ x <> Fail st y = Fail st (x<>y)
174 FailFatal _ x <> Fail st y = FailFatal st (x<>y)
175 Fail _ x <> FailFatal st y = FailFatal st (x<>y)
176 FailFatal _ x <> FailFatal st y = FailFatal st (x<>y)
178 -- ** Type 'ServerState'
179 newtype ServerState = ServerState
180 { serverState_request :: Wai.Request
182 instance Show ServerState where
183 show _ = "ServerState"
185 instance Cat Server where
189 repr a b -> repr b c -> repr a c
190 -- NOTE: if x fails, run y to see if it fails on a more prioritized check.
191 -- And if so, fail with y instead of x.
193 -- This long spaghetti code may probably be avoided
194 -- with a more sophisticated 'Server' using a binary tree
195 -- instead of nested 'Either's, so that its 'Monad' instance
196 -- would do the right thing. But to my mind,
197 -- with the very few priorities of checks currently needed,
198 -- this is not worth the cognitive pain to design it.
199 -- Some copying/pasting/adapting will do for now.
200 Server x <.> Server y = Server $
202 xPath <- MC.exec @IO $ runServerChecks x st
204 Left xe -> MC.throw xe
208 yPath <- MC.exec @IO $ runServerChecks y (failState xe)
210 Left ye -> MC.throw ye
211 Right _yMethod -> MC.throw xe
215 yPath <- MC.exec @IO $ runServerChecks y (failState xe)
217 Left ye -> MC.throw ye
220 Left ye -> MC.throw ye
221 Right _yBasicAuth -> MC.throw xe
225 yPath <- MC.exec @IO $ runServerChecks y (failState xe)
227 Left ye -> MC.throw ye
230 Left ye -> MC.throw ye
233 Left ye -> MC.throw ye
234 Right _yAccept -> MC.throw xe
235 Right xContentType ->
238 yPath <- MC.exec @IO $ runServerChecks y (failState xe)
240 Left ye -> MC.throw ye
243 Left ye -> MC.throw ye
246 Left ye -> MC.throw ye
249 Left ye -> MC.throw ye
250 Right _yQuery -> MC.throw xe
254 yPath <- MC.exec @IO $ runServerChecks y (failState xe)
256 Left ye -> MC.throw ye
259 Left ye -> MC.throw ye
262 Left ye -> MC.throw ye
265 Left ye -> MC.throw ye
268 Left ye -> MC.throw ye
269 Right _yHeader -> MC.throw xe
273 yPath <- MC.exec @IO $ runServerChecks y (failState xe)
275 Left ye -> MC.throw ye
278 Left ye -> MC.throw ye
281 Left ye -> MC.throw ye
284 Left ye -> MC.throw ye
287 Left ye -> MC.throw ye
290 Left ye -> MC.throw ye
291 Right _yBody -> MC.throw xe
295 yPath <- MC.exec @IO $ runServerChecks y (failState xe)
297 Left ye -> MC.throw ye
300 Left ye -> MC.throw ye
303 Left ye -> MC.throw ye
306 Left ye -> MC.throw ye
309 Left ye -> MC.throw ye
312 Left ye -> MC.throw ye
313 Right _yBody -> MC.throw xe
315 first (. a2b) <$> S.runStateT y st'
316 instance Alt Server where
317 -- (<!>) :: repr a k -> repr b k -> repr (a:!:b) k
318 Server x <!> Server y = Server $
320 xPath <- MC.exec @IO $ runServerChecks x st
321 let fy = (first (\b2k (_a:!:b) -> b2k b) <$>)
323 Left xe | FailFatal{} <- xe -> MC.throw xe
325 yPath <- MC.exec @IO $ runServerChecks y st
327 Left ye -> MC.throw (xe<>ye)
329 fy $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $
330 return $ Right yMethod
333 Left xe | FailFatal{} <- xe -> MC.throw xe
335 yPath <- MC.exec @IO $ runServerChecks y st
337 Left _ye -> MC.throw xe
340 Left ye -> MC.throw (xe<>ye)
342 fy $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $
343 return $ Right $ yBasicAuth
346 Left xe | FailFatal{} <- xe -> MC.throw xe
348 yPath <- MC.exec @IO $ runServerChecks y st
350 Left _ye -> MC.throw xe
353 Left _ye -> MC.throw xe
356 Left ye -> MC.throw (xe<>ye)
358 fy $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $
359 return $ Right yAccept
362 Left xe | FailFatal{} <- xe -> MC.throw xe
364 yPath <- MC.exec @IO $ runServerChecks y st
366 Left _ye -> MC.throw xe
369 Left _ye -> MC.throw xe
372 Left _ye -> MC.throw xe
375 Left ye -> MC.throw (xe<>ye)
376 Right yContentType ->
377 fy $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $
378 return $ Right yContentType
379 Right xContentType ->
381 Left xe | FailFatal{} <- xe -> MC.throw xe
383 yPath <- MC.exec @IO $ runServerChecks y st
385 Left _ye -> MC.throw xe
388 Left _ye -> MC.throw xe
391 Left _ye -> MC.throw xe
394 Left _ye -> MC.throw xe
395 Right yContentType ->
397 Left ye -> MC.throw (xe<>ye)
399 fy $ ExceptT $ ExceptT $ ExceptT $ ExceptT $
400 return $ Right yQuery
403 Left xe | FailFatal{} <- xe -> MC.throw xe
405 yPath <- MC.exec @IO $ runServerChecks y st
407 Left _ye -> MC.throw xe
410 Left _ye -> MC.throw xe
413 Left _ye -> MC.throw xe
416 Left _ye -> MC.throw xe
417 Right yContentType ->
419 Left _ye -> MC.throw xe
422 Left ye -> MC.throw (xe<>ye)
424 fy $ ExceptT $ ExceptT $ ExceptT $
425 return $ Right yHeader
428 Left xe | FailFatal{} <- xe -> MC.throw xe
430 yPath <- MC.exec @IO $ runServerChecks y st
432 Left _ye -> MC.throw xe
435 Left _ye -> MC.throw xe
438 Left _ye -> MC.throw xe
441 Left _ye -> MC.throw xe
442 Right yContentType ->
444 Left _ye -> MC.throw xe
447 Left _ye -> MC.throw xe
450 Left ye -> MC.throw (xe<>ye)
452 fy $ ExceptT $ ExceptT $
456 Left xe | FailFatal{} <- xe -> MC.throw xe
458 yPath <- MC.exec @IO $ runServerChecks y st
460 Left _ye -> MC.throw xe
463 Left _ye -> MC.throw xe
466 Left _ye -> MC.throw xe
469 Left _ye -> MC.throw xe
470 Right yContentType ->
472 Left _ye -> MC.throw xe
475 Left _ye -> MC.throw xe
478 Left _ye -> MC.throw xe
481 Left ye -> MC.throw (xe<>ye)
486 return $ first (\a2k (a:!:_b) -> a2k a) xr
487 instance Pro Server where
488 dimap a2b _b2a (Server r) = Server $ (\k b2k -> k (b2k . a2b)) <$> r
490 -- ** Type 'ServerErrorPath'
491 newtype ServerErrorPath = ServerErrorPath Text
494 instance HTTP_Path Server where
495 type PathConstraint Server a = Web.FromHttpApiData a
496 segment expSegment = Server $ do
498 { serverState_request = req
500 case Wai.pathInfo req of
501 [] -> MC.throw $ Fail st [ServerErrorPath "empty path segment"]
502 [""] -> MC.throw $ Fail st [ServerErrorPath "trailing slash"]
504 | curr /= expSegment ->
505 MC.throw $ Fail st [ServerErrorPath $ "expected: "<>expSegment<>" but got: "<>curr]
508 { serverState_request = req{ Wai.pathInfo = next }
511 capture' :: forall a k. PathConstraint Server a => Name -> Server (a -> k) k
512 capture' name = Server $ do
514 { serverState_request = req
516 case Wai.pathInfo req of
517 [] -> MC.throw $ Fail st [ServerErrorPath "empty"]
518 [""] -> MC.throw $ Fail st [ServerErrorPath "trailing slash"]
520 case Web.parseUrlPiece curr of
521 Left err -> MC.throw $ Fail st [ServerErrorPath $ "capture: "<>fromString name<>": "<>err]
524 { serverState_request = req{ Wai.pathInfo = next }
527 captureAll = Server $ do
528 req <- S.gets serverState_request
529 return ($ Wai.pathInfo req)
531 -- ** Type 'ServerErrorMethod'
532 data ServerErrorMethod = ServerErrorMethod
535 -- | TODO: add its own error?
536 instance HTTP_Version Server where
537 version exp = Server $ do
539 let got = Wai.httpVersion $ serverState_request st
542 else MC.throw $ Fail st [ServerErrorMethod] -- FIXME: ServerErrorVersion
544 -- ** Type 'ServerErrorAccept'
545 data ServerErrorAccept =
548 (Maybe (Either BS.ByteString MediaType))
551 -- ** Type 'ServerErrorContentType'
552 data ServerErrorContentType = ServerErrorContentType
555 -- ** Type 'ServerErrorQuery'
556 newtype ServerErrorQuery = ServerErrorQuery Text
558 instance HTTP_Query Server where
559 type QueryConstraint Server a = Web.FromHttpApiData a
560 queryParams' name = Server $ do
562 lift $ ExceptT $ ExceptT $ ExceptT $ return $
563 let qs = Wai.queryString $ serverState_request st in
564 let vals = catMaybes $ (<$> qs) $ \(n,v) ->
566 then Web.parseQueryParam . Text.decodeUtf8 <$> v
568 case sequence vals of
569 Left err -> Left $ Fail st [ServerErrorQuery err]
570 Right vs -> Right $ Right $ Right ($ vs)
572 -- ** Type 'ServerErrorHeader'
573 data ServerErrorHeader = ServerErrorHeader
575 instance HTTP_Header Server where
576 header n = Server $ do
578 lift $ ExceptT $ ExceptT $ return $
579 let hs = Wai.requestHeaders $ serverState_request st in
580 case List.lookup n hs of
581 Nothing -> Left $ Fail st [ServerErrorHeader]
582 Just v -> Right $ Right ($ v)
584 -- ** Type 'ServerErrorBasicAuth'
585 data ServerErrorBasicAuth =
586 ServerErrorBasicAuth BasicAuthRealm (BasicAuth ())
589 -- ** Class 'ServerBasicAuth'
590 -- | Custom 'BasicAuth' check.
591 class ServerBasicAuth a where
597 -- | WARNING: current implementation of Basic Access Authentication
598 -- is not immune to certain kinds of timing attacks.
599 -- Decoding payloads does not take a fixed amount of time.
600 instance HTTP_BasicAuth Server where
601 type BasicAuthConstraint Server a = ServerBasicAuth a
602 type BasicAuthArgs Server a k = a -> k
603 basicAuth' realm = Server $ do
605 let err e = MC.throw $ Fail st [ServerErrorBasicAuth realm e]
606 case decodeAuthorization $ serverState_request st of
607 Nothing -> err BasicAuth_BadPassword
608 Just (user, pass) -> do
609 MC.exec @IO (serverBasicAuth user pass) >>= \case
610 BasicAuth_BadPassword -> err BasicAuth_BadPassword
611 BasicAuth_NoSuchUser -> err BasicAuth_NoSuchUser
612 BasicAuth_Unauthorized -> err BasicAuth_Unauthorized
613 BasicAuth_Authorized u -> return ($ u)
615 -- | Find and decode an 'Authorization' header from the request as a Basic Auth
616 decodeAuthorization :: Wai.Request -> Maybe (BasicAuthUser, BasicAuthPass)
617 decodeAuthorization req = do
618 hAuthorization <- List.lookup "Authorization" $ Wai.requestHeaders req
619 let (basic, rest) = BS.break Word8.isSpace hAuthorization
620 guard (BS.map Word8.toLower basic == "basic")
621 let decoded = BS64.decodeLenient (BS.dropWhile Word8.isSpace rest)
622 let (user, colon_pass) = BS.break (== Word8._colon) decoded
623 (_, pass) <- BS.uncons colon_pass
624 return (Text.decodeUtf8 user, Text.decodeUtf8 pass)
626 -- ** Type 'ServerErrorBody'
627 newtype ServerErrorBody = ServerErrorBody String
630 -- *** Type 'ServerBodyArg'
631 newtype ServerBodyArg (ts::[Type]) a = ServerBodyArg a
633 instance HTTP_Body Server where
634 type BodyConstraint Server a ts = MimeTypes ts (MimeDecodable a)
635 type BodyArg Server a ts = ServerBodyArg ts a
638 BodyConstraint repr a ts =>
640 repr (BodyArg repr a ts -> k) k
643 lift $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ do
644 let hs = Wai.requestHeaders $ serverState_request st
646 -- DOC: http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1
647 -- DOC: http://www.w3.org/2001/tag/2002/0129-mime
648 fromMaybe "application/octet-stream" $
649 List.lookup HTTP.hContentType hs
650 case matchContent @ts @(MimeDecodable a) reqContentType of
651 Nothing -> return $ Left $ Fail st [ServerErrorContentType]
652 Just (MimeType mt) -> do
653 bodyBS <- MC.exec @IO $ Wai.requestBody $ serverState_request st
654 return $ Right $ Right $ Right $
655 -- NOTE: delay 'mimeDecode' after all checks
656 case mimeDecode mt $ BSL.fromStrict bodyBS of
657 Left err -> Left $ Fail st [ServerErrorBody err]
658 Right a -> Right ($ ServerBodyArg a)
660 -- *** Type 'ServerBodyStreamArg'
661 newtype ServerBodyStreamArg as (ts::[Type]) framing
662 = ServerBodyStreamArg as
663 instance HTTP_BodyStream Server where
664 type BodyStreamConstraint Server as ts framing =
665 ( FramingDecode framing as
666 , MC.MonadExec IO (FramingMonad as)
667 , MimeTypes ts (MimeDecodable (FramingYield as))
669 type BodyStreamArg Server as ts framing =
670 ServerBodyStreamArg as ts framing
672 forall as ts framing k repr.
673 BodyStreamConstraint repr as ts framing =>
675 repr (BodyStreamArg repr as ts framing -> k) k
676 bodyStream'= Server $ do
678 lift $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ do
679 let hs = Wai.requestHeaders $ serverState_request st
681 -- DOC: http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1
682 -- DOC: http://www.w3.org/2001/tag/2002/0129-mime
683 fromMaybe "application/octet-stream" $
684 List.lookup HTTP.hContentType hs
685 case matchContent @ts @(MimeDecodable (FramingYield as)) reqContentType of
686 Nothing -> return $ Left $ Fail st [ServerErrorContentType]
687 Just (MimeType mt) -> do
688 let bodyBS = Wai.requestBody $ serverState_request st
689 return $ Right $ Right $ Right $
690 Right ($ ServerBodyStreamArg $
691 framingDecode (Proxy @framing) (mimeDecode mt) $
695 -- * Type 'ServerResponse'
696 -- | A continuation for 'server''s users to respond.
698 -- This newtype has two uses :
700 -- * Carrying the 'ts' type variable to 'server'.
701 -- * Providing a 'return' for the simple response case
702 -- of 'HTTP.status200' and no extra headers.
703 newtype ServerRes (ts::[Type]) m a
705 { unServerResponse :: m a
706 } deriving (Functor, Applicative, Monad)
707 type ServerResponse ts m = ServerRes ts
708 (R.ReaderT Wai.Request
709 (W.WriterT HTTP.ResponseHeaders
710 (W.WriterT HTTP.Status
711 (C.ContT Wai.Response m))))
712 instance MonadTrans (ServerRes ts) where
713 lift = ServerResponse
714 -- | All supported effects are handled by nested 'Monad's.
715 type instance MC.CanDo (ServerResponse ts m) eff = 'False
716 type instance MC.CanDo (C.ContT ts m) (MC.EffExec eff) = 'False
718 instance HTTP_Response Server where
719 type ResponseConstraint Server a ts = MimeTypes ts (MimeEncodable a)
720 type ResponseArgs Server a ts = ServerResponse ts IO a
721 type Response Server =
723 (Wai.Response -> IO Wai.ResponseReceived) ->
724 IO Wai.ResponseReceived
727 ResponseConstraint repr a ts =>
730 repr (ResponseArgs repr a ts)
732 response expMethod = Server $ do
734 { serverState_request = req
737 -- Check the path has been fully consumed
738 unless (List.null $ Wai.pathInfo req) $
739 MC.throw $ Fail st [ServerErrorPath "path is longer"]
742 let reqMethod = Wai.requestMethod $ serverState_request st
743 unless (reqMethod == expMethod
744 || reqMethod == HTTP.methodHead
745 && expMethod == HTTP.methodGet) $
746 MC.throw $ Fail st [ServerErrorMethod]
748 -- Check the Accept header
749 let reqHeaders = Wai.requestHeaders $ serverState_request st
750 MimeType reqAccept <- do
751 case List.lookup HTTP.hAccept reqHeaders of
753 return $ NonEmpty.head $ mimeTypes @ts @(MimeEncodable a)
755 case matchAccept @ts @(MimeEncodable a) h of
756 Nothing -> MC.throw $ Fail st [ServerErrorAccept (mediaTypes @ts @(MimeEncodable a)) (Just (Left h))]
759 return $ \(ServerResponse k) rq re -> re =<< do
760 C.runContT (W.runWriterT $ W.runWriterT $ R.runReaderT k rq) $ \((a,hs),sta) ->
763 ((HTTP.hContentType, Media.renderHeader $ mediaTypeFor reqAccept):hs)
764 (if reqMethod == HTTP.methodHead
766 else mimeEncode reqAccept a)
768 -- * Type 'ServerResponseStream'
770 -- This newtype has three uses :
772 -- * Carrying the 'framing' type variable to 'server'.
773 -- * Carrying the 'ts' type variable to 'server'.
774 -- * Providing a 'return' for the simple response case
775 -- of 'HTTP.status200' and no extra headers.
776 newtype ServerResStream framing (ts::[Type]) m as
777 = ServerResponseStream
778 { unServerResponseStream :: m as
779 } deriving (Functor, Applicative, Monad)
780 instance MonadTrans (ServerResStream framing ts) where
781 lift = ServerResponseStream
782 type ServerResponseStream framing ts m = ServerResStream framing ts
783 (R.ReaderT Wai.Request
784 (W.WriterT HTTP.ResponseHeaders
785 (W.WriterT HTTP.Status
786 (C.ContT Wai.Response m))))
787 -- | All supported effects are handled by nested 'Monad's.
788 type instance MC.CanDo (ServerResponseStream framing ts m) eff = 'False
790 instance HTTP_ResponseStream Server where
791 type ResponseStreamConstraint Server as ts framing =
792 ( FramingEncode framing as
793 , MimeTypes ts (MimeEncodable (FramingYield as))
795 type ResponseStreamArgs Server as ts framing =
796 ServerResponseStream framing ts IO as
797 type ResponseStream Server =
799 (Wai.Response -> IO Wai.ResponseReceived) ->
800 IO Wai.ResponseReceived
802 forall as ts framing repr.
803 ResponseStreamConstraint repr as ts framing =>
806 repr (ResponseStreamArgs repr as ts framing)
807 (ResponseStream repr)
808 responseStream expMethod = Server $ do
810 { serverState_request = req
813 -- Check the path has been fully consumed
814 unless (List.null $ Wai.pathInfo req) $
815 MC.throw $ Fail st [ServerErrorPath "path is longer"]
818 let reqMethod = Wai.requestMethod $ serverState_request st
819 unless (reqMethod == expMethod
820 || reqMethod == HTTP.methodHead
821 && expMethod == HTTP.methodGet) $
822 MC.throw $ Fail st [ServerErrorMethod]
824 -- Check the Accept header
825 let reqHeaders = Wai.requestHeaders $ serverState_request st
826 MimeType reqAccept <- do
827 case List.lookup HTTP.hAccept reqHeaders of
829 return $ NonEmpty.head $ mimeTypes @ts @(MimeEncodable (FramingYield as))
831 case matchAccept @ts @(MimeEncodable (FramingYield as)) h of
832 Nothing -> MC.throw $ Fail st [ServerErrorAccept (mediaTypes @ts @(MimeEncodable (FramingYield as))) (Just (Left h))]
835 return $ \(ServerResponseStream k) rq re -> re =<< do
836 C.runContT (W.runWriterT $ W.runWriterT $ R.runReaderT k rq) $ \((as,hs),sta) ->
838 Wai.responseStream sta
839 ( (HTTP.hContentType, Media.renderHeader $ mediaTypeFor reqAccept)
842 if reqMethod == HTTP.methodHead
845 let enc = framingEncode (Proxy @framing) (mimeEncode reqAccept) in
849 Right (bsl, next) -> do
850 unless (BSL.null bsl) $ do
851 write (BSB.lazyByteString bsl)
856 -- | Return worse 'HTTP.Status'.
857 instance Semigroup HTTP.Status where
859 if rank (HTTP.statusCode x) < rank (HTTP.statusCode y)
864 rank 404 = 0 -- Not Found
865 rank 405 = 1 -- Method Not Allowed
866 rank 401 = 2 -- Unauthorized
867 rank 415 = 3 -- Unsupported Media Type
868 rank 406 = 4 -- Not Acceptable
869 rank 400 = 5 -- Bad Request
871 -- | Useful when 'HTTP.Status' is within a 'W.WriterT'.
872 instance Monoid HTTP.Status where
873 mempty = HTTP.status200
877 -- | 'Trans'form a 'Server' to merge 'Alt'ernative 'segment's into a 'routing'.
878 data Router repr a b where
879 -- | Lift any @(repr)@ into 'Router', those not useful to segregate
880 -- wrt. the 'Trans'formation performed, aka. 'noTrans'.
881 Router_Any :: repr a b -> Router repr a b
882 -- | Represent 'segment'.
883 Router_Seg :: PathSegment -> Router repr k k
884 -- | Represent ('<.>').
885 Router_Cat :: Router repr a b -> Router repr b c -> Router repr a c
886 -- | Represent 'routing'.
887 Router_Map :: RouterMap repr a k -> Router repr a k
888 -- | Represent ('<!>').
889 Router_Alt :: Router repr a k -> Router repr b k -> Router repr (a:!:b) k
890 -- | Represent |capture'|.
891 Router_Cap :: PathConstraint Server a => Name -> Router repr (a->k) k
892 -- | Represent 'captures'.
893 Router_Caps :: Captures (Router repr) cs k -> Router repr (AltFromBinTree cs) k
895 -- ** Type 'RouterMap'
896 -- | Optimize the routing by using a 'Map.Map' instead of ('<!>') over 'segment'.
897 type RouterMap repr a k =
898 Map.Map PathSegment (RouterUnion repr a k)
900 -- *** Type 'RouterUnion'
901 -- | Unify 'Router's which have different 'handlers'.
902 -- Useful to put alternative 'Router's in a 'Map.Map' as in 'RouterMap'.
903 data RouterUnion repr a k where
904 RouterUnion :: (b->a) -> (Router repr) a k -> RouterUnion repr b k
907 RouterMap repr xs k ->
908 RouterMap repr ys k ->
909 RouterMap repr (xs:!:ys) k
912 (Map.traverseMissing $ const $ \(RouterUnion b2a r) ->
913 return $ RouterUnion (\(x:!:_y) -> b2a x) r)
914 (Map.traverseMissing $ const $ \(RouterUnion b2a r) ->
915 return $ RouterUnion (\(_x:!:y) -> b2a y) r)
916 (Map.zipWithAMatched $ const $ \(RouterUnion xb2a xr) (RouterUnion yb2a yr) ->
917 return $ RouterUnion (\(x:!:y) -> xb2a x:!:yb2a y) $ xr`router_Alt`yr)
919 -- ** Type 'Captures'
920 data Captures repr (cs::BinTree Type) k where
921 Captures0 :: PathConstraint Server a =>
922 Proxy a -> Name -> repr x k ->
923 Captures repr ('BinTree0 (a->x)) k
924 Captures2 :: Captures repr x k ->
926 Captures repr ('BinTree2 x y) k
928 -- *** Type 'BinTree'
929 -- | Use @DataKinds@ to define a 'BinTree' of 'Type's.
930 -- Useful for gathering together 'capture's of different 'Type's.
933 | BinTree2 (BinTree a) (BinTree a)
935 -- *** Type family 'AltFromBinTree'
936 type family AltFromBinTree (cs::BinTree Type) :: Type where
937 AltFromBinTree ('BinTree0 x) = x
938 AltFromBinTree ('BinTree2 x y) = AltFromBinTree x :!: AltFromBinTree y
940 instance Trans (Router Server) where
941 type UnTrans (Router Server) = Server
943 unTrans (Router_Any x) = x
944 unTrans (Router_Seg s) = segment s
945 unTrans (Router_Cat x y) = unTrans x <.> unTrans y
946 unTrans (Router_Alt x y) = unTrans x <!> unTrans y
947 unTrans (Router_Map ms) = routing (unRouterUnion <$> ms)
949 unRouterUnion :: RouterUnion Server ms k -> Server ms k
950 unRouterUnion (RouterUnion b2a r) = Server $ (. b2a) <$> unServer (unTrans r)
951 unTrans (Router_Cap n) = capture' n
952 unTrans (Router_Caps xs) = captures $ unTransCaptures xs
954 unTransCaptures :: Captures (Router Server) cs k -> Captures Server cs k
955 unTransCaptures (Captures0 a n r) = Captures0 a n (unTrans r)
956 unTransCaptures (Captures2 x y) = unTransCaptures x `Captures2` unTransCaptures y
958 instance Cat (Router Server) where
960 instance Alt (Router Server) where
962 instance repr ~ Server => HTTP_Path (Router repr) where
963 type PathConstraint (Router repr) a = PathConstraint repr a
965 capture' = Router_Cap
966 instance HTTP_Routing (Router Server) where
967 routing = Router_Map . (RouterUnion id <$>)
968 captures = Router_Caps
969 instance Pro (Router Server)
970 instance HTTP_Query (Router Server)
971 instance HTTP_Header (Router Server)
972 instance HTTP_Body (Router Server)
973 instance HTTP_BodyStream (Router Server)
974 instance HTTP_BasicAuth (Router Server)
975 instance HTTP_Response (Router Server)
976 instance HTTP_ResponseStream (Router Server)
978 -- ** Class 'HTTP_Routing'
979 class HTTP_Routing repr where
980 routing :: Map.Map PathSegment (repr a k) -> repr a k
981 captures :: Captures repr cs k -> repr (AltFromBinTree cs) k
985 HTTP_Routing (UnTrans repr) =>
986 Map.Map PathSegment (repr a k) -> repr a k
987 routing = noTrans . routing . (unTrans <$>)
990 HTTP_Routing (UnTrans repr) =>
991 Captures repr cs k -> repr (AltFromBinTree cs) k
992 captures = noTrans . captures . unTransCaptures
994 unTransCaptures :: Captures repr cs k -> Captures (UnTrans repr) cs k
995 unTransCaptures (Captures0 a n r) = Captures0 a n (unTrans r)
996 unTransCaptures (Captures2 x y) = Captures2 (unTransCaptures x) (unTransCaptures y)
998 instance HTTP_Routing Server where
999 routing ms = Server $ do
1001 { serverState_request = req
1003 case Wai.pathInfo req of
1004 [] -> MC.throw $ Fail st [ServerErrorPath "empty path segment"]
1005 [""] -> MC.throw $ Fail st [ServerErrorPath "trailing slash"]
1007 case Map.lookup curr ms of
1008 Nothing -> MC.throw $ Fail st [ServerErrorPath $ "expected: "<>Text.pack (show (Map.keys ms))<>" but got: "<>curr]
1011 { serverState_request = req{ Wai.pathInfo = next }
1015 captures :: Captures Server cs k -> Server (AltFromBinTree cs) k
1016 captures cs = Server $ do
1018 { serverState_request = req
1020 case Wai.pathInfo req of
1021 [] -> MC.throw $ Fail st [ServerErrorPath "empty"]
1022 [""] -> MC.throw $ Fail st [ServerErrorPath "trailing slash"]
1025 Left errs -> MC.throw $ Fail st
1026 [ServerErrorPath $ "captures: "<>fromString (List.intercalate "|" ((\(name,err) -> name) <$> errs))]
1027 Right a -> unServer a
1029 go :: forall cs k. Captures Server cs k -> Either [(Name,Text)] (Server (AltFromBinTree cs) k)
1030 go (Captures0 (Proxy::Proxy a) name currRepr) =
1031 case Web.parseUrlPiece currSeg of
1032 Left err -> Left [(name,err)]
1035 S.put st { serverState_request = req{ Wai.pathInfo = nextSeg } }
1036 (\x2k a2x -> x2k (a2x a)) <$> unServer currRepr
1037 go (Captures2 x y) =
1041 Left ye -> Left (xe<>ye)
1042 Right a -> Right $ Server $ (\r2k (_l:!:r) -> r2k r) <$> unServer a
1043 Right a -> Right $ Server $ (\l2k (l:!:_r) -> l2k l) <$> unServer a
1045 -- | Traverse a 'Router' to transform 'Router_Alt'
1046 -- into 'Router_Map' when possible.
1047 -- Used in 'server' on the 'Router' inferred from the given API.
1048 router :: Router repr a b -> Router repr a b
1052 (Router_Seg x `Router_Cat` y) ->
1053 Router_Map $ Map.singleton x $ RouterUnion id $ router y
1057 -- Associate to the right
1058 Router_Cat (router x) $
1059 Router_Cat (router y) (router z)
1060 _ -> router xy `Router_Cat` router z
1061 Router_Alt x y -> router_Alt x y
1062 Router_Map xs -> Router_Map $ (<$> xs) $ \(RouterUnion b2a r) -> RouterUnion b2a (router r)
1063 Router_Cap n -> Router_Cap n
1064 Router_Caps cs -> Router_Caps (go cs)
1066 go :: Captures (Router repr) cs k -> Captures (Router repr) cs k
1067 go (Captures0 a n r) = Captures0 a n (router r)
1068 go (Captures2 x y) = Captures2 (go x) (go y)
1070 -- | Insert a 'Router_Map' or 'Router_Caps' if possible
1071 -- or default to a 'Router_Alt'.
1072 router_Alt :: Router repr a k -> Router repr b k -> Router repr (a:!:b) k
1073 -- Merge alternative segments together
1074 router_Alt (Router_Seg x `Router_Cat` xt) (Router_Seg y `Router_Cat` yt) =
1076 Map.singleton x (RouterUnion id $ router xt)
1078 Map.singleton y (RouterUnion id $ router yt)
1079 router_Alt (Router_Seg x `Router_Cat` xt) (Router_Map ys) =
1081 Map.singleton x (RouterUnion id $ router xt)
1083 router_Alt (Router_Map xs) (Router_Seg y `Router_Cat` yt) =
1086 Map.singleton y (RouterUnion id $ router yt)
1087 router_Alt (Router_Map xs) (Router_Map ys) =
1088 Router_Map $ xs `unionRouterMap` ys
1090 -- Merge alternative 'segment's or alternative |capture'|s together.
1091 router_Alt (Router_Cap xn `Router_Cat` x) (Router_Cap yn `Router_Cat` y) =
1093 Captures0 Proxy xn x
1095 Captures0 Proxy yn y
1096 router_Alt (Router_Caps xs) (Router_Caps ys) =
1097 Router_Caps $ xs`Captures2`ys
1098 router_Alt (Router_Cap xn `Router_Cat` x) (Router_Caps ys) =
1099 Router_Caps $ Captures0 Proxy xn x `Captures2` ys
1100 router_Alt (Router_Caps xs) (Router_Cap yn `Router_Cat` y) =
1101 Router_Caps $ xs `Captures2` Captures0 Proxy yn y
1103 router_Alt x (Router_Alt y z) =
1104 case router_Alt y z of
1105 yz@Router_Alt{} -> Router_Alt x yz
1106 yz -> router_Alt x yz
1107 router_Alt (Router_Alt x y) z =
1108 case router_Alt x y of
1109 xy@Router_Alt{} -> Router_Alt xy z
1110 xy -> router_Alt xy z
1111 router_Alt x y = Router_Alt (router x) (router y)