Safe Haskell | None |
---|---|
Language | Haskell2010 |
Servant.Server
Contents
Description
- serve :: HasServer api '[] => Proxy api -> Server api -> Application
- serveWithContext :: HasServer api context => Proxy api -> Context context -> Server api -> Application
- toApplication :: RoutingApplication -> Application
- class HasServer api context where
- type Server api = ServerT api Handler
- data EmptyServer
- emptyServer :: ServerT EmptyAPI m
- newtype Handler a = Handler {
- runHandler' :: ExceptT ServantErr IO a
- runHandler :: Handler a -> IO (Either ServantErr a)
- layout :: HasServer api '[] => Proxy api -> Text
- layoutWithContext :: HasServer api context => Proxy api -> Context context -> Text
- enter :: Enter typ m n ret => (:~>) * m n -> typ -> ret
- newtype (k :~> f) g :: forall k. (k -> *) -> (k -> *) -> * = NT {}
- liftNat :: (MonadTrans t, Monad m) => (:~>) * m (t m)
- runReaderTNat :: r -> (:~>) * (ReaderT * r m) m
- evalStateTLNat :: Monad m => s -> (:~>) * (StateT s m) m
- evalStateTSNat :: Monad m => s -> (:~>) * (StateT s m) m
- logWriterTLNat :: MonadIO m => (w -> IO ()) -> (:~>) * (WriterT w m) m
- logWriterTSNat :: MonadIO m => (w -> IO ()) -> (:~>) * (WriterT w m) m
- hoistNat :: (MFunctor t, Monad m) => (:~>) * m n -> (:~>) * (t m) (t n)
- embedNat :: (MMonad t, Monad n) => (:~>) * m (t n) -> (:~>) * (t m) (t n)
- squashNat :: (Monad m, MMonad t) => (:~>) * (t (t m)) (t m)
- generalizeNat :: Applicative m => (:~>) * Identity m
- tweakResponse :: (RouteResult Response -> RouteResult Response) -> Router env -> Router env
- data Context contextTypes where
- EmptyContext :: Context '[]
- (:.) :: x -> Context xs -> Context (x ': xs)
- class HasContextEntry context val where
- data NamedContext name subContext = NamedContext (Context subContext)
- descendIntoNamedContext :: forall context name subContext. HasContextEntry context (NamedContext name subContext) => Proxy (name :: Symbol) -> Context context -> Context subContext
- newtype BasicAuthCheck usr = BasicAuthCheck {
- unBasicAuthCheck :: BasicAuthData -> IO (BasicAuthResult usr)
- data BasicAuthResult usr
- = Unauthorized
- | BadPassword
- | NoSuchUser
- | Authorized usr
- data ServantErr = ServantErr {
- errHTTPCode :: Int
- errReasonPhrase :: String
- errBody :: ByteString
- errHeaders :: [Header]
- err300 :: ServantErr
- err301 :: ServantErr
- err302 :: ServantErr
- err303 :: ServantErr
- err304 :: ServantErr
- err305 :: ServantErr
- err307 :: ServantErr
- err400 :: ServantErr
- err401 :: ServantErr
- err402 :: ServantErr
- err403 :: ServantErr
- err404 :: ServantErr
- err405 :: ServantErr
- err406 :: ServantErr
- err407 :: ServantErr
- err409 :: ServantErr
- err410 :: ServantErr
- err411 :: ServantErr
- err412 :: ServantErr
- err413 :: ServantErr
- err414 :: ServantErr
- err415 :: ServantErr
- err416 :: ServantErr
- err417 :: ServantErr
- err418 :: ServantErr
- err422 :: ServantErr
- err500 :: ServantErr
- err501 :: ServantErr
- err502 :: ServantErr
- err503 :: ServantErr
- err504 :: ServantErr
- err505 :: ServantErr
- type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived
- newtype Tagged k s b :: forall k. k -> * -> * = Tagged {
- unTagged :: b
Run a wai application from an API
serve :: HasServer api '[] => Proxy api -> Server api -> Application #
serve
allows you to implement an API and produce a wai Application
.
Example:
type MyApi = "books" :> Get '[JSON] [Book] -- GET /books :<|> "books" :> ReqBody Book :> Post '[JSON] Book -- POST /books server :: Server MyApi server = listAllBooks :<|> postBook where listAllBooks = ... postBook book = ... myApi :: Proxy MyApi myApi = Proxy app :: Application app = serve myApi server main :: IO () main = Network.Wai.Handler.Warp.run 8080 app
serveWithContext :: HasServer api context => Proxy api -> Context context -> Server api -> Application #
Construct a wai Application from an API
Handlers for all standard combinators
class HasServer api context where #
Minimal complete definition
Instances
HasServer * EmptyAPI context # | The server for an type MyApi = "nothing" :> EmptyApi server :: Server MyApi server = emptyAPIServer |
HasServer * Raw context # | Just pass the request to the underlying application and serve its response. Example: type MyApi = "images" :> Raw server :: Server MyApi server = serveDirectory "/var/www/images" |
(HasServer * a context, HasServer * b context) => HasServer * ((:<|>) a b) context # | A server for type MyApi = "books" :> Get '[JSON] [Book] -- GET /books :<|> "books" :> ReqBody Book :> Post '[JSON] Book -- POST /books server :: Server MyApi server = listAllBooks :<|> postBook where listAllBooks = ... postBook book = ... |
(HasContextEntry context (NamedContext name subContext), HasServer * subApi subContext) => HasServer * (WithNamedContext name subContext subApi) context # | |
(KnownSymbol realm, HasServer k api context, HasContextEntry context (BasicAuthCheck usr)) => HasServer * ((:>) k * (BasicAuth realm usr) api) context # | Basic Authentication |
HasServer k api context => HasServer * ((:>) k * HttpVersion api) context # | |
HasServer k api context => HasServer * ((:>) k * Vault api) context # | |
HasServer k api context => HasServer * ((:>) k * IsSecure api) context # | |
HasServer k api context => HasServer * ((:>) k * RemoteHost api) context # | |
(KnownSymbol path, HasServer k api context) => HasServer * ((:>) k Symbol path api) context # | Make sure the incoming request starts with |
(AllCTUnrender list a, HasServer k api context) => HasServer * ((:>) k * (ReqBody * list a) api) context # | If you use All it asks is for a Example: type MyApi = "books" :> ReqBody '[JSON] Book :> Post '[JSON] Book server :: Server MyApi server = postBook where postBook :: Book -> Handler Book postBook book = ...insert into your db... |
(KnownSymbol sym, HasServer k api context) => HasServer * ((:>) k * (QueryFlag sym) api) context # | If you use Example: type MyApi = "books" :> QueryFlag "published" :> Get '[JSON] [Book] server :: Server MyApi server = getBooks where getBooks :: Bool -> Handler [Book] getBooks onlyPublished = ...return all books, or only the ones that are already published, depending on the argument... |
(KnownSymbol sym, FromHttpApiData a, HasServer k api context) => HasServer * ((:>) k * (QueryParams * sym a) api) context # | If you use This lets servant worry about looking up 0 or more values in the query string
associated to You can control how the individual values are converted from Example: type MyApi = "books" :> QueryParams "authors" Text :> Get '[JSON] [Book] server :: Server MyApi server = getBooksBy where getBooksBy :: [Text] -> Handler [Book] getBooksBy authors = ...return all books by these authors... |
(KnownSymbol sym, FromHttpApiData a, HasServer k api context) => HasServer * ((:>) k * (QueryParam * sym a) api) context # | If you use This lets servant worry about looking it up in the query string
and turning it into a value of the type you specify, enclosed
in You can control how it'll be converted from Example: type MyApi = "books" :> QueryParam "author" Text :> Get '[JSON] [Book] server :: Server MyApi server = getBooksBy where getBooksBy :: Maybe Text -> Handler [Book] getBooksBy Nothing = ...return all books... getBooksBy (Just author) = ...return books by the given author... |
(KnownSymbol sym, FromHttpApiData a, HasServer k api context) => HasServer * ((:>) k * (Header sym a) api) context # | If you use All it asks is for a Example: newtype Referer = Referer Text deriving (Eq, Show, FromHttpApiData) -- GET /view-my-referer type MyApi = "view-my-referer" :> Header "Referer" Referer :> Get '[JSON] Referer server :: Server MyApi server = viewReferer where viewReferer :: Referer -> Handler referer viewReferer referer = return referer |
(KnownSymbol capture, FromHttpApiData a, HasServer k sublayout context) => HasServer * ((:>) k * (CaptureAll * capture a) sublayout) context # | If you use You can control how they'll be converted from Example: type MyApi = "src" :> CaptureAll "segments" Text :> Get '[JSON] SourceFile server :: Server MyApi server = getSourceFile where getSourceFile :: [Text] -> Handler Book getSourceFile pathSegments = ... |
(KnownSymbol capture, FromHttpApiData a, HasServer k api context) => HasServer * ((:>) k * (Capture * capture a) api) context # | If you use You can control how it'll be converted from Example: type MyApi = "books" :> Capture "isbn" Text :> Get '[JSON] Book server :: Server MyApi server = getBook where getBook :: Text -> Handler Book getBook isbn = ... |
(AllCTRender ctypes a, ReflectMethod k1 method, KnownNat status, GetHeaders (Headers h a)) => HasServer * (Verb * k1 method status ctypes (Headers h a)) context # | |
(AllCTRender ctypes a, ReflectMethod k1 method, KnownNat status) => HasServer * (Verb * k1 method status ctypes a) context # | |
emptyServer :: ServerT EmptyAPI m #
Server for EmptyAPI
Constructors
Handler | |
Fields
|
runHandler :: Handler a -> IO (Either ServantErr a) #
Debugging the server layout
layout :: HasServer api '[] => Proxy api -> Text #
The function layout
produces a textual description of the internal
router layout for debugging purposes. Note that the router layout is
determined just by the API, not by the handlers.
Example:
For the following API
type API = "a" :> "d" :> Get '[JSON] NoContent :<|> "b" :> Capture "x" Int :> Get '[JSON] Bool :<|> "c" :> Put '[JSON] Bool :<|> "a" :> "e" :> Get '[JSON] Int :<|> "b" :> Capture "x" Int :> Put '[JSON] Bool :<|> Raw
we get the following output:
/ ├─ a/ │ ├─ d/ │ │ └─• │ └─ e/ │ └─• ├─ b/ │ └─ <capture>/ │ ├─• │ ┆ │ └─• ├─ c/ │ └─• ┆ └─ <raw>
Explanation of symbols:
├
- Normal lines reflect static branching via a table.
a/
- Nodes reflect static path components.
─•
- Leaves reflect endpoints.
<capture>/
- This is a delayed capture of a path component.
<raw>
- This is a part of the API we do not know anything about.
┆
- Dashed lines suggest a dynamic choice between the part above and below. If there is a success for fatal failure in the first part, that one takes precedence. If both parts fail, the "better" error code will be returned.
Enter
Sometimes our cherished ExceptT
monad isn't quite the type you'd like for
your handlers. Maybe you want to thread some configuration in a Reader
monad. Or have your types ensure that your handlers don't do any IO. Enter
enter
.
With enter
, you can provide a function, wrapped in the `(:~>)` / NT
newtype, to convert any number of endpoints from one type constructor to
another. For example
Note: Server
Raw
can also be entered. It will be retagged.
>>>
import Control.Monad.Reader
>>>
import qualified Control.Category as C
>>>
type ReaderAPI = "ep1" :> Get '[JSON] Int :<|> "ep2" :> Get '[JSON] String :<|> Raw :<|> EmptyAPI
>>>
let readerServer = return 1797 :<|> ask :<|> Tagged (error "raw server") :<|> emptyServer :: ServerT ReaderAPI (Reader String)
>>>
let nt = generalizeNat C.. (runReaderTNat "hi") :: Reader String :~> Handler
>>>
let mainServer = enter nt readerServer :: Server ReaderAPI
Basic functions and datatypes
newtype (k :~> f) g :: forall k. (k -> *) -> (k -> *) -> * infixr 0 #
A natural transformation suitable for storing in a container.
Nat
utilities
logWriterTLNat :: MonadIO m => (w -> IO ()) -> (:~>) * (WriterT w m) m #
Like logWriterTSNat
, but for lazy WriterT
.
logWriterTSNat :: MonadIO m => (w -> IO ()) -> (:~>) * (WriterT w m) m #
Log the contents of WriterT
with the function provided as the
first argument, and return the value of the WriterT
computation
Functions based on mmorph
generalizeNat :: Applicative m => (:~>) * Identity m #
Like mmorph
's generalize
.
tweakResponse :: (RouteResult Response -> RouteResult Response) -> Router env -> Router env #
Apply a transformation to the response of a Router
.
Context
data Context contextTypes where #
Context
s are used to pass values to combinators. (They are not meant
to be used to pass parameters to your handlers, i.e. they should not replace
any custom ReaderT
-monad-stack that you're using
with Enter
.) If you don't use combinators that
require any context entries, you can just use serve
as always.
If you are using combinators that require a non-empty Context
you have to
use serveWithContext
and pass it a Context
that contains all
the values your combinators need. A Context
is essentially a heterogenous
list and accessing the elements is being done by type (see getContextEntry
).
The parameter of the type Context
is a type-level list reflecting the types
of the contained context entries. To create a Context
with entries, use the
operator (
::.
)
>>>
:type True :. () :. EmptyContext
True :. () :. EmptyContext :: Context '[Bool, ()]
Constructors
EmptyContext :: Context '[] | |
(:.) :: x -> Context xs -> Context (x ': xs) infixr 5 |
class HasContextEntry context val where #
This class is used to access context entries in Context
s. getContextEntry
returns the first value where the type matches:
>>>
getContextEntry (True :. False :. EmptyContext) :: Bool
True
If the Context
does not contain an entry of the requested type, you'll get
an error:
>>>
getContextEntry (True :. False :. EmptyContext) :: String
... ...No instance for (HasContextEntry '[] [Char]) ...
Minimal complete definition
Methods
getContextEntry :: Context context -> val #
Instances
HasContextEntry ((:) * val xs) val # | |
HasContextEntry xs val => HasContextEntry ((:) * notIt xs) val # | |
NamedContext
data NamedContext name subContext #
Normally context entries are accessed by their types. In case you need
to have multiple values of the same type in your Context
and need to access
them, we provide NamedContext
. You can think of it as sub-namespaces for
Context
s.
Constructors
NamedContext (Context subContext) |
descendIntoNamedContext :: forall context name subContext. HasContextEntry context (NamedContext name subContext) => Proxy (name :: Symbol) -> Context context -> Context subContext #
descendIntoNamedContext
allows you to access NamedContext
s. Usually you
won't have to use it yourself but instead use a combinator like
WithNamedContext
.
This is how descendIntoNamedContext
works:
>>>
:set -XFlexibleContexts
>>>
let subContext = True :. EmptyContext
>>>
:type subContext
subContext :: Context '[Bool]>>>
let parentContext = False :. (NamedContext subContext :: NamedContext "subContext" '[Bool]) :. EmptyContext
>>>
:type parentContext
parentContext :: Context '[Bool, NamedContext "subContext" '[Bool]]>>>
descendIntoNamedContext (Proxy :: Proxy "subContext") parentContext :: Context '[Bool]
True :. EmptyContext
Basic Authentication
newtype BasicAuthCheck usr #
Datatype wrapping a function used to check authentication.
Constructors
BasicAuthCheck | |
Fields
|
Instances
Functor BasicAuthCheck # | |
Generic (BasicAuthCheck usr) # | |
type Rep (BasicAuthCheck usr) # | |
data BasicAuthResult usr #
servant-server's current implementation of basic authentication is not immune to certian kinds of timing attacks. Decoding payloads does not take a fixed amount of time.
The result of authentication/authorization
Constructors
Unauthorized | |
BadPassword | |
NoSuchUser | |
Authorized usr |
Instances
Functor BasicAuthResult # | |
Eq usr => Eq (BasicAuthResult usr) # | |
Read usr => Read (BasicAuthResult usr) # | |
Show usr => Show (BasicAuthResult usr) # | |
Generic (BasicAuthResult usr) # | |
type Rep (BasicAuthResult usr) # | |
General Authentication
Default error type
data ServantErr #
Constructors
ServantErr | |
Fields
|
Instances
3XX
err300 :: ServantErr #
err300
Multiple Choices
Example:
failingHandler :: Handler () failingHandler = throwError $ err300 { errBody = "I can't choose." }
err301 :: ServantErr #
err302 :: ServantErr #
err303 :: ServantErr #
err304 :: ServantErr #
err305 :: ServantErr #
err307 :: ServantErr #
4XX
err400 :: ServantErr #
err400
Bad Request
Example:
failingHandler :: Handler () failingHandler = throwError $ err400 { errBody = "Your request makes no sense to me." }
err401 :: ServantErr #
err401
Unauthorized
Example:
failingHandler :: Handler () failingHandler = throwError $ err401 { errBody = "Your credentials are invalid." }
err402 :: ServantErr #
err402
Payment Required
Example:
failingHandler :: Handler () failingHandler = throwError $ err402 { errBody = "You have 0 credits. Please give me $$$." }
err403 :: ServantErr #
err403
Forbidden
Example:
failingHandler :: Handler () failingHandler = throwError $ err403 { errBody = "Please login first." }
err404 :: ServantErr #
err404
Not Found
Example:
failingHandler :: Handler () failingHandler = throwError $ err404 { errBody = "(╯°□°)╯︵ ┻━┻)." }
err405 :: ServantErr #
err405
Method Not Allowed
Example:
failingHandler :: Handler () failingHandler = throwError $ err405 { errBody = "Your account privileges does not allow for this. Please pay $$$." }
err406 :: ServantErr #
err407 :: ServantErr #
err407
Proxy Authentication Required
Example:
failingHandler :: Handler () failingHandler = throwError err407
err409 :: ServantErr #
err409
Conflict
Example:
failingHandler :: Handler () failingHandler = throwError $ err409 { errBody = "Transaction conflicts with 59879cb56c7c159231eeacdd503d755f7e835f74" }
err410 :: ServantErr #
err410
Gone
Example:
failingHandler :: Handler () failingHandler = throwError $ err410 { errBody = "I know it was here at some point, but.. I blame bad luck." }
err411 :: ServantErr #
err412 :: ServantErr #
err412
Precondition Failed
Example:
failingHandler :: Handler () failingHandler = throwError $ err412 { errBody = "Precondition fail: x < 42 && y > 57" }
err413 :: ServantErr #
err413
Request Entity Too Large
Example:
failingHandler :: Handler () failingHandler = throwError $ err413 { errBody = "Request exceeded 64k." }
err414 :: ServantErr #
err414
Request-URI Too Large
Example:
failingHandler :: Handler () failingHandler = throwError $ err414 { errBody = "Maximum length is 64." }
err415 :: ServantErr #
err415
Unsupported Media Type
Example:
failingHandler :: Handler () failingHandler = throwError $ err415 { errBody = "Supported media types: gif, png" }
err416 :: ServantErr #
err416
Request range not satisfiable
Example:
failingHandler :: Handler () failingHandler = throwError $ err416 { errBody = "Valid range is [0, 424242]." }
err417 :: ServantErr #
err417
Expectation Failed
Example:
failingHandler :: Handler () failingHandler = throwError $ err417 { errBody = "I found a quux in the request. This isn't going to work." }
err418 :: ServantErr #
err418
Expectation Failed
Example:
failingHandler :: Handler () failingHandler = throwError $ err418 { errBody = "Apologies, this is not a webserver but a teapot." }
err422 :: ServantErr #
err422
Unprocessable Entity
Example:
failingHandler :: Handler () failingHandler = throwError $ err422 { errBody = "I understood your request, but can't process it." }
5XX
err500 :: ServantErr #
err500
Internal Server Error
Example:
failingHandler :: Handler () failingHandler = throwError $ err500 { errBody = "Exception in module A.B.C:55. Have a great day!" }
err501 :: ServantErr #
err501
Not Implemented
Example:
failingHandler :: Handler () failingHandler = throwError $ err501 { errBody = "/v1/foo is not supported with quux in the request." }
err502 :: ServantErr #
err502
Bad Gateway
Example:
failingHandler :: Handler () failingHandler = throwError $ err502 { errBody = "Tried gateway foo, bar, and baz. None responded." }
err503 :: ServantErr #
err503
Service Unavailable
Example:
failingHandler :: Handler () failingHandler = throwError $ err503 { errBody = "We're rewriting in PHP." }
err504 :: ServantErr #
err504
Gateway Time-out
Example:
failingHandler :: Handler () failingHandler = throwError $ err504 { errBody = "Backend foobar did not respond in 5 seconds." }
err505 :: ServantErr #
err505
HTTP Version not supported
Example usage:
failingHandler :: Handler () failingHandler = throwError $ err505 { errBody = "I support HTTP/4.0 only." }
Re-exports
type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived #
The WAI application.
Note that, since WAI 3.0, this type is structured in continuation passing
style to allow for proper safe resource handling. This was handled in the
past via other means (e.g., ResourceT
). As a demonstration:
app :: Application app req respond = bracket_ (putStrLn "Allocating scarce resource") (putStrLn "Cleaning up") (respond $ responseLBS status200 [] "Hello World")
newtype Tagged k s b :: forall k. k -> * -> * #
A
value is a value Tagged
s bb
with an attached phantom type s
.
This can be used in place of the more traditional but less safe idiom of
passing in an undefined value with the type, because unlike an (s -> b)
,
a
can't try to use the argument Tagged
s bs
as a real value.
Moreover, you don't have to rely on the compiler to inline away the extra argument, because the newtype is "free"
Tagged
has kind k -> * -> *
if the compiler supports PolyKinds
, therefore
there is an extra k
showing in the instance haddocks that may cause confusion.
Instances