Examining Hello World
Before we dive into writing our first Snap web application, let's do a quick overview of the parts of the Snap framework. Currently Snap is divided into four components:
snap-coreis the core of Snap. It defines an API for interfacing with web servers and includes type definitions and all code that is server-agnostic. This API is on the same level of abstraction as Java Servlets and is the focus of this tutorial.
snap-serveris an HTTP server library that supports the interface defined in
heistis the HTML templating library. You do not need it to use the above two libraries but you are certainly welcome to.
snapis a library that builds on the above three prackages and provides higher-level abstractions for building complex websites. It also contains a
snapexecutable which can generate several different skeleton projects to get you started.
In the Quick Start Guide, we installed Snap and created a template "hello world" application. Here we'll look at what's inside the application and describe basic use of Snap's web server API.
If you understand servlets and Haskell, most of the rest of the tutorial should be very self-explanatory. Even if you don't, have no fear! The following tutorial only expects that you know a little bit of Haskell.
snap init barebones creates a single file in the
src directory, Main.hs.
Here's the important code in Main.hs:
main :: IO () main = quickHttpServe site site :: Snap () site = ifTop (writeBS "hello world") <|> route [ ("foo", writeBS "bar") , ("echo/:echoparam", echoHandler) ] <|> dir "static" (serveDirectory ".") echoHandler :: Snap () echoHandler = do param <- getParam "echoparam" maybe (writeBS "must specify echo/param in URL") writeBS param
The behavior of this code can be summarized with the following rules:
- If the user requested the site's root page (http://mysite.com/), then return a page containing the string "hello world".
- If the user requested /foo, then return "bar".
- If the user requested /echo/xyz, then return "xyz".
- If the request URL begins with /static/, then look for files on disk matching the rest of the path and serve them.
- If none of these match, then Snap returns a 404 page.
Let's go over each of the Snap API functions used here.
dir runs its action only if the request path starts with the specified directory. You can combine successive dir calls to match more than one subdirectory into the path.
ifTop only executes its argument if the client requested the root URL. Use this for your home page. It can also be combined with the
dir function to define your index handler for a certain URL directory.
writeBS appends a strict ByteString to the response being constructed. Snap also provides an analogous function
writeLBS for lazy ByteStrings. You can also use the functions
writeLazyText if you use Data.Text instead of ByteString.
If you're not familiar with Haskell, you may be wondering about the
<|>. It is simply a binary operator that evaluates its first argument, and if it failed, evaluates the second. If the first argument succeeded, then it stops without evaluating the second argument.
site function uses
<|> to connect three different functions that guard what page gets rendered. First, the
ifTop function runs. This function succeeds if the requested URL is
http://site.com/. If that happens, then Snap sends a response of "hello world". Otherwise the
route function is executed.
You could build your whole site using
<|> to connect different handlers together, but this kind of routing is expensive because the amount of time it takes to construct a request scales linearly with the number of handlers in your site. For large sites, this could be quite noticeable. To remedy this, Snap also provides you with the
route function that routes requests based on the request path in
O(log n) time:
route takes a list of (route, handler) tuples and succeeds returning the result of the associated handler of the route that matches. If no route matches, then
route fails and execution is passed on to
In a real application, you will want to use
route for almost everything. We didn't do it that way in this example because we wanted to demonstrate more of the API.
getParam retrieves a GET or POST parameter from the request. In this
route function binds a captured portion of the URL to the
echoParam so the associated handler can make easy use of it.
echoHandler checks to see whether a parameter was passed and returns the
value or an error message if it didn't exist.