1 {-# LANGUAGE GADTs #-} -- for 'Router'
2 {-# LANGUAGE ConstraintKinds #-}
3 {-# LANGUAGE DataKinds #-} -- for 'Tree'
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
878 -- | 'Trans'form a 'Server' to merge 'Alt'ernative 'segment's into a 'routing'.
879 data Router repr a b where
880 -- | Lift any @(repr)@ into 'Router', those not useful to segregate
881 -- wrt. the 'Trans'formation performed, aka. 'noTrans'.
882 Router_Any :: repr a b -> Router repr a b
883 -- | Represent 'segment'.
884 Router_Seg :: PathSegment -> Router repr a a
885 -- | Represent ('<.>').
886 Router_Cat :: Router repr a b -> Router repr b c -> Router repr a c
887 -- | Represent 'routing'.
888 Router_Map :: Map.Map PathSegment (Router repr a k) -> Router repr a k
889 -- | Represent ('<!>').
890 Router_Alt :: Router repr a k -> Router repr b k -> Router repr (a:!:b) k
891 -- | Used to transform 'Router_Alt' into 'Router_Map',
892 -- while following the way ('<!>') combinators are associated in the API.
893 -- Use 'router_AltL' to insert it correctly.
894 Router_AltL :: Router repr a k -> Router repr (a:!:b) k
895 -- | Used to transform 'Router_Alt' into 'Router_Map'
896 -- while following the way ('<!>') combinators are associated in the API.
897 -- Use 'router_AltR' to insert it correctly.
898 Router_AltR :: Router repr b k -> Router repr (a:!:b) k
899 -- Router_AltB :: Router repr (a:!:a) k -> Router repr a k
900 -- Router_AltB :: Router repr (a:!:a) k -> Router repr a k
901 Router_Cap :: PathConstraint repr a => Name -> Router repr (a->k) k
903 Router_Caps :: Alts2Caps xs =>
904 Captures (Router repr) xs k ->
905 Router repr (Caps2Alts xs) k
907 -- | Use @DataKinds@ to define a 'Tree' of 'Type's.
908 -- Useful for factorizing 'capture's of different 'Type's.
911 | Tree2 (Tree a) (Tree a)
913 -- ** Type 'Captures'
914 data Captures repr (cs::Tree Type) k where
915 Captures0 :: PathConstraint repr a =>
916 Proxy a -> Name -> repr x k ->
917 Captures repr ('Tree0 (a->x)) k
918 Captures2 :: Captures repr x k ->
920 Captures repr ('Tree2 x y) k
923 data Capture (cs::Tree Type) where
924 Capture0 :: (a->x) -> Capture ('Tree0 (a->x))
925 Capture2 :: Capture x -> Capture y -> Capture ('Tree2 x y)
927 -- ** Type family 'Caps2Alts'
928 type family Caps2Alts (cs::Tree Type) :: Type where
929 Caps2Alts ('Tree0 x) = x
930 Caps2Alts ('Tree2 x y) = Caps2Alts x :!: Caps2Alts y
932 -- ** Class 'Alts2Caps'
933 class Alts2Caps cs where
934 alts2caps :: Caps2Alts cs -> Capture cs
935 instance Alts2Caps ('Tree0 (a->x)) where
936 alts2caps a = Capture0 a
937 instance (Alts2Caps x, Alts2Caps y) => Alts2Caps ('Tree2 x y) where
938 alts2caps (a:!:b) = Capture2 (alts2caps a) (alts2caps b)
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_AltL x) = Server $ (\a2k (a:!:_b) -> a2k a) <$> unServer (unTrans x)
948 unTrans (Router_AltR x) = Server $ (\b2k (_a:!:b) -> b2k b) <$> unServer (unTrans x)
949 unTrans (Router_Map ms) = routing (unTrans <$> ms)
950 unTrans (Router_Cap n) = capture' n
951 unTrans (Router_Caps xs) =
952 Server $ (\c2k -> c2k . alts2caps) <$> unServer
953 (captures (unTransCaptures xs))
955 unTransCaptures :: Captures (Router Server) cs k -> Captures Server cs k
956 unTransCaptures (Captures0 a n r) = Captures0 a n (unTrans r)
957 unTransCaptures (Captures2 x y) = unTransCaptures x `Captures2` unTransCaptures y
959 instance Cat (Router Server) where
961 instance Alt (Router Server) where
963 instance repr ~ Server => HTTP_Path (Router repr) where
964 type PathConstraint (Router repr) a = PathConstraint repr a
966 capture' = Router_Cap
967 instance HTTP_Routing (Router Server) where
969 captures = Router_Any . captures . unTransCaptures
971 unTransCaptures :: Captures (Router Server) cs k -> Captures Server cs k
972 unTransCaptures (Captures0 a n r) = Captures0 a n (unTrans r)
973 unTransCaptures (Captures2 x y) = unTransCaptures x `Captures2` unTransCaptures y
974 instance Pro (Router Server)
975 instance HTTP_Query (Router Server)
976 instance HTTP_Header (Router Server)
977 instance HTTP_Body (Router Server)
978 instance HTTP_BodyStream (Router Server)
979 instance HTTP_BasicAuth (Router Server)
980 instance HTTP_Response (Router Server)
981 instance HTTP_ResponseStream (Router Server)
983 -- ** Class 'HTTP_Routing'
984 class HTTP_Routing repr where
985 routing :: Map.Map PathSegment (repr a k) -> repr a k
986 captures :: Captures repr cs k -> repr (Capture cs) k
990 HTTP_Routing (UnTrans repr) =>
991 Map.Map PathSegment (repr a k) -> repr a k
992 routing = noTrans . routing . (unTrans <$>)
993 {- NOTE: cannot define this default simply,
994 - due to the need for: forall a. PathConstraint (Router repr) a ~ PathConstraint repr a
995 - so let's just define it in (HTTP_Routing (Router Server))
998 HTTP_Routing (UnTrans repr) =>
999 Captures repr cs k -> repr (Capture cs) k
1000 captures = noTrans . captures . unTransCaptures
1002 unTransCaptures :: Captures repr cs k -> Captures (UnTrans repr) cs k
1003 unTransCaptures (Captures0 a n r) = Captures0 a n (unTrans r)
1004 unTransCaptures (Captures2 x y) = Captures2 (unTransCaptures x) (unTransCaptures y)
1007 instance HTTP_Routing Server where
1008 routing ms = Server $ do
1010 { serverState_request = req
1012 case Wai.pathInfo req of
1013 [] -> MC.throw $ Fail st [ServerErrorPath "empty path segment"]
1014 [""] -> MC.throw $ Fail st [ServerErrorPath "trailing slash"]
1016 case Map.lookup curr ms of
1017 Nothing -> MC.throw $ Fail st [ServerErrorPath $ "expected: "<>Text.pack (show (Map.keys ms))<>" but got: "<>curr]
1020 { serverState_request = req{ Wai.pathInfo = next }
1024 captures :: Captures Server cs k -> Server (Capture cs) k
1025 captures cs = Server $ do
1027 { serverState_request = req
1029 case Wai.pathInfo req of
1030 [] -> MC.throw $ Fail st [ServerErrorPath "empty"]
1031 [""] -> MC.throw $ Fail st [ServerErrorPath "trailing slash"]
1034 Left errs -> MC.throw $ Fail st
1035 [ServerErrorPath $ "capture: "<>fromString (List.intercalate "|" ((\(name,err) -> name) <$> errs))]
1036 Right a -> unServer a
1038 go :: forall cs k. Captures Server cs k -> Either [(Name,Text)] (Server (Capture cs) k)
1039 go (Captures0 (Proxy::Proxy cap) name currRepr) =
1040 case Web.parseUrlPiece currSeg of
1041 Left err -> Left [(name,err)]
1042 Right (a::cap) -> Right $ Server $ do
1043 S.put st { serverState_request = req{ Wai.pathInfo = nextSeg } }
1044 (\x2k (Capture0 a2x) -> x2k (a2x a)) <$> unServer currRepr
1045 go (Captures2 x y) =
1049 Left ye -> Left (xe<>ye)
1050 Right a -> Right $ Server $ (\c2k (Capture2 _x' y') -> c2k y') <$> unServer a
1051 Right a -> Right $ Server $ (\c2k (Capture2 x' _y') -> c2k x') <$> unServer a
1053 -- | Traverse a 'Router' to transform 'Router_Alt'
1054 -- into 'Router_Map' when possible.
1055 -- Used in 'server' on the 'Router' inferred from the given API.
1056 -- router :: Router repr a b -> Router repr a b
1057 router :: Router repr a b -> Router repr a b
1058 router i = case {-Dbg.trace ("router: " <> show i)-} i of
1062 Router_Cat (Router_Seg s) (l`Router_Alt`r) ->
1063 (Router_Cat (Router_Seg s) l) `Router_Alt`
1064 (Router_Cat (Router_Seg s) r)
1066 Router_Cat x y -> router x `Router_Cat` router y
1067 Router_Alt x y -> router_Alt x y
1068 Router_AltL x -> Router_AltL (router x)
1069 Router_AltR x -> Router_AltR (router x)
1070 Router_Map xs -> Router_Map (router <$> xs)
1071 Router_Cap n -> Router_Cap n
1072 Router_Caps cs -> Router_Caps (go cs)
1074 go :: Captures (Router repr) cs k -> Captures (Router repr) cs k
1075 go (Captures0 a n r) = Captures0 a n (router r)
1076 go (Captures2 x y) = Captures2 (go x) (go y)
1078 router_Cat :: repr ~ Server => Router repr a b -> Router repr b c -> Router repr a c
1081 ({-Dbg.trace ("cat: x: " <> show x0)-} x0)
1082 ({-Dbg.trace ("cat: y: " <> show y0)-} y0) in
1083 {-Dbg.trace ("cat: r: " <> show r)-} r
1085 go x y = Router_Cat x y
1087 go (Router_Seg x `Router_Cat` xt)
1088 (Router_Seg y `Router_Cat` yt) =
1091 -- | Insert a 'Router_Alt' or a 'Router_Map' if possible.
1094 PathConstraint (Router repr) a ~ PathConstraint repr a =>
1095 PathConstraint (Router repr) b ~ PathConstraint repr b =>
1098 Router repr a k -> Router repr b k -> Router repr (a:!:b) k
1101 ({-Dbg.trace ("alt: x: " <> show x0)-} x0)
1102 ({-Dbg.trace ("alt: y: " <> show y0)-} y0) in
1103 {-Dbg.trace ("alt: r: " <> show r)-} r
1105 go :: forall a b k repr.
1108 PathConstraint (Router repr) a ~ PathConstraint repr a =>
1109 PathConstraint (Router repr) b ~ PathConstraint repr b =>
1111 Router repr a k -> Router repr b k -> Router repr (a:!:b) k
1112 go (Router_Seg x `Router_Cat` xt)
1113 (Router_Seg y `Router_Cat` yt)
1114 | x == y = Router_Seg y `Router_Cat` (xt `router_Alt` yt)
1116 Router_Map $ Map.fromListWith
1117 (\_xt _yt -> xt `router_Alt` yt)
1118 [ (x, router_AltL xt)
1119 , (y, router_AltR yt)
1121 go (Router_Seg x `Router_Cat` xt) (Router_Map ys) =
1124 (Map.traverseMissing $ const $ return . router_AltL)
1125 (Map.traverseMissing $ const $ return . router_AltR)
1126 (Map.zipWithAMatched $ const $ \a b -> return $ a`router_Alt`b)
1127 (Map.singleton x xt) ys
1128 go (Router_Map xs) (Router_Seg y `Router_Cat` yt) =
1131 (Map.traverseMissing $ const $ return . router_AltL)
1132 (Map.traverseMissing $ const $ return . router_AltR)
1133 (Map.zipWithAMatched $ const $ \a b -> return $ a`router_Alt`b)
1134 xs (Map.singleton y yt)
1135 go (Router_Map xs) (Router_Map ys) =
1138 (Map.traverseMissing $ const $ return . router_AltL)
1139 (Map.traverseMissing $ const $ return . router_AltR)
1140 (Map.zipWithAMatched $ const $ \a b -> return $ a`router_Alt`b)
1142 go (Router_Cat (Router_Cat x y) z) w =
1143 router_Alt (Router_Cat x (Router_Cat y z)) w
1144 go w (Router_Cat (Router_Cat x y) z) =
1145 router_Alt w (Router_Cat x (Router_Cat y z))
1147 go (Router_Cat (Router_Cap xn) x) (Router_Cat (Router_Cap yn) y) =
1148 Router_Caps $ Captures2 (Captures0 Proxy xn x) (Captures0 Proxy yn y)
1149 go (Router_Cat (Router_Cap xn) x) (Router_Caps ys) =
1150 Router_Caps $ Captures2 (Captures0 Proxy xn x) ys
1151 go (Router_Caps xs) (Router_Cat (Router_Cap yn) y) =
1152 Router_Caps $ Captures2 xs (Captures0 Proxy yn y)
1153 go (Router_Caps xs) (Router_Caps ys) =
1154 Router_Caps $ Captures2 xs ys
1156 go x (Router_Alt y z) =
1157 case router_Alt y z of
1158 yz@Router_Alt{} -> Router_Alt x yz
1159 yz -> router_Alt x yz
1160 go (Router_Alt x y) z =
1161 case router_Alt x y of
1162 xy@Router_Alt{} -> Router_Alt xy z
1163 xy -> router_Alt xy z
1164 go x y = Router_Alt (router x) (router y)
1166 -- | Insert a 'Router_AltL' as deep as possible
1167 -- in order to not prevent the transformation
1168 -- of 'Router_Alt' into 'Router_Map' in 'router_Alt'.
1169 router_AltL :: Router repr a k -> Router repr (a:!:b) k
1171 Router_Cat (Router_Seg x) y -> Router_Cat (Router_Seg x) (router_AltL y)
1172 Router_Cat x y -> Router_Cat (router_AltL x) y
1173 Router_Alt x y -> router_AltL (Router_Alt x y)
1174 Router_Map xs -> Router_Map (router_AltL <$> xs)
1176 Router_Caps (Captures0 a n r) ->
1177 let () = Router_Caps (Captures0 a n (router_AltL r)) in
1179 Router_Caps xs -> Router_Caps (mapCaptures xs)
1181 -- mapCaptures :: Captures (Router repr) a k -> Captures (Router repr) (a:!:b) k
1182 mapCaptures (Captures0 a n r) = Captures0 a n (router_AltL r)
1183 -- mapCaptures (Captures2 x y) = Captures2 (mapCaptures x) (mapCaptures y)
1187 -- | Like 'router_AltL' but for 'Router_AltR'.
1188 router_AltR :: Router repr b k -> Router repr (a:!:b) k
1190 Router_Cat (Router_Seg x) y -> Router_Cat (Router_Seg x) (router_AltR y)
1191 Router_Cat x y -> Router_Cat (router_AltR x) y
1192 Router_Alt x y -> router_AltR (Router_Alt x y)
1193 Router_Map xs -> Router_Map (router_AltR <$> xs)