module Network.HTTP.Server
( server, serverWith, Handler
, Config(..), defaultConfig
, Request(..), Response(..), RequestMethod(..)
, module Network.HTTP.Headers
, module Network.HTTP.Server.Response
) where
import Network.HTTP.Server.Utils
import Network.HTTP.Server.Logger
import Network.HTTP.Server.Response
#ifndef NO_PRELUDE_CATCH
import Prelude hiding (catch)
#endif
#ifdef _OS_UNIX
import qualified System.Posix.Signals as P
(installHandler,sigPIPE,Handler(Ignore))
#endif
import Network.Socket
(Socket,socket,withSocketsDo,SocketType(..),SocketOption(..)
,setSocketOption,SockAddr(..),listen,bindSocket,accept
,sOMAXCONN)
import qualified Network.Socket as Socket (close)
import Network.BSD
import Network.HTTP
import Network.HTTP.Headers
import Network.URI
import Network.URL
import Control.Concurrent(forkIO)
import Control.Exception(catch,SomeException)
import Data.Maybe(fromMaybe)
data Config = Config
{ srvLog :: Logger
, srvHost :: HostName
, srvPort :: PortNumber
}
defaultConfig :: Config
defaultConfig = Config
{ srvLog = quietLogger
, srvHost = "localhost"
, srvPort = 8000
}
server_init :: Config -> IO Socket
server_init conf = withSocketsDo $
do
#ifdef _OS_UNIX
_ <- P.installHandler P.sigPIPE P.Ignore Nothing
#endif
let host_name = srvHost conf
lg = srvLog conf
port_num = srvPort conf
hst <- getHostByName host_name
s <- socket (hostFamily hst) Stream =<< getProtocolNumber "TCP"
setSocketOption s ReuseAddr 1
case hostAddresses hst of
h : _ -> bindSocket s (SockAddrInet port_num h)
_ -> ioError (userError ("Could not resolve host address for: "
++ host_name))
listen s sOMAXCONN
logInfo lg 0 ("Listening on " ++ host_name ++ ":" ++ show port_num)
return s
type Handler a = SockAddr -> URL -> Request a -> IO (Response a)
server :: HStream a => Handler a -> IO ()
server = serverWith defaultConfig
serverWith :: HStream a => Config -> Handler a -> IO ()
serverWith conf handler = withSocketsDo $
do s <- server_init conf
loop s `catch` \e ->
logError lg ("Unexpected (0): " ++ show (e :: SomeException))
Socket.close s
where
lg = srvLog conf
loop s = do (client_sock,sock_addr) <- accept s
_ <- forkIO (client client_sock sock_addr)
loop s
get_request sock =
do mbreq <- receiveHTTP sock
case mbreq of
Left err -> logError lg (show err) >> return Nothing
Right req ->
let url_txt = show (rqURI req)
in case importURL url_txt of
Just url -> return (Just (url,req))
Nothing ->
do logError lg ("Invalid URL: " ++ url_txt)
return Nothing
client sock addr =
do let client_host = ppHostAddr addr
let portnum = portFromSockAddr addr
let client_addr = ppSockAddr addr ""
logInfo lg 0 ("Accepted connection from " ++ client_addr)
conn <- socketConnection client_host portnum sock
setStreamHooks conn nullHooks { hook_close =
logInfo lg 0 ("Closing connection to " ++ client_addr)
}
client_interact addr conn
client_interact addr conn =
do mbreq <- get_request conn
resp <- case mbreq of
Just (url,req) -> do
auth <- authorityToAuth `fmap` getAuth req
handler addr url req { rqURI = (rqURI req) {
uriScheme = "http:"
, uriAuthority = Just auth
}
}
`catch` \e ->
do logError lg ("Unexpected (1): "
++ show (e :: SomeException))
return (err_response InternalServerError)
Nothing -> return (err_response BadRequest)
let resp1 = fromMaybe resp
$ do (_,rq) <- mbreq
"close" <- findHeader HdrConnection rq
return (insertHeaderIfMissing HdrConnection "close" resp)
closing = case findHeader HdrConnection resp1 of
Just "close" -> True
_ -> False
resp2 = resp1
resp3 = insertHeaderIfMissing HdrServer
"Haskell HTTP Server" resp2
respondHTTP conn resp3
if closing
then do close conn
logInfo lg 0 ("Closed connection to " ++ ppSockAddr addr "")
else client_interact addr conn
authorityToAuth :: URIAuthority -> URIAuth
authorityToAuth a =
URIAuth { uriUserInfo = info
, uriRegName = Network.HTTP.host a
, uriPort = maybe "" ((':':).show) (Network.HTTP.port a)
}
where
info = case (user a, password a) of
(Just x, Just y) -> x ++ ":" ++ y
(Just x, Nothing) -> x
_ -> ""