a.k.a Responding to different url paths
Happstack provides a variety of ways to match on parts of the Request
(such as the path or request method) and respond appropriately.
Happstack provides two different systems for mapping the request path to a handler. In this section we will cover a simple, untyped routing system. Later in the crash course we will look at fancier, type-safe routing sytem known as web-routes.
ServerPartTs
In the first example, we had only one ServerPartT
. All
Request
s were handled by the same part and returned the
same Response
.
In general, our applications will have many
ServerPartT
s. We combine them into a single top-level
ServerPartT
by using MonadPlus
. Typically
via the msum
function:
> msum :: (MonadPlus m) => [m a] -> m a
In the following example we combine three ServerPartT
s
together.
> module Main where > > import Control.Monad > import Happstack.Server (nullConf, simpleHTTP, ok, dir) > > main :: IO () > main = simpleHTTP nullConf $ msum [ mzero > , ok "Hello, World!" > , ok "Unreachable ServerPartT" > ]
[Source code for the app is here.]
The behaviour of MonadPlus
is to try each ServerPartT
in succession, until one succeeds.
In the example above, the first part is mzero
, so it will always fail. The second part will always succeed. That means the third part will never be reachable.
Alas, that means this application will appear to behave exactly like the first application. What we need are some ways to have parts match or fail depending on the contents of the http Request
.
dir
to match on static path componentsWe can use dir
to handle components of the URI path which are static. For example, we might have a site with the two URLS /hello and /goodbye.
> module Main where > > import Control.Monad > import Happstack.Server (nullConf, simpleHTTP, ok, dir) > > main :: IO () > main = simpleHTTP nullConf $ msum [ dir "hello" $ ok "Hello, World!" > , dir "goodbye" $ ok "Goodbye, World!" > ]
[Source code for the app is here.]
If we start the app and point our browser at http://localhost:8000/hello we get the hello message, and if we point it at http://localhost:8000/goodbye, we get the goodbye message.
dir
to match on multiple componentsWe can match on multiple components by chaining calls to dir
together
> module Main where > > import Control.Monad (msum) > import Happstack.Server (nullConf, simpleHTTP, ok, dir) > > main :: IO () > main = simpleHTTP nullConf $ msum [ dir "hello" $ dir "world" $ ok "Hello, World!" > , dir "goodbye" $ dir "moon" $ ok "Goodbye, Moon!" > ]
[Source code for the app is here.]
If we start the app and point our browser at http://localhost:8000/hello/world we get the hello message, and if we point it at http://localhost:8000/goodbye/moon, we get the goodbye message.
dirs
as shorthand to match on multiple componentsAs a shorthand, we can also use dirs
to handle multiple static patch components.
> module Main where > > import Control.Monad (msum) > import Happstack.Server (nullConf, simpleHTTP, ok, dirs) > > main :: IO () > main = simpleHTTP nullConf $ msum [ dirs "hello/world" $ ok "Hello, World!" > , dirs "goodbye/moon" $ ok "Goodbye, Moon!" > ]
[Source code for the app is here.]
If we start the app and point our browser at http://localhost:8000/hello/world we get the hello message, and if we point it at http://localhost:8000/goodbye/moon, we get the goodbye message.
path
Often times a path segment will contain a variable value we want to
extract and use, such as a number or a string. We can use the
path
combinator to do that.
> path :: (FromReqURI a, MonadPlus m, ServerMonad m) => (a -> m b) -> m b
You may find that type to be a little hard to follow because it is pretty abstract looking. Fortunately, we can look at it in an easier way. A ServerPart
is a valid instance of, ServerMonad m
, so we can just replace the m
with ServerPart
. You can do this anywhere you see type signatures with (ServerMonad m) =>
in them. In this case, the final result would look like:
> path :: (FromReqURI a) => (a -> ServerPart b) -> ServerPart b
path
will attempt to extract and decode a path
segment, and if it succeeds, it will pass the decode value to the nested
server part.
Let's start with the most basic example, extracting a
String
value. We will extend the Hello World server so
that we can say hello to anyone.
> module Main where > > import Control.Monad (msum) > import Happstack.Server (nullConf, simpleHTTP, ok, dir, path) > > main :: IO () > main = simpleHTTP nullConf $ msum [ dir "hello" $ path $ \s -> ok $ "Hello, " ++ s > ]
[Source code for the app is here.]
Now, if we start the app and point our browser at: http://localhost:8000/hello/World we get the "Hello, World".
if we point it at http://localhost:8000/hello/Haskell, we get "Hello, Haskell".
FromReqURI
: extending path
We can extend path so that we can extract our own types from the path components as well. We simply add an instance to the FromReqURI class:
> class FromReqURI a where > fromReqURI :: String -> Maybe a
For example, let's say that we want to create a type to represent subjects we can greet.
> module Main where > > import Control.Monad (msum) > import Data.Char (toLower) > import Happstack.Server (FromReqURI(..), nullConf, simpleHTTP, ok, dir, path) > > data Subject = World | Haskell > > sayHello :: Subject -> String > sayHello World = "Hello, World!" > sayHello Haskell = "Greetings, Haskell!" >
We simply add an instance such as:
> instance FromReqURI Subject where > fromReqURI sub = > case map toLower sub of > "haskell" -> Just Haskell > "world" -> Just World > _ -> Nothing >
Now when we use path
it will extract a value of type Subject
.
> main :: IO () > main = simpleHTTP nullConf $ dir "hello" $ path $ \subject -> ok $ (sayHello subject) >
[Source code for the app is here.]
Now, if we start the app and point our browser at: http://localhost:8000/hello/World we get the "Hello, World"".
if we point it at http://localhost:8000/hello/Haskell, we get "Greetings, Haskell!".
(GET, POST, etc)
methodM
and methodOnly
We can specify that a route is only valid for specific HTTP request methods by using the methodM
guard:
> methodM :: (ServerMonad m, MonadPlus m, MatchMethod method) => method -> m ()
The methodM
guard does two things:
Here is a simple demo app:
> module Main where > > import Control.Monad (msum) > import Happstack.Server (Method(GET, POST), dir, methodM, nullConf, ok, simpleHTTP) > > main :: IO () > main = simpleHTTP nullConf $ msum > [ do methodM GET > ok $ "You did a GET request.\n" > , do methodM POST > ok $ "You did a POST request.\n" > , dir "foo" $ do methodM GET > ok $ "You did a GET request on /foo\n" > ] >
[Source code for the app is here.]
Using curl
we can see the expected results for normal GET
and POST
requests to /
:
$ curl http://localhost:8000/ You did a GET request. $ curl -d '' http://localhost:8000/ You did a POST request.
Note that methodM
also requires that the all the segments of request path have been consumed. We can see in here that /foo
is accepted, but not /foo/bar
, since only foo
is consumed by the, dir "foo"
filter.
$ curl http://localhost:8000/foo You did a GET request on /foo $ curl http://localhost:8000/foo/bar <404 message>
If we want to allow unconsumed path segments we can use methodOnly
instead.
> methodOnly :: (ServerMonad m, MonadPlus m, MatchMethod method) => method -> m ()
In fact the definition for methodM
is simply:
> methodM :: (ServerMonad m, MonadPlus m, MatchMethod method) => method -> m () > methodM meth = methodOnly meth >> nullDir
MatchMethod
The method routing functions use a class (MatchMethod method)
instead of the concrete type Method
.
> class MatchMethod m where > matchMethod :: m -> Method -> Bool > > instance MatchMethod Method where ... > instance MatchMethod [Method] where ... > instance MatchMethod (Method -> Bool) where ... > instance MatchMethod () where ...
This allows us to easily match on more than one method by either providing a list of acceptable matches, or by providing a function which returns a boolean value. We can use this feature to support the HEAD
method. When the client does a HEAD
request, the server is supposed to return the same headers it would for a GET request, but with an empty response body. Happstack includes special support for handling this automatically in most cases.
> module Main where > > import Control.Monad (msum) > import Happstack.Server (Method(GET, HEAD), dir, methodM, nullConf, ok, simpleHTTP) > > main :: IO () > main = simpleHTTP nullConf $ msum > [ do methodM [GET, HEAD] > ok $ "Hello, World\n" > ] >
[Source code for the app is here.]
We can now use curl to do a normal GET
request, or we can
use the -I
flag which does a HEAD
request:
$ curl http://localhost:8000/ Hello, World $ curl -I http://localhost:8000/ HTTP/1.1 200 OK Connection: Keep-Alive Content-Length: 13 Content-Type: text/plain; charset=UTF-8 Date: Tue, 15 Jun 2010 19:56:07 GMT Server: Happstack/0.5.0
Happstack automatically notices that it is a HEAD
request, and does not send the body.
SimpleHTTP includes a number of other useful routing filters, such as:
nullDir :: (ServerMonad m, MonadPlus m) => m ()
host :: (ServerMonad m, MonadPlus m) => String -> m a -> m a
withHost :: (ServerMonad m, MonadPlus m) => (String -> m a) -> m a
uriRest :: (ServerMonad m) => (String -> m a) -> m a
anyPath :: (ServerMonad m, MonadPlus m) => m r -> m r
trailingSlash :: (ServerMonad m, MonadPlus m) => m ()
/
. Useful for distinguishing between foo
and foo/