Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

http

haskell

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?

like image 211
Soni Bergraj Avatar asked May 27 '11 20:05

Soni Bergraj


2 Answers

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.

like image 83
Echo Nolan Avatar answered Oct 31 '22 09:10

Echo Nolan


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.

like image 37
C. A. McCann Avatar answered Oct 31 '22 09:10

C. A. McCann