This is a reasonably simple attempt at fixing #460.
By moving the accept check to a place before the body check,
we can make it recoverable (the body check is irreversible,
so everything done after the body check has to fail fatally).
The advantage is that we can now specify routes offering
different content types modularly. Failure to match one
is not fatal, and will result in subsequent routes being
tried.
The disadvantage is that we hereby bump the error priority
of the 406 status code. If a request contains a bad accept
header and a bad body, we now get 406 rather than 400. This
deviates from the HTTP decision diagram we try to follow,
but seems like an acceptable compromise for now.
We've previously used functions in the Router type to provide
information for subrouters. But this accesses the Requests too
early, and breaks sharing of the router structure in general,
causing the Router or large parts of the Router to be recomputed
on every request.
We now do not use functions anymore, and properly compute all
static parts of the router first, and gain access to the request
only in Delayed.
This also turns the code used within Delayed into a proper monad
now called DelayedIO, making some of the code using it a bit
nicer.
* Improves how Routers are built, in particular via
the `choice` smart constructors. Static lookups are
now used more often.
* We now have test cases making sure that certain
routers have the same structure.
* The router structure can now be visualized for debugging
purposes as a tree. The new functions `layout` and
`layoutWithContext` do this.
This introduces a `Delayed` type in `RoutingApplication.hs` that
contains a handler together with delayed checks. There are several
blocks of delayed checks, so that we can ultimately execute them in the
order we desire.
The process is documented in more detail in `RoutingApplication.hs`.
tighter version bounds for network
cleanup
document the new combinators
servant-server: add some tests for HttpVersion, IsSecure, RemoteHost and Vault
update changelogs
address Julian's feedback
remove vault test in servant-server
servant-server tests: -Werror friendly
This change makes an attempt of abstracting out some of the common
functionality found in the handlers for the different request methods.
There's still a bit of code duplication between the cases for headers
and no headers and empty responses. But it's a significant relative
improvement already.
The main `Server.Internal` module was getting a bit large for my taste.
It now contains just the instances. All the administrative utilities
are in their own dedicated modules.
Instead of directly interpreting a server as a `RoutingApplication`,
this change introduces the concept of a `Router`, which is a datatype
with several constructors.
In particular, the type of the `route` function changes from
route :: Proxy layout -> Server layout -> RoutingApplication
to
route :: Proxy layout -> IO (RouteResult (Server layout)) -> Router
Most important in practice is the case of the `StaticRouter` constructor
in `Router`. For choices between statically known paths, we can now use
a lookup table to dispatch requests rather than trying each request
individually.
This brings down routing complexity of a common case from
O(n) to O(log n).
Another important change is that the handler that is passed down by
`route` is no longer of type `Server layout`, but of type
`IO (RouteResult (Server layout))`. This means that API constructs
can "delay" checks and failure. For example, `ReqBody` does not have
to fetch the request body and feed it to the handler immediately; it
can instead record these actions in the handler that is passed down.
The code will only be executed at a leaf / endpoint of the API.
This is desired behaviour: We prefer to save work by doing all matching
on static path components first. Furthermore, we get better error codes
by doing so.