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 Interface

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 Response. The Request and Response

To run a web server with a RequestHandler the runHttpServer method can be called.

type RequestHandler = Request -> IO Response runHttpServer :: RequestHandler -> IO ()

The Example

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 RequestHandler

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"

The Implementation

The 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 ++ "

" 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)

Improvments

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,

Logging,

Configuration, at least to allow a different port.

Conclusion

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.