hack2-handler-happstack-server (empty) → 2011.6.10
raw patch · 7 files changed
+258/−0 lines, 7 filesdep +basedep +bytestringdep +cgisetup-changed
Dependencies added: base, bytestring, cgi, containers, data-default, hack2, happstack-server, mtl, network
Files
- LICENSE +31/−0
- Nemesis +34/−0
- Setup.lhs +4/−0
- changelog.md +4/−0
- hack2-handler-happstack-server.cabal +22/−0
- readme.md +9/−0
- src/Hack2/Handler/HappstackServer.hs +154/−0
+ LICENSE view
@@ -0,0 +1,31 @@+Copyright (c) 2011, Jinjing Wang++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 Jinjing Wang nor the names of other+ 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.
+ Nemesis view
@@ -0,0 +1,34 @@+nemesis = do+ + clean+ [ "**/*.hi"+ , "**/*.o"+ , "manifest"+ , "main"+ , "nemesis-tmp.*"+ , "Test"+ ]+ ++ desc "prepare cabal dist"+ task "dist" $ do+ sh "cabal clean"+ sh "cabal configure"+ sh "cabal sdist"+++ desc "put all .hs files in manifest"+ task "manifest" $ do+ sh "find . | grep 'hs$' > manifest"+++ desc "start console"+ task "i" (sh "ghci -isrc src/Hack2/Handler/HappstackServer.hs")++ desc "test"+ task "test" $ do+ sh "runghc -isrc src/Test.hs"+ + desc "show sloc"+ task "stat" $ do+ sh "cloc -match-f=hs$ --quiet src"
+ Setup.lhs view
@@ -0,0 +1,4 @@+#! /usr/bin/env runhaskell++> import Distribution.Simple+> main = defaultMain
+ changelog.md view
@@ -0,0 +1,4 @@+2011.6.10+----------++* init
+ hack2-handler-happstack-server.cabal view
@@ -0,0 +1,22 @@+Name: hack2-handler-happstack-server+Version: 2011.6.10+Build-type: Simple+Synopsis: Hack2 Happstack server handler+Description: Hack2 Happstack server handler+License: BSD3+License-file: LICENSE+Author: Jinjing Wang+Maintainer: Jinjing Wang <nfjinjing@gmail.com>+Build-Depends: base+Cabal-version: >= 1.2+category: Web+homepage: http://github.com/nfjinjing/hack2-handler-happstack-server+data-files: readme.md, changelog.md, Nemesis++library+ ghc-options: -Wall+ build-depends: base >= 4.0 && < 5, cgi, network, bytestring, data-default >= 0.2, hack2 >= 2011.6.10, happstack-server < 6, containers, mtl+ hs-source-dirs: src/+ exposed-modules: + Hack2.Handler.HappstackServer+
+ readme.md view
@@ -0,0 +1,9 @@+Happstack server handler+-----------------++* preferred for production++Known issues+------------++* The backend does not distinguish between `http://example/abc/` and `http://example/abc`, (notice the absence of trailing slash).
+ src/Hack2/Handler/HappstackServer.hs view
@@ -0,0 +1,154 @@+-- code structure written by John MacFarlane, +-- I filled in some missing pieces and make it compile.++{-# LANGUAGE PackageImports #-}+{-# LANGUAGE OverloadedStrings #-}+++module Hack2.Handler.HappstackServer (run, runWithConfig, ServerConf(..), appToServerPart) where+++import Control.Arrow ((>>>))+import "mtl" Control.Monad.State+import Data.Char+import Data.Default+import Data.List+import Data.Maybe+import qualified Hack2 as Hack2+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.ByteString.Lazy.Char8 as L++import qualified Data.Map as M+import qualified Happstack.Server.SimpleHTTP as H++data ServerConf = ServerConf { port :: Int, serverName :: L.ByteString }+ deriving (Show)++instance Default ServerConf where+ def = ServerConf { port = 3000, serverName = "localhost"}++runWithConfig :: ServerConf -> Hack2.Application -> IO ()+runWithConfig conf = simpleHTTP nullConf { H.port = port conf } . appToServerPart conf++run :: Hack2.Application -> IO ()+run = runWithConfig def++appToServerPart :: ServerConf -> Hack2.Application -> ServerPart (Happstack.Response)+appToServerPart conf app = askRq >>= liftIO . (hackRToServerPartR <$>) . app . reqToEnv+ where+ reqToEnv req =+ def+ { Hack2.requestMethod = convertRequestMethod $ rqMethod req+ , Hack2.scriptName = L.empty+ , Hack2.pathInfo = L.pack $ escape $ "/" ++ (intercalate "/" $ rqPaths req)+ , Hack2.queryString = L.pack $ escape $ dropWhile (== '?') $ rqQuery req+ , Hack2.serverName = serverName conf+ , Hack2.serverPort = (snd $ rqPeer req)+ , Hack2.httpHeaders = headersToHttp (rqHeaders req)+ , Hack2.hackInput = (\(Body x) -> x) (rqBody req)+ , Hack2.hackHeaders = [("RemoteHost", L.pack $ fst $ rqPeer req)]+ }+ + escape = escapeURIString isAllowedInURI+ + convertRequestMethod Happstack.OPTIONS = Hack2.OPTIONS+ convertRequestMethod Happstack.GET = Hack2.GET+ convertRequestMethod Happstack.HEAD = Hack2.HEAD+ convertRequestMethod Happstack.POST = Hack2.POST+ convertRequestMethod Happstack.PUT = Hack2.PUT+ convertRequestMethod Happstack.DELETE = Hack2.DELETE+ convertRequestMethod Happstack.TRACE = Hack2.TRACE+ convertRequestMethod Happstack.CONNECT = Hack2.CONNECT+ ++headersToHttp :: Headers -> [(L.ByteString, L.ByteString)]+headersToHttp = M.toList >>> map snd >>> map headerToPair+ where+ headerToPair (HeaderPair k v) = + (L.pack $ normalizeHeader $ S.unpack k, s2l $ S.intercalate " " v)++hackRToServerPartR :: Hack2.Response -> Happstack.Response+hackRToServerPartR r = Happstack.Response + { rsCode = Hack2.status r+ , rsHeaders = httpToHeaders $ Hack2.headers r+ , rsFlags = RsFlags {rsfContentLength = False}+ , rsBody = Hack2.body r+ , rsValidator = Nothing + }++l2s :: L.ByteString -> S.ByteString+l2s = S.concat . L.toChunks++s2l :: S.ByteString -> L.ByteString+s2l = L.fromChunks . return++httpToHeaders :: [(L.ByteString, L.ByteString)] -> Headers+httpToHeaders = map pairToHeader >>> M.fromList+ where + pairToHeader (k,v) = + ((S.pack $ map toLower $ L.unpack k), HeaderPair (l2s k) [l2s 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"+ ]