http-server-1.0.2: Network/HTTP/Server.hs
--------------------------------------------------------------------------------
-- |
-- Module : Network.HTTP.Server
-- Copyright : (c) Galois, Inc. 2007, 2008
-- License : BSD3
--
-- Maintainer : diatchki@galois.com
-- Stability : provisional
-- Portability :
--
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,sClose,accept
,sOMAXCONN)
import Network.BSD
import Network.HTTP
import Network.HTTP.Headers -- for re-export above
import Network.URI
import Network.URL
import Control.Concurrent(forkIO)
import Control.Exception(catch,SomeException)
import Data.Maybe(fromMaybe)
-- XXX: Add timeouts to close lingering connections
-- | Server configuration.
data Config = Config
{ srvLog :: Logger -- ^ Server reports what's going on here.
, srvHost :: HostName -- ^ Host name to bind to.
, srvPort :: PortNumber -- ^ Port to listen on.
}
-- | Some default options for a server:
-- no logging output, listen on \"localhost:8000\".
defaultConfig :: Config
defaultConfig = Config
{ srvLog = quietLogger
, srvHost = "localhost"
, srvPort = 8000
}
-- Note: See http://www.haskell.org/ghc/docs/latest/html/libraries/network-2.1.0.0/Network.html#10 for details ob the sigPIPE handler.
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 -- socket level 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
-- | Handlers invoked to process requests.
-- The type parameter is for the type of the payload in the body.
-- It is a variation on string of some sort (e.g., String, ByteString, etc.)
type Handler a = SockAddr -> URL -> Request a -> IO (Response a)
-- | Start a server with the default configuration, and the given handler.
-- Requests are handled in separate threads.
server :: HStream a => Handler a -> IO ()
server = serverWith defaultConfig
-- | Start a server with the given configuration and handler.
-- Requests are handled in separate threads.
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))
sClose s
where
lg = srvLog conf
loop s = do (client_sock,sock_addr) <- accept s
_ <- forkIO (client client_sock sock_addr)
loop s
-- get_request :: HandleStream a -> IO (Maybe (URL, Request a))
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 :: SockAddr -> HandleStream a -> IO ()
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
{-
msg_len = length (rspBody resp1)
resp2 = insertHeaderIfMissing HdrContentLength
(show msg_len) resp1
-}
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
_ -> ""