-- code structure written by John MacFarlane, -- I filled in some missing pieces and make it compile. module Hack.Handler.Happstack (run, runWithConfig, ServerConf(..), appToServerPart) where import Control.Arrow ((>>>)) import Control.Monad.State import Data.Char import Data.Default import Data.List import Data.Maybe import Hack hiding (serverName) import qualified Hack as Hack import Happstack.Server.SimpleHTTP as Happstack hiding (port, escape) import Network.URI (escapeURIString, isAllowedInURI) import Control.Applicative import qualified Data.ByteString.Char8 as S import qualified Data.Map as M import qualified Happstack.Server.SimpleHTTP as H data ServerConf = ServerConf { port :: Int, serverName :: String } deriving (Show) instance Default ServerConf where def = ServerConf { port = 3000, serverName = "localhost"} runWithConfig :: ServerConf -> Application -> IO () runWithConfig conf = simpleHTTP nullConf { H.port = port conf } . appToServerPart conf run :: Application -> IO () run = runWithConfig def appToServerPart :: ServerConf -> Application -> ServerPart (Happstack.Response) appToServerPart conf app = askRq >>= liftIO . (hackRToServerPartR <$>) . app . reqToEnv where reqToEnv req = def { requestMethod = convertRequestMethod $ rqMethod req , scriptName = "" , pathInfo = escape $ "/" ++ (intercalate "/" $ rqPaths req) , queryString = escape $ dropWhile (== '?') $ rqQuery req , Hack.serverName = serverName conf , serverPort = (snd $ rqPeer req) , http = headersToHttp (rqHeaders req) , hackInput = (\(Body x) -> x) (rqBody req) , remoteHost = (fst $ rqPeer req) } escape = escapeURIString isAllowedInURI convertRequestMethod Happstack.OPTIONS = Hack.OPTIONS convertRequestMethod Happstack.GET = Hack.GET convertRequestMethod Happstack.HEAD = Hack.HEAD convertRequestMethod Happstack.POST = Hack.POST convertRequestMethod Happstack.PUT = Hack.PUT convertRequestMethod Happstack.DELETE = Hack.DELETE convertRequestMethod Happstack.TRACE = Hack.TRACE convertRequestMethod Happstack.CONNECT = Hack.CONNECT headersToHttp :: Headers -> [(String, String)] headersToHttp = M.toList >>> map snd >>> map headerToPair where headerToPair (HeaderPair k v) = (normalizeHeader $ S.unpack k, intercalate " " $ map S.unpack v) hackRToServerPartR :: Hack.Response -> Happstack.Response hackRToServerPartR r = Happstack.Response { rsCode = status r , rsHeaders = httpToHeaders $ headers r , rsFlags = RsFlags {rsfContentLength = False} , rsBody = body r , rsValidator = Nothing } httpToHeaders :: [(String, String)] -> Headers httpToHeaders = map pairToHeader >>> M.fromList where pairToHeader (k,v) = ((S.pack $ map toLower k), HeaderPair (S.pack k) [S.pack v]) -- happstack converts all request header to lowercase ... -- so we need to convert it back ... normalizeHeader :: String -> String normalizeHeader s = fromMaybe s $ find (map toLower >>> (== s) ) headerList headerList :: [String] headerList = [ "Cache-Control" , "Connection" , "Date" , "Pragma" , "Transfer-Encoding" , "Upgrade" , "Via" , "Accept" , "Accept-Charset" , "Accept-Encoding" , "Accept-Language" , "Authorization" , "Cookie" , "Expect" , "From" , "Host" , "If-Modified-Since" , "If-Match" , "If-None-Match" , "If-Range" , "If-Unmodified-Since" , "Max-Forwards" , "Proxy-Authorization" , "Range" , "Referer" , "User-Agent" , "Age" , "Location" , "Proxy-Authenticate" , "Public" , "Retry-After" , "Server" , "Set-Cookie" , "TE" , "Trailer" , "Vary" , "Warning" , "WWW-Authenticate" , "Allow" , "Content-Base" , "Content-Encoding" , "Content-Language" , "Content-Length" , "Content-Location" , "Content-MD5" , "Content-Range" , "Content-Type" , "ETag" , "Expires" , "Last-Modified" , "Content-Transfer-Encodeing" ]