mohws-0.2: src/Network/MoHWS/Logger/Access.hs
-- -----------------------------------------------------------------------------
-- Copyright 2002, Simon Marlow.
-- Copyright 2006, Bjorn Bringert.
-- Copyright 2009, Henning Thielemann.
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are
-- met:
--
-- * Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- * Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- * Neither the name of the copyright holder(s) nor the names of
-- contributors may be used to endorse or promote products derived from
-- this software without specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-- OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-- -----------------------------------------------------------------------------
module Network.MoHWS.Logger.Access (
Handle,
Request(..),
start,
stop,
mkRequest,
log,
) where
import qualified Network.MoHWS.Logger as Logger
import qualified Network.MoHWS.HTTP.Header as Header
import qualified Network.MoHWS.HTTP.Response as Response
import qualified Network.MoHWS.Server.Request as ServerRequest
import Network.MoHWS.Utility (formatTimeSensibly, )
import Network.BSD (HostEntry, hostName, )
import qualified Network.Socket as Socket
import System.Time (ClockTime, toUTCTime, getClockTime, TimeDiff, timeDiffToString, )
import Control.Monad (liftM, liftM2, )
import Prelude hiding (log, )
type Handle = Logger.Handle Request
{-
FIXME:
Instead of using body type ()
we should have data structures for the Response and Request headers
without the body,
like ResponseData and RequestData that are internally used in Network.HTTP.
-}
data Request = Request
{
request :: ServerRequest.T (),
response :: Response.T (),
serverHost :: HostEntry,
time :: ClockTime,
delay :: TimeDiff
}
start :: String -> FilePath -> IO Handle
start format file = Logger.start (mkLine format) file
{-
Instead of the class we could just use IO monad,
but I like to make explicit,
what are the functions that force us to do IO.
-}
class Monad m => Help m where
inet_ntoa :: Socket.HostAddress -> m String
instance Help IO where
inet_ntoa = Socket.inet_ntoa
infixr 5 +^+, ^:
(+^+) :: Monad m => m [a] -> m [a] -> m [a]
(+^+) = liftM2 (++)
(^:) :: Monad m => a -> m [a] -> m [a]
(^:) x = liftM (x:)
mkLine :: Help m => String -> Request -> m String
mkLine "" _ = return ""
mkLine ('%':'{':rest) r =
case span (/= '}') rest of
(str, '}':c:rest1) -> expand (Just str) c r +^+ mkLine rest1 r
_ -> '%' ^: '{' ^: mkLine rest r
mkLine ('%':c:rest) r = expand Nothing c r +^+ mkLine rest r
mkLine (c:rest) r = c ^: mkLine rest r
expand :: Help m => Maybe String -> Char -> Request -> m String
expand arg c info =
let resp = response info
sreq = request info
req = ServerRequest.clientRequest sreq
-- host = clientName (log_request info)
header _ Nothing = ""
header x (Just n) = unwords (Header.lookupMany (Header.makeName n) x)
addr = inet_ntoa (ServerRequest.clientAddress sreq)
in case c of
'b' -> return $ maybe "unknown" show $ Response.size (Response.body resp)
'f' -> return $ ServerRequest.serverFilename sreq
-- %h is the hostname if hostnameLookups is on, otherwise the
-- IP address.
'h' -> maybe addr (return . hostName) (ServerRequest.clientName sreq)
'a' -> addr
'l' -> return "-" -- FIXME: does anyone use identd these days?
'r' -> return $ show req
-- ToDo: 'p' -> canonical port number of server
's' -> return $ show (Response.code resp)
't' -> return $ formatTimeSensibly (toUTCTime (time info))
'T' -> return $ timeDiffToString (delay info)
'v' -> return $ hostName (serverHost info)
'u' -> return "-" -- FIXME: implement HTTP auth
'i' -> return $ header req arg
'o' -> return $ header resp arg
-- ToDo: other stuff
_ -> return ['%',c]
stop :: Handle -> IO ()
stop l = Logger.stop l
mkRequest :: ServerRequest.T body -> Response.T body -> HostEntry -> TimeDiff -> IO Request
mkRequest req resp host delay0 =
do time0 <- getClockTime
return $
Request {
request = fmap (const ()) req,
response = fmap (const ()) resp,
serverHost = host,
time = time0,
delay = delay0
}
log :: Handle -> Request -> IO ()
log l r = Logger.log l r