module Network.Shed.Httpd
( Server
, initServer
, Request(..)
, Response(..)
, queryToArguments
) where
import Network.URI
import Network
import System.IO
import Control.Monad
import Control.Concurrent
import Control.Exception as Exc
import Control.Concurrent.Chan
import qualified Data.List as List
import qualified Data.Char as Char
data Server = Server
initServer
:: Int
-> (Request -> IO Response)
-> IO Server
initServer portNo callOut = do
chan <- newChan
sock <- listenOn (PortNumber $ fromIntegral portNo)
loopIO
(do (h,nm,port) <- accept sock
forkIO $ do
tid <- myThreadId
ln <- hGetLine h
case words ln of
[mode,uri,"HTTP/1.1"] ->
case parseURIReference uri of
Just uri' -> readHeaders h mode uri' []
_ -> do print uri
hClose h
_ -> hClose h
return ()
) `finally` sClose sock
where
loopIO m = do m
loopIO m
readHeaders h mode uri hds = do
line <- hGetLine h
case span (/= ':') line of
("\r","") -> sendRequest h mode uri hds
(name,':':rest) -> readHeaders h mode uri (hds ++ [(name,dropWhile Char.isSpace rest)])
_ -> hClose h
message code = show code ++ " " ++
case lookup code longMessages of
Just msg -> msg
Nothing -> "-"
sendRequest h mode uri hds = do
resp <- callOut $ Request { reqMethod = mode
, reqURI = uri
, reqHeaders = hds
, reqBody = ""
}
hPutStr h $ "HTTP/1.1 " ++ message (resCode resp) ++ "\r\n"
hPutStr h $ "Connection: close\r\n"
sequence [ hPutStr h $
hdr ++ ": " ++ val ++ "\r\n"
| (hdr,val) <- resHeaders resp
]
hPutStr h $ "Content-Length: " ++
show (length (resBody resp)) ++ "\r\n"
hPutStr h $ "\r\n"
hPutStr h $ (resBody resp) ++ "\r\n"
hClose h
queryToArguments :: String -> [(String,String)]
queryToArguments ('?':rest) = queryToArguments rest
queryToArguments input = findIx input
where
findIx = findIx' . span (/= '=')
findIx' (index,'=':rest) = findVal (unEscapeString index) rest
findIx' _ = []
findVal index = findVal' index . span (/= '&')
findVal' index (value,'&':rest) = (index,unEscapeString value) : findIx rest
findVal' index (value,[]) = [(index,unEscapeString value)]
findVal' _ _ = []
data Request = Request
{ reqMethod :: String
, reqURI :: URI
, reqHeaders :: [(String,String)]
, reqBody :: String
}
deriving Show
data Response = Response
{ resCode :: Int
, resHeaders :: [(String,String)]
, resBody :: String
}
deriving Show
addCache :: Int -> (String,String)
addCache n = ("Cache-Control","max-age=" ++ show n)
noCache :: (String,String)
noCache = ("Cache-Control","no-cache")
contentType :: String -> (String,String)
contentType msg = ("Content-Type",msg)
longMessages =
[ (200,"OK")
, (404,"Not Found")
]