1 {-# LANGUAGE DataKinds #-}
2 {-# LANGUAGE DeriveFunctor #-}
3 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
4 {-# LANGUAGE InstanceSigs #-}
5 {-# LANGUAGE OverloadedStrings #-}
6 {-# LANGUAGE TypeFamilies #-}
7 {-# LANGUAGE TypeOperators #-}
8 module Symantic.HTTP.Server where
10 import Control.Arrow (first)
11 import Control.Monad (Monad(..), unless, sequence, guard)
12 import Control.Monad.Trans.Class (MonadTrans(..))
13 import Control.Monad.Trans.Except (ExceptT(..), runExceptT)
15 import Data.Either (Either(..))
16 import Data.Eq (Eq(..))
17 import Data.Function (($), (.), id)
18 import Data.Functor (Functor, (<$>))
20 import Data.Maybe (Maybe(..), fromMaybe, catMaybes)
21 import Data.Proxy (Proxy(..))
22 import Data.Semigroup (Semigroup(..))
23 import Data.String (String, IsString(..))
24 import Data.Text (Text)
27 import Text.Show (Show(..))
28 import qualified Control.Monad.Classes as MC
29 import qualified Control.Monad.Trans.State as S
30 import qualified Data.ByteString as BS
31 import qualified Data.ByteString.Base64 as BS64
32 import qualified Data.ByteString.Lazy as BSL
33 import qualified Data.List as List
34 import qualified Data.Text.Encoding as Text
35 import qualified Data.Word8 as Word8
36 import qualified Network.HTTP.Media as Media
37 import qualified Network.HTTP.Types as HTTP
38 import qualified Network.HTTP.Types.Header as HTTP
39 import qualified Network.Wai as Wai
40 import qualified Web.HttpApiData as Web
42 import Symantic.HTTP.API
43 import Symantic.HTTP.Mime
46 -- | @Server f k@ is a recipe to produce an 'Wai.Application'
47 -- from handlers 'f' (one per number of alternative routes).
49 -- 'Server' is analogous to a scanf using a format customized for HTTP routing.
51 -- The multiple monad transformers are there to prioritize the errors
52 -- according to the type of check raising them,
53 -- instead of the order of the combinators within an actual API specification.
54 newtype Server f k = Server { unServer ::
56 (ServerCheckT [ServerErrorBody] -- 8th check, 400 error
57 (ServerCheckT [ServerErrorHeader] -- 7th check, 400 error
58 (ServerCheckT [ServerErrorQuery] -- 6th check, 400 error
59 (ServerCheckT [ServerErrorContentType] -- 5th check, 415 error
60 (ServerCheckT [ServerErrorAccept] -- 4th check, 406 error
61 (ServerCheckT [ServerErrorBasicAuth] -- 3rd check, 401 error
62 (ServerCheckT [ServerErrorMethod] -- 2nd check, 405 error
63 (ServerCheckT [ServerErrorPath] -- 1st check, 404 error
68 -- | @'server' api handlers@ returns a 'Wai.Application'
69 -- ready to be given to @Warp.run 80@.
71 Server handlers ServerResponse ->
74 server (Server api) handlers rq re = do
75 lrPath <- runServerChecks api $ ServerState 0 rq
77 Left err -> respondError status404 [] err
80 Left err -> respondError status405 [] err
83 Left (Fail _st (ServerErrorBasicAuth realm err:_)) ->
85 BasicAuth_Unauthorized -> respondError status403 [] err
86 _ -> respondError status401 [(HTTP.hWWWAuthenticate, "Basic realm=\""<>realm<>"\"")] err
89 Left err -> respondError status406 [] err
90 Right lrContentType ->
92 Left err -> respondError status415 [] err
95 Left err -> respondError status400 [] err
98 Left err -> respondError status400 [] err
101 Left err -> respondError status400 [] err
103 let ServerResponse app = a2k handlers in
109 [(HTTP.HeaderName, HeaderValue)] ->
110 err -> IO Wai.ResponseReceived
111 respondError st hs err =
112 -- Trace.trace (show err) $
113 re $ Wai.responseLBS st
114 ( (HTTP.hContentType, Media.renderHeader $ mimeType mimePlainText)
116 ) (fromString $ show err) -- TODO: see what to return in the body
119 -- | Unwrap the 'ExceptT' constructors to reach the 'Either' constructors.
121 S.StateT ServerState (ExceptT e1 (ExceptT e2 (ExceptT e3 (ExceptT e4 (ExceptT e5 (ExceptT e6 (ExceptT e7 (ExceptT e8 IO)))))))) a ->
122 ServerState -> IO (Either e8 (Either e7 (Either e6 (Either e5 (Either e4 (Either e3 (Either e2 (Either e1 (a, ServerState)))))))))
123 runServerChecks s st =
134 -- ** Type 'ServerCheckT'
135 type ServerCheckT e = ExceptT (Fail e)
137 -- *** Type 'RouteResult'
138 type RouteResult e = Either (Fail e)
142 = Fail ServerState e -- ^ Keep trying other paths. 404, 405 or 406.
143 | FailFatal !ServerState !e -- ^ Don't try other paths.
145 failState :: Fail e -> ServerState
146 failState (Fail st _) = st
147 failState (FailFatal st _) = st
148 instance Semigroup e => Semigroup (Fail e) where
149 Fail _ x <> Fail st y = Fail st (x<>y)
150 FailFatal _ x <> Fail st y = FailFatal st (x<>y)
151 Fail _ x <> FailFatal st y = FailFatal st (x<>y)
152 FailFatal _ x <> FailFatal st y = FailFatal st (x<>y)
154 -- ** Type 'ServerState'
155 data ServerState = ServerState
156 { serverState_offset :: Offset -- TODO: remove
157 , serverState_request :: Wai.Request
161 instance Show ServerState where
162 show _ = "ServerState"
164 type instance HttpApiData Server = Web.FromHttpApiData
165 instance Cat Server where
169 repr a b -> repr b c -> repr a c
170 -- NOTE: if x fails, run y to see if it fails on a more prioritized check.
171 -- And if so, fail with y instead of x.
173 -- This long spaghetti code may probably be avoided
174 -- with a more sophisticated 'Server' using a binary tree
175 -- instead of nested 'Either's, so that its 'Monad' instance
176 -- would do the right thing. But to my mind,
177 -- with the very few priorities of checks currently needed,
178 -- this is not worth the cognitive pain to design it.
179 -- A copy/paste/adapt will do for now.
180 Server x <.> Server y = Server $
182 xPath <- liftIO $ runServerChecks x st
184 Left xe -> MC.throw xe
188 yPath <- liftIO $ runServerChecks y (failState xe)
190 Left ye -> MC.throw ye
191 Right _yMethod -> MC.throw xe
195 yPath <- liftIO $ runServerChecks y (failState xe)
197 Left ye -> MC.throw ye
200 Left ye -> MC.throw ye
201 Right _yBasicAuth -> MC.throw xe
205 yPath <- liftIO $ runServerChecks y (failState xe)
207 Left ye -> MC.throw ye
210 Left ye -> MC.throw ye
213 Left ye -> MC.throw ye
214 Right _yAccept -> MC.throw xe
215 Right xContentType ->
218 yPath <- liftIO $ runServerChecks y (failState xe)
220 Left ye -> MC.throw ye
223 Left ye -> MC.throw ye
226 Left ye -> MC.throw ye
229 Left ye -> MC.throw ye
230 Right _yQuery -> MC.throw xe
234 yPath <- liftIO $ runServerChecks y (failState xe)
236 Left ye -> MC.throw ye
239 Left ye -> MC.throw ye
242 Left ye -> MC.throw ye
245 Left ye -> MC.throw ye
248 Left ye -> MC.throw ye
249 Right _yHeader -> MC.throw xe
253 yPath <- liftIO $ runServerChecks y (failState xe)
255 Left ye -> MC.throw ye
258 Left ye -> MC.throw ye
261 Left ye -> MC.throw ye
264 Left ye -> MC.throw ye
267 Left ye -> MC.throw ye
270 Left ye -> MC.throw ye
271 Right _yBody -> MC.throw xe
275 yPath <- liftIO $ runServerChecks y (failState xe)
277 Left ye -> MC.throw ye
280 Left ye -> MC.throw ye
283 Left ye -> MC.throw ye
286 Left ye -> MC.throw ye
289 Left ye -> MC.throw ye
292 Left ye -> MC.throw ye
293 Right _yBody -> MC.throw xe
295 (first (. a2b)) <$> S.runStateT y st'
296 instance Alt Server where
297 Server x <!> Server y = Server $
299 xPath <- liftIO $ runServerChecks x st
300 yPath <- liftIO $ runServerChecks y st
301 let fy = (first (\b2k (_a:!:b) -> b2k b) <$>)
303 Left xe | FailFatal{} <- xe -> MC.throw xe
306 Left ye -> MC.throw (xe<>ye)
308 fy $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $
309 return $ Right yMethod
312 Left xe | FailFatal{} <- xe -> MC.throw xe
315 Left _ye -> MC.throw xe
318 Left ye -> MC.throw (xe<>ye)
320 fy $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $
321 return $ Right $ yBasicAuth
324 Left xe | FailFatal{} <- xe -> MC.throw xe
327 Left _ye -> MC.throw xe
330 Left _ye -> MC.throw xe
333 Left ye -> MC.throw (xe<>ye)
335 fy $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $
336 return $ Right yAccept
339 Left xe | FailFatal{} <- xe -> MC.throw xe
342 Left _ye -> MC.throw xe
345 Left _ye -> MC.throw xe
348 Left _ye -> MC.throw xe
351 Left ye -> MC.throw (xe<>ye)
352 Right yContentType ->
353 fy $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ ExceptT $
354 return $ Right yContentType
355 Right xContentType ->
357 Left xe | FailFatal{} <- xe -> MC.throw xe
360 Left _ye -> MC.throw xe
363 Left _ye -> MC.throw xe
366 Left _ye -> MC.throw xe
369 Left _ye -> MC.throw xe
370 Right yContentType ->
372 Left ye -> MC.throw (xe<>ye)
374 fy $ ExceptT $ ExceptT $ ExceptT $ ExceptT $
375 return $ Right yQuery
378 Left xe | FailFatal{} <- xe -> MC.throw xe
381 Left _ye -> MC.throw xe
384 Left _ye -> MC.throw xe
387 Left _ye -> MC.throw xe
390 Left _ye -> MC.throw xe
391 Right yContentType ->
393 Left _ye -> MC.throw xe
396 Left ye -> MC.throw (xe<>ye)
398 fy $ ExceptT $ ExceptT $ ExceptT $
399 return $ Right yHeader
402 Left xe | FailFatal{} <- xe -> MC.throw xe
405 Left _ye -> MC.throw xe
408 Left _ye -> MC.throw xe
411 Left _ye -> MC.throw xe
414 Left _ye -> MC.throw xe
415 Right yContentType ->
417 Left _ye -> MC.throw xe
420 Left _ye -> MC.throw xe
423 Left ye -> MC.throw (xe<>ye)
425 fy $ ExceptT $ ExceptT $
429 Left xe | FailFatal{} <- xe -> MC.throw xe
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
453 Left ye -> MC.throw (xe<>ye)
458 return $ first (\a2k (a:!:_b) -> a2k a) xr
459 instance Pro Server where
460 dimap a2b _b2a (Server r) = Server $ (\k b2k -> k (b2k . a2b)) <$> r
462 -- ** Type 'ServerErrorPath'
463 data ServerErrorPath = ServerErrorPath Offset Text
465 instance HTTP_Path Server where
466 segment expSegment = Server $ do
468 { serverState_offset = o
469 , serverState_request = req
471 case Wai.pathInfo req of
472 [] -> MC.throw $ Fail st [ServerErrorPath o "segment: empty"]
473 [""] -> MC.throw $ Fail st [ServerErrorPath o "trailing slash"]
475 | curr /= expSegment ->
476 MC.throw $ Fail st [ServerErrorPath o $ "expected: "<>expSegment<>" but got: "<>curr]
479 { serverState_offset = o+1
480 , serverState_request = req{ Wai.pathInfo = next }
483 capture' :: forall a k. HttpApiData Server a => Name -> Server (a -> k) k
484 capture' name = Server $ do
486 { serverState_offset = o
487 , serverState_request = req
489 case Wai.pathInfo req of
490 [] -> MC.throw $ Fail st [ServerErrorPath o "empty"]
491 [""] -> MC.throw $ Fail st [ServerErrorPath o "trailing slash"]
493 case Web.parseUrlPiece curr of
494 Left err -> MC.throw $ Fail st [ServerErrorPath o $ "capture: "<>fromString name<>": "<>err]
497 { serverState_offset = o+1
498 , serverState_request = req{ Wai.pathInfo = next }
501 captureAll = Server $ do
502 req <- S.gets serverState_request
503 return ($ Wai.pathInfo req)
505 -- ** Type 'ServerErrorMethod'
506 data ServerErrorMethod = ServerErrorMethod
509 -- | TODO: add its own error?
510 instance HTTP_Version Server where
511 version exp = Server $ do
513 let got = Wai.httpVersion $ serverState_request st
516 else MC.throw $ Fail st [ServerErrorMethod] -- FIXME: ServerErrorVersion
518 -- ** Type 'ServerErrorAccept'
519 data ServerErrorAccept =
522 (Maybe (Either BS.ByteString MediaType))
525 -- ** Type 'ServerErrorContentType'
526 data ServerErrorContentType = ServerErrorContentType
529 -- ** Type 'ServerErrorQuery'
530 newtype ServerErrorQuery = ServerErrorQuery Text
532 instance HTTP_Query Server where
533 queryParams' name = Server $ do
535 lift $ ExceptT $ ExceptT $ ExceptT $ return $
536 let qs = Wai.queryString $ serverState_request st in
537 let vals = catMaybes $ (<$> qs) $ \(n,v) ->
539 then Web.parseQueryParam . Text.decodeUtf8 <$> v
541 case sequence vals of
542 Left err -> Left $ Fail st [ServerErrorQuery err]
543 Right vs -> Right $ Right $ Right ($ vs)
545 -- ** Type 'ServerErrorHeader'
546 data ServerErrorHeader = ServerErrorHeader
548 instance HTTP_Header Server where
549 header n = Server $ do
551 lift $ ExceptT $ ExceptT $ return $
552 let hs = Wai.requestHeaders $ serverState_request st in
553 case List.lookup n hs of
554 Nothing -> Left $ Fail st [ServerErrorHeader]
555 Just v -> Right $ Right ($ v)
557 -- ** Type 'ServerErrorBasicAuth'
558 data ServerErrorBasicAuth =
559 ServerErrorBasicAuth BasicAuthRealm (BasicAuth ())
562 -- ** Class 'ServerBasicAuthable'
563 class ServerBasicAuthable context a where
564 serverBasicAuthable ::
570 -- | WARNING: current implementation of Basic Access Authentication
571 -- is not immune to certian kinds of timing attacks.
572 -- Decoding payloads does not take a fixed amount of time.
573 instance HTTP_BasicAuth Server where
574 type BasicAuthable Server = ServerBasicAuthable
575 basicAuth' realm context = Server $ do
577 let err e = MC.throw $ Fail st [ServerErrorBasicAuth realm e]
578 case decodeAuthorization $ serverState_request st of
579 Nothing -> err BasicAuth_BadPassword
580 Just (user, pass) -> do
581 liftIO (serverBasicAuthable context user pass) >>= \case
582 BasicAuth_BadPassword -> err BasicAuth_BadPassword
583 BasicAuth_NoSuchUser -> err BasicAuth_NoSuchUser
584 BasicAuth_Unauthorized -> err BasicAuth_Unauthorized
585 BasicAuth_Authorized a -> return ($ a)
587 -- | Find and decode an 'Authorization' header from the request as a Basic Auth
588 decodeAuthorization :: Wai.Request -> Maybe (BasicAuthName, BasicAuthPass)
589 decodeAuthorization req = do
590 hAuthorization <- List.lookup "Authorization" $ Wai.requestHeaders req
591 let (basic, rest) = BS.break Word8.isSpace hAuthorization
592 guard (BS.map Word8.toLower basic == "basic")
593 let decoded = BS64.decodeLenient (BS.dropWhile Word8.isSpace rest)
594 let (user, colon_pass) = BS.break (== Word8._colon) decoded
595 (_, pass) <- BS.uncons colon_pass
598 -- ** Type 'ServerErrorBody'
599 newtype ServerErrorBody = ServerErrorBody String
602 -- *** Type 'ServerBodyArg'
603 newtype ServerBodyArg mt a = ServerBodyArg a
605 instance HTTP_Body Server where
606 type BodyArg Server = ServerBodyArg
609 MimeUnserialize a mt =>
610 MimeSerialize a mt =>
612 repr (BodyArg repr mt a -> k) k
615 lift $ ExceptT $ ExceptT $ ExceptT $ ExceptT $ do
616 let hs = Wai.requestHeaders $ serverState_request st
617 let expContentType = (Proxy::Proxy mt)
619 -- DOC: http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1
620 -- DOC: http://www.w3.org/2001/tag/2002/0129-mime
621 fromMaybe "application/octet-stream" $
622 List.lookup HTTP.hContentType hs
623 case Media.mapContentMedia
624 [ ( mimeType expContentType
625 , mimeUnserialize expContentType )
627 Nothing -> return $ Left $ Fail st [ServerErrorContentType]
628 Just unSerialize -> do
629 bodyBS <- liftIO $ Wai.requestBody $ serverState_request st
630 return $ Right $ Right $ Right $
631 -- NOTE: delay unSerialize after all checks
632 case unSerialize $ BSL.fromStrict bodyBS of
633 Left err -> Left $ Fail st [ServerErrorBody err]
634 Right a -> Right ($ ServerBodyArg a)
636 -- ** Type 'ServerResponse'
637 newtype ServerResponse = ServerResponse
638 ( -- the request made to the server
640 -- the continuation for the server to respond
641 (Wai.Response -> IO Wai.ResponseReceived) ->
642 IO Wai.ResponseReceived
644 instance Show ServerResponse where
645 show _ = "ServerResponse"
647 -- *** Type 'ServerResponseArg'
648 newtype ServerResponseArg mt a =
651 HTTP.ResponseHeaders ->
654 instance HTTP_Response Server where
655 type Response Server = ServerResponse
656 type ResponseArg Server = ServerResponseArg
659 MimeUnserialize a mt =>
660 MimeSerialize a mt =>
664 repr (ResponseArg repr mt a -> k) k
665 response expMethod = Server $ do
667 { serverState_offset = o
668 , serverState_request = req
671 -- Check the path has been fully consumed
672 unless (List.null $ Wai.pathInfo req) $
673 MC.throw $ Fail st [ServerErrorPath o "path is longer"]
676 let reqMethod = Wai.requestMethod $ serverState_request st
677 unless (reqMethod == expMethod
678 || reqMethod == HTTP.methodHead
679 && expMethod == HTTP.methodGet) $
680 MC.throw $ Fail st [ServerErrorMethod]
682 -- Check the Accept header
683 let reqHeaders = Wai.requestHeaders $ serverState_request st
684 let expAccept = (Proxy::Proxy mt)
686 case List.lookup HTTP.hAccept reqHeaders of
687 Nothing -> return expAccept
689 case Media.parseAccept h of
690 Nothing -> MC.throw $ Fail st
691 [ServerErrorAccept (mimeType expAccept) (Just (Left h))]
693 | mimeType expAccept`Media.matches`gotAccept -> return expAccept
694 -- FIXME: return gotAccept, maybe with GADTs
695 | otherwise -> MC.throw $ Fail st
696 [ServerErrorAccept (mimeType expAccept) (Just (Right gotAccept))]
699 return ($ ServerResponseArg $ \s hs a ->
701 ((HTTP.hContentType, Media.renderHeader $ mimeType reqAccept):hs)
702 (if reqMethod == HTTP.methodHead
704 else mimeSerialize reqAccept a))
707 status200 :: HTTP.Status
708 status200 = HTTP.mkStatus 200 "Success"
709 status400 :: HTTP.Status
710 status400 = HTTP.mkStatus 400 "Bad Request"
711 status401 :: HTTP.Status
712 status401 = HTTP.mkStatus 401 "Unauthorized"
713 status403 :: HTTP.Status
714 status403 = HTTP.mkStatus 403 "Forbidden"
715 status404 :: HTTP.Status
716 status404 = HTTP.mkStatus 404 "Not Found"
717 status405 :: HTTP.Status
718 status405 = HTTP.mkStatus 405 "Method Not Allowed"
719 status406 :: HTTP.Status
720 status406 = HTTP.mkStatus 406 "Not Acceptable"
721 status415 :: HTTP.Status
722 status415 = HTTP.mkStatus 415 "Unsupported Media Type"
725 liftIO :: MC.MonadExec IO m => IO a -> m a
727 {-# INLINE liftIO #-}