Thursday, June 16, 2016

How to write a simple HTTP server in Haskell using Network.HTTP.receiveHTTP

Leave a Comment

The module Network.HTTP exposes the functions receiveHTTP and respondHTTP which I'd like to use for a very basic web server. I wrote a stub that just waits for clients:

{-# LANGUAGE OverloadedStrings #-} module Main where  import Network.HTTP import Network import Control.Monad  main = withSocketsDo $ do   socket <- listenOn $ PortNumber 8080   forever $ do     (handle, host, port) <- accept socket     print (host, port) 

Here accpet gives me a Handle, and now I can't figure out how to use a Handle with receiveHTTP.

I found an example with Google, but it is from 2008 and does not work anymore. And I was not able to port it.

Any ideas?

2 Answers

Answers 1

Perhaps it expects you to use the accept function from Network.Socket instead of Network? That gives you a Socket instead of a Handle, which you should be able to convert to a form that receiveHTTP can use.

Normally a Handle would be nicer to work with directly, but here the HTTP library is taking care of it for you so it expects the lower-level interface instead.

EDIT: After looking at it a bit further, it seems the socketConnection function in Network.TCP does what you need. The funny part is it's actually making the socket into a Handle internally before it reads from it, but doesn't seem to provide a way to read from an externally-provided Handle. The string parameter to the function is supposed to be the name of the remote host, but it looks like that's merely kept for reference; it's not actually initiating a connection to that host or anything.

Answers 2

You can do this, but I really think you shouldn't. HTTP can act as a server, but is designed to be used client side. I Googled a little and I can't find any examples of someone actually using respondHTTP. If you're doing client side HTTP in 2016 use http-conduit. On the server side, warp or something that depends upon it is probably what you want.

Nevertheless, here's the code.

#!/usr/bin/env stack -- stack --resolver lts-6.3 --install-ghc runghc --package HTTP {-# LANGUAGE RecordWildCards #-} import Control.Monad import qualified Data.ByteString as B import Network.HTTP import Network.Socket import Network.URI  main = do   lsock <- socket AF_INET Stream defaultProtocol   bind lsock (SockAddrInet 8080 iNADDR_ANY)   listen lsock 1   forever $ do     (csock, _) <- accept lsock     hs <- socketConnection "" 8080 csock     req <- receiveHTTP hs     case req of       Left _ -> error "Receiving request failed"       Right (Request {..}) -> if uriPath rqURI == "/"                             then do                               respondHTTP hs $                                 Response (2,0,0) "OK" [] "Hello HTTP"                               Network.HTTP.close hs                             else do                               respondHTTP hs $                                 Response (4,0,4) "Not found" [] "Nothing here"                               Network.HTTP.close hs 

The above uses Stack's support for shebang scripts. chmod +x it or run it with stack foo.hs.

The Network module is deprecated. Always use Network.Socket if you need a socket API. For something higher level, use connection.

You do the normal POSIX socket thing, then convert the connected socket to a HandleStream with socketConnection and run respondHTTP and receiveHTTP on it. socketConnection is a weird function. The first two parameters are a hostname and a port which AFAICT aren't used when running a server.

I used the RecordWildCards extension. It lets me write Right (Request {..}) in a pattern and have all the fields of the record in scope on the right hand side.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment