{-| HTTP server library. -} module Network.Web.Server (connection, WebServer, WebConfig(..)) where import Control.Applicative import Control.Exception import qualified Data.ByteString.Char8 as S import Data.Maybe import Data.Time import Network.Web.Date import qualified Network.Web.HTTP as HTTP (receive,respond) import Network.Web.HTTP hiding (receive,respond) import System.IO import System.Timeout ---------------------------------------------------------------- {-| The type for HTTP server. -} type WebServer = Maybe Request -> IO Response {-| The configuration for 'connection'. -} data WebConfig = WebConfig { -- | A hook to be called when an HTTP connection is closed. closedHook :: String -> IO () -- | A hook to be called when access succeeds. , accessHook :: String -> IO () -- | A hook to be called when an access error occurs. , errorHook :: String -> IO () -- | A hook to be called when a fatal error occurs. , fatalErrorHook :: String -> IO () -- | A time to unblock receiving an HTTP request in seconds. , connectionTimer :: Int } ---------------------------------------------------------------- {-| A function to run an 'WebServer'. 'Handle' should be mode by converting an accepted socket. Keep-alive / termination of HTTP 1.0 and HTTP 1.1 is correctly handled. So, 'WebServer' need not to handle the Connection: header in response. The Date: header is automatically added in response. -} connection :: Handle -> WebServer -> WebConfig -> IO () connection hdl srv cnf = session hdl srv cnf `catches` serverError where serverError = [Handler (\e -> closedHook cnf (show (e::ServerException))), Handler (\e -> errorHook cnf (show (e::SomeException)))] ---------------------------------------------------------------- session :: Handle -> WebServer -> WebConfig -> IO () session hdl svr cnf = do mreq <- recvRequest hdl cnf rsp <- runServer svr mreq persist <- sendResponse hdl cnf rsp mreq case persist of Close -> closedHook cnf "Connection is closed" Keep -> session hdl svr cnf _ -> return () -- never reached where runServer server mreq = do date <- utcToDate <$> getCurrentTime addDate date <$> server mreq addDate = insertField FkDate ---------------------------------------------------------------- recvRequest :: Handle -> WebConfig -> IO (Maybe Request) recvRequest hdl cnf = fromMaybe (throw TimeOut) <$> timeout tm (HTTP.receive hdl) where microseconds = 1000000 tm = connectionTimer cnf * microseconds ---------------------------------------------------------------- sendResponse :: Handle -> WebConfig -> Response -> Maybe Request -> IO Persist sendResponse hdl cnf rsp (Just req) = do let ver = reqVersion req cnct = lookupField FkConnection req status = rspStatus rsp persist = if badStatus status then Close else checkPersist ver cnct rsp hook = accessHook cnf sendResponse' hdl ver persist rsp hook sendResponse hdl cnf rsp Nothing = do let hook = errorHook cnf sendResponse' hdl HTTP10 Close rsp hook sendResponse' :: Handle -> Version -> Persist -> Response -> (String -> IO ()) -> IO Persist sendResponse' hdl ver persist rsp hook = do HTTP.respond hdl ver persist rsp hook $ rspLogMsg rsp ++ " [" ++ show (rspStatus rsp) ++ "]" return persist ---------------------------------------------------------------- -- CGI and HTTP/1.0 -> Close checkPersist :: Version -> Maybe S.ByteString -> Response -> Persist checkPersist HTTP11 Nothing _ = Keep checkPersist HTTP11 (Just cnct) _ | toPersist cnct == Close = Close | otherwise = Keep checkPersist HTTP10 Nothing _ = Close checkPersist HTTP10 (Just cnct) rsp | toPersist cnct == Keep = if isJust (rspBody rsp) && isNothing (rspLength rsp) then Close else Keep | otherwise = Close