-
Notifications
You must be signed in to change notification settings - Fork 65
Happstack
Happstack is the web application framework that Courseography is built on. A web framework is a library that reduces the overhead that is encountered when building a web application, such as serving dynamic content, accessing/managing resources (e.g. databases) and promotes the reuse of code (e.g. templating).
Developing Courseography on Happstack is a unique and excellent experience. However, due to the strong static type system of Haskell, the successful Happstack developer requires a good arsenal of prerequisite knowledge. This guide will provide that arsenal.
Let's begin understanding Happstack by simply sending a string, 'Hello World' to the user when the user sends a request to the server.
module Main where
import Happstack.Server
main :: IO ()
main = simpleHTTP nullConf $ ok "Hello World!"
Once you have completed the example, run the file (let's suppose it's saved as 'server.hs') by executing the command runhaskell server.hs
. When we visit the URL http://localhost:8000/
we should see the words "Hello World!"! Great Success!
In order to send the string "Hello World!" to the user, we see that we need to do quite a few things. Let's understand the types of this example.
The main method in our server.hs works just as it does in any other Haskell program. Any actions in the body of the function must also be of the IO type. For instance, let's suppose that we wanted to print out a message when the main method was run to tell us that the server is ready. The main method would look like this:
main :: IO ()
main = print "Server is starting." >>
(simpleHTTP nullConf $ ok "Hello World!")
This may or may not be new syntax for you. Don't be scared! The >>
operator stands for 'then'. It means that it's combining two monadic values together. It also has a sequential control flow, so the print statement will always happen before the server is started.
The type declaration of print
is Show a => a -> IO ()
, which means that the action is of the IO type. The type declaration of simpleHTTP
is ToMessage a => Conf -> ServerPartT IO a -> IO ()
, which means that the action is of the IO type. So, both of these actions can be performed inside of our main method.
As mentioned above, simpleHTTP
has the type declaration ToMessage a => Conf -> ServerPartT IO a -> IO ()
. The Conf
part is not important to us now, for now we can just think of it as the reason when we visit the URL localhost:8000
we interact with the server.
The interesting part of simpleHTTP
is the type of the input, which is of the type ServerPartT IO a
, where a
is of the typeclass ToMessage
. The ToMessage
typeclass allows Happstack to turn different types into HTTP responses that the server can serve to the client. This means that a
can be an Integer
, String
, Response
, or many more things that you can find here. The String
"Hello World!" works here, as it is of type String
, which is of the typeclass ToMessage
. But what about the ServerPartT IO
- where does that come from...?
ServerPartT IO
is the central type in Happstack. Here is a visual aid to help you remember the importance of ServerPartT IO
:
The ever important ServerPartT IO
allows us to do several things on top of the IO type. It is required by the above call to simpleHTTP
. We will later see it's various uses.
EDIT-NOTE: Show the use of this type more here.
The type declaration of ok
is FilterMonad Response m => a -> m a. One important piece of knowledge to have in our arsenal is that ServerPartT
implements FilterMonad Response
. In our example, we pass a String
into ok
, and receive a FilterMonad Response m String
out of it.
EDIT-NOTE: Need to better understand instances in Haskell to better explain above paragraph.
ok
is used to set the response code of the response. A response code that you've likely seen before is 404, which means that the server could not find anything that matched the Request-URI. The response code of ok
is not 404, but rather 200. You have seen responses with this code many times. It means that the server succeeded in responding to your request. Here is a good list of response code definitions.
ok
also produces a Response
from the input. Let's explore this. The type of ok
is FilterMonad Response m => a -> m a
. This is the expected type declaration for many of the functions that set response codes. It means that it will take in an a
(anything) and output an a
that is an instance of FilterMonad Response
.
This means that in our example above, we are feeding in something of the type FilterMonad Response
(the ok
output) to a function that takes ServerPartT IO a
as input. How do these two match up?
Let's look at ServerPartT
. ServerPartT
is just a monad that lets us basically run/perform operations on a response. So why can we use something of the class FilterMonad Response
? It turns out that ServerPartT
implements FilterMonad Response
, so we are safe to use an instance of FilterMonad Response
here.
Now we know how to set up the server to respond to any request to localhost:8000
with a response. Websites often have more than one page, which users can often request by typing in different URLs. In this next section, we direct our attention to something called 'Route Filtering'. In 'Route Filtering', the server provides different ways of responding to different requests.
Let's jump right into an example:
Suppose we have a website and we want users to be able to visit two different web pages with the URLs
localhost:8000/dogs
localhost:8000/cats
Our server would look something like this:
module Main where
import Happstack.Server
import Control.Monad (msum)
main :: IO ()
main = simpleHTTP nullConf $
msum [ dir "dog" $ ok "Woof.",
dir "cat" $ ok "Meow."
]
Visit the URLs localhost:8000/dog
and localhost:8000/cat
. You should see the corresponding translation for 'Hello'.
Since we're creating multiple Serverpart
s and we want to use them all as the parameter for the simpleHTTP
function, we need to somehow combine each of them into one entity. For this task, we use msum
, which has a type declaration of MonadPlus m => [m a] -> m a
. MonadPlus is a typeclass that provides choice and failure. This is fundamental in route filtering as we need to be able to match a specific response to a specific filter. ServerPartT implements MonadPlus
, so simpleHTTP will be able to take the result of msum as its input.
When the user sends a request, the server will need to match a particular response to that request. msum
will go through each entry in its list from top to bottom, and will test each entry to see if its pattern matches the requested URL. In our example, the server tries to match the URL against 'dog' first, and if that fails, it matches the URL against 'cat'. Should that fail, the server responds with the native Happstack 404 error, as expected.
dir
simply pops an element off of the requested URL (for instance "dog in localhost:8000/dog
) and checks if it matches the input string ("cat" or "dog" from out example). Should it match, it runs the corresponding handler, which in our case would be ok dogString
for localhost:8000/dog
. If our URL were extended to localhost:8000/dog/wolf
, we would receive a 404 error. This is because there is no URL that matches /dog/wolf
on the server. The server will match 'dog' with its corresponding entry in the list passed into msum
, however the server will only call the ok dogString
handler if every filter in the URL has been matched. This is why the string 'dog' can be matched in our above example, but 'dog/wolf' cannot. In order to be able to handle 'dog/wolf', we need to add in another dir
.
The type declaration of dir is (ServerMonad m, MonadPlus m) => String -> m a -> m a. The first parameter is the filter to match on, and the second is something that is an instance of both ServerMonad
and MonadPlus
.
Let's take a look at an example where we can handle the URL localhost:8000/dog/wolf
.
main :: IO ()
main = simpleHTTP nullConf $
msum [ dir "dog" $ dir "wolf" $ ok "Woof.",
dir "cat" $ ok "Meow."
]
Notice that when the URL matches with "dog", the server then tries to match the URL with "wolf". The first call to dir
will pop the first entry in the URL off the URL (in our case, it's dog
) and try to match it with the call to dir "dog"
. At this point, if the requested URL was localhost:8000/dog
, the server would try to match the rest of the URL after "dog" with wolf
- but there are no more filters after dog
! So, our request to localhost:8000/dog
fails.
Should we request localhost:8000/dog/wolf
, we will receive the English translation of the wolf 'hello', as expected.
Route Filtering to match URLs to corresponding responses is incredibly useful. Some responses, like an HTML page, might require additional files from the server, for instance a CSS or JavaScript file. Up until now, we have not yet learned how to access files from a specific directory. Let's explore this with another example:
module Main where
import Happstack.Server
import Control.Monad (msum)
main :: IO ()
main = simpleHTTP nullConf $ serveDirectory EnableBrowsing [] "/home/cygnet/courseography"
Note that you must change /home/cygnet/courseography
to the directory that contains your server file.
Now, visit localhost:8000
. You should be able to see all of your directory contents!
The type declaration of serveDirectory is (WebMonad Response m, ServerMonad m, FilterMonad Response m, MonadIO m, MonadPlus m) => Browsing -> [FilePath] -> FilePath -> m Response
.
The first parameter takes in either EnableBrowsing
or DisableBrowsing
. With EnableBrowsing
, the user can browse the entire directory. It is advised to not use this option. DisableBrowsing
simply denies the user permission to browse the directory.
The second parameter is a list of index files (for instance, visiting some directories of some websites will default to a page called 'index.html'). The server looks through the list from left to right, and chooses the first index file that works. Note that the directory contents will no longer be viewable from localhost:8000
if there exists an index file in this list that can be served. In this case, should EnableBrowsing
be set, one can venture into the directory localhost:8000/hs
and view the directory contents of courseography/hs
. Again, if DisableBrowsing
is set, the user cannot go to localhost:8000/hs
. In our above example, the list is empty. This means that the server will not look for any index files, and show the directory contents no matter what.
The third parameter is the path of the directory that is being served.
The output of serveDirectory
is a Response
that implements the classes WebMonad Response m, ServerMonad m, FilterMonad Response m, MonadIO m, MonadPlus m
.
It will often be the case that the client wishes to send a request with more precise constraints, but cannot express this in the hierarchical path structure. For instance, the client may wish to pass arguments to the server so that the server can query a database for those arguments.
Query parameters are the perfect tool for the job. Query parameters are part of the URL, and go after a question mark (?
). The format of a query string (a string of query parameters) is something like: localhost:8000/kitty?dog=woof&cat=meow
. Here we have two query parameters, cat
and dog
, with their corresponding values woof
and meow
. Let's see how we can retrieve these values from the URL within our server.
module Main where
import Happstack.Server
import Control.Monad (msum)
main :: IO ()
main = simpleHTTP nullConf $
msum [ dir "puppy" $ look "dog" >>= ok,
dir "kitty" $ look "cat" >>= ok
]
When we visit the URL http://localhost:8000/kitty?cat=meow
, we see the value corresponding to the query parameter cat
, which is meow
. If we go to http://localhost:8000/puppy?dog=woof
, we see woof
!
look
will search the query string for the parameter that it takes as input, and produce a string of the value that corresponds to that input string in the query string.
The type declaration of look
is (Functor m, Monad m, HasRqData m) => String -> m String
. The first parameter, as mentioned above, is the query parameter whose value we wish to know. The output is a string that is of the class (Functor m, Monad m, HasRqData m)
. The above example extracts the String
from the (Functor m, Monad m, HasRqData m) => m String
, so we are left with a String
that we can pass to ok
.