A Simple Haskell Web Server
February 14, 2008
I am always interested in web development as that is the industry I work in. As I also have an interest in Haskell I decided to try out using Haskell for web development.
There are some innovative web frameworks developed in Haskell, such as HAppS and WASH. There are also simpler approachs such as CGI and FastCGI libraries. I decided to develop my own simple web server and framework as I was interested in how Haskell would fare with more conventional and simpler HTML and database backed web sites, but was after more flexibility to experiment than CGI based approaches can provide.
What I’ve ended up with is not production quality by any means, but it provides a good and simple base to experiment and work from. Thanks to the HTTP and network libraries it worked out to around 20 lines of code. Below I present the primitive interface of the web server and its implmentation.
The primitive interface for my Simple Haskell Web Framework (SHWF) reflects the HTTP protocol. It defines a
RequestHandler as a function that accepts a
Request and will return a
To run a web server with a
runHttpServer method can be called.
type RequestHandler = Request -> IO Response runHttpServer :: RequestHandler -> IO ()
As an example of how the interface is used and as a basic test I developed a ‘Hello World’ application.
helloWorldHandler is a
RequestHandler that will return a simple HTML document to every request. The
main function simply runs the server with that
main :: IO () main = runHttpServer helloWorldHandler helloWorldHandler :: RequestHandler helloWorldHandler _ = return $ successReponse $ prettyHtml helloWorldDoc successResponse :: String -> Response successResponse = Response (2,0,0) ""  helloWorldDoc :: Html helloWorldDoc = header << thetitle << "Hello World" +++ body << h1 << "Hello World"
runHttpServer function creates the socket that will accept connections. It will then loop forever accepting connections on that socket and handling them.
runHttpServer :: RequestHandler -> IO () runHttpServer r = withSocketsDo $ do sock <- listenOn (PortNumber 8080) forever $ acceptConnection sock $ handleHttpConnection r
acceptConnection is a convenience method for accepting connections on a socket. It is passed a continuation that will be called with a
Handle that can be used to read and write data to and from the socket.
acceptConnection :: Socket -> (Handle -> IO ()) -> IO () acceptConnection s k = accept s >>= \(h,_,_) -> forkIO $ k h
To integrate with the HTTP library it is necessary to provide and instance of the
Stream class for
Handle. The functions are quite straight forward, but it’s important to note that
readLine is expected to return the newline character on the end.
instance Stream Handle where readLine h = hGetLine h >>= \l -> return $ Right $ l ++ "\n" readBlock h n = replicateM n (hGetChar h) >>= return . Right writeBlock h s = mapM_ (hPutChar h) s >>= return . Right close = hClose
In implementing the
handleHttpConnection I decided to experiment with arrows. Any function of type
a -> m b can be made in to a Kleisli arrow. This means that
RequestHandler can easily be made an arrow of type
Kleisli IO Request Response. Using arrow functions it is possible to transform this to the required type of
Kelisli IO Handle (). I’m not sure that
handleHttpConnectioni has turned out as clean as it could be. I’m not sure whether Kleisli arrows are not appropriate here, or whether I can simplify what I have.
handleHttpConnection :: RequestHandler -> Handle -> IO () handleHttpConnection r c = runKleisli (receiveRequest >>> handleRequest r >>> handleResponse) c >> close c where receiveRequest = Kleisli receiveHTTP handleRequest r = right (Kleisli r) handleResponse = Kleisli (print ||| respondHTTP c)
The minimal interface should also make it easy to improve the server. Some improvements that I hope will be straight forward include:
- Exception handling, especially around the socket operations,
- Configuration, at least to allow a different port.
The server implemented is not production quality, but it does suit my purposes. It is only around 20 lines, so is simple enough to understand. I’m hoping that its small size and ease of understanding makes it easier to experiment with.