Safe Haskell | Trustworthy |
---|---|
Language | Haskell2010 |
Web.Simple
Description
Simple is based on WAI - an standard interface for communicating between web servers (like warp) and web applications. You can use Simple completely independently (and of course, use any WAI server to run it). Alternatively, you can embed existing existing WAI applications inside an app built with Simple, and embed an app built with simple in another WAI app.
All the components in Simple are designed to be small and simple enough to understand, replaceable, and work as well independantly as they do together.
- module Web.Simple.Responses
- module Web.Simple.Controller
- module Web.Simple.Static
- module Network.Wai
Documentation
module Web.Simple.Responses
module Web.Simple.Controller
module Web.Simple.Static
module Network.Wai
Overview
WAI applications are functions of type Application
- given a
client Request
they return a Response
to return to
the client (i.e. an HTTP status code, headers, body etc'). A Simple
application Controller
-- a wrapper around WAI's Application
either returns a monadic value, or a Response
. This allows
Controller
s to be chained together to create arbitrary complex routes. If a
Controller
"matches" a route (e.g., based on the HTTP path, hostname,
cookies etc), it can respond
which shortcircuits the remaining execution and
immediately send the response back to the client. If none, of the Controller
s
match, an HTTP 404 (NOT FOUND) response will be returned.
For example, this is a trivial Simple app that notices whether the incoming request was for the hostname "hackage.haskell.org" or "www.haskell.org":
routeHost "hackage.haskell.org" $ do respond $ okHtml "Welcome to Hackage" routeHost "www.haskell.org" $ do respond $ okHtml "You've reached the Haskell Language home page"
routeHost
is a combinator that matches the a request based on the "Host"
header and defers to the passed in Controller
or returns '()'. There are
other built-in combinators for matching based on the request path, the HTTP
method, and it's easy to write your own combinators. You can chain such
combinators together monadically or using mappend
(since Controller
is an
instance of Monoid
). A typical Simple app looks something like this:
controllerApp () $ do routeTop $ do ... handle home page ... routeName "posts" $ do routeMethod GET $ ... get all posts ... routeMethod POST $ ... create new post ...
where controllerApp
generates an Application
from a Controller
returning a 404 (not found) response if all routes fail.
This package also includes the Web.Frank module which provide an API to create
applications similar to the Sinatra framework for Ruby, and the Web.REST
module to create RESTful applications similar to Ruby on Rails. Neither of
these modules is "special", in the sense that they are merely implemented in
terms of Controller
s. The example above could be rewritten using Web.Frank
as such:
controllerApp () $ do get "/" $ do ... display home page ... get "/posts" $ do ... get all posts ... post "/posts" $ do ... create new post ...
Simple is broken down into the following modules:
Web |-- Web.Simple - Re-exports most common modules | |-- Web.Simple.Controller - Base monad and built-in routing combinators | |-- Web.Simple.Responses - Common HTTP responses | |-- Web.Simple.Auth -Controller
s for authentication | |-- Web.Simple.Cache - in memory and filesystem cache utilities |-- Web.Frank - Sinatra styleRoute
s +-- Web.REST - Monad for creating RESTful controllers
Tutorial
Simple comes with a utility called smpl which automates some common tasks like creating a new application, running migrations and launching a development server. To create a new Simple app in a directory called "example_app", run:
$ smpl create example_app
This will create a directory called "example_app" containing a .cabal file and and a single Haskell source file, "Main.hs":
{-# LANGUAGE OverloadedStrings #-} module Main where import Web.Simple import Network.Wai.Handler.Warp import System.Posix.Env app :: (Application -> IO ()) -> IO () app runner = runner $ do -- TODO: App initialization code here controllerApp () $ do respond $ okHtml "Hello World" main :: IO () main = do port <- read `fmap` getEnvDefault "PORT" "3000" app (run port)
The app
function is the entry point to your application. The argument is a
function that knows how to run a Application
-- for example,
warp's run method. mkRouter
transforms a Routeable
into an
Application
. The boilerplate is just a Response
with the body
"Hello World" (and content-type "text/html"). To run a development server
on port 3000:
$ cd example_app $ smpl
Pointing your browser to http://localhost:3000 should display "Hello World"!
Controllers
What is this controllerApp
business? The basic type in Simple is a
Controller
which contains both a Request
and app specific state.
controllerApp
takes an initial application state (unit in the example above)
and transforms a Controller
into a WAI Application
so it can be run by a
server like warp.
A Controller
is a Monad
that can perform actions in IO
(using liftIO
),
access the underlying request
or application state (via controllerState
).
Finally, a Controller
can respond
to a request. respond
short-circuits
the rest of the computation and returns the Response
to the client.
controllerApp
transforms a Controller
into a WAI application by running the
Controller
. If the Controller
does not call respond
, controllerApp
defaults to responding to the client with a 404 not found. For example:
controllerApp () $ do liftIO $ putStrLn "Responding to request" respond $ okHtml "Hello World" liftIO $ putStrLn "This message is never actually printed"
When run, this code will always print the first message ("Responding to request") and respond with a 200 page containing "Hello World", but never print the second message. Short-circuiting the computation in this way allows us to respond in different ways based on the request:
controllerApp () $ do path <- rawPathInfo <$> request when (path == "/timeofday") $ do timeStr <- liftIO $ S8.pack . show <$> getClockTime respond $ okHtml timeStr when (path == "/whoami") $ user <- liftIO $ S8.pack <$> getLoginName respond $ okHtml user
This controller will respond with the current time if the path "/timeofday" is requested, and the user running the server if the path "/whoami" is requested. If neither of those paths match, it will respond with a 404 (NOT FOUND).
Routing
An app that does the same thing for every request is not very useful (well, it
might be, but if it is, even Simple is not simple enough for you). We want to
build applications that do perform different actions based on properties of the
client's request - e.g., the path requests, GET or POST requests, the "Host"
header, etc'. Simple's Controller
s are flexible to accomplish this.
Controller
s encapsulate a function from a Request
to Either
a Response
or some monadic value.
For example, let's extend the example using the Monad
syntax:
controllerApp () $ do routeTop $ do routeHost "localhost" $ respond $ okHtml "Hello, localhost!" routeHost "test.lvh.me" $ respond $ okHtml "Hello, test.lvh.me!" routeName "advice" $ okHtml "Be excellent to each other!"
Now, the app will respond differently depending on whether the client is
requesting the host name "localhost" or "test.lvh.me", or if the requested
path is "/advice" rather than "/". Take it for a spin in the browser (make
sure smpl
is still running):
In this example, routeTop
matches if the Request
s
pathInfo
is empty, which means the requested path is "/" (as
in this case), or the rest of the path has been consumed by previous Route
s.
routeName
matches if the next component in the path (specifically the head
of pathInfo
) matches the argument (and if so, removes it). Check
out Web.Simple.Router for more complete documentation of these and other
Route
s.
For many apps it will be convenient to use even higher level routing APIs. The
modules Web.Frank and Web.Sinatra provide Sinatra-like and RESTful APIs,
respectively. Both modules are implement purely in terms of Route
s and you
can easily implement your own patterns as well.