nfjinjing / hack
- Source
- Commits
- Network (2)
- Issues (0)
- Downloads (0)
- Wiki (2)
- Graphs
-
Branch:
prep
commit 7d820655dd96cfc171b780b3d1fda3c7e1cd294a
tree a42b54e0ac12d4b4e940d21ab46c07f5b92841f3
parent 86633a09afacc75e1521418c1fd2cc4223c58e2a
tree a42b54e0ac12d4b4e940d21ab46c07f5b92841f3
parent 86633a09afacc75e1521418c1fd2cc4223c58e2a
hack /
| name | age | message | |
|---|---|---|---|
| |
.gitignore | Sat Jun 13 22:16:25 -0700 2009 | |
| |
LICENSE | ||
| |
Nemesis | Mon Nov 30 21:50:56 -0800 2009 | |
| |
Setup.lhs | Fri May 15 05:21:46 -0700 2009 | |
| |
bench/ | ||
| |
changelog.md | ||
| |
hack.cabal | ||
| |
readme.md | ||
| |
src/ |
readme.md
Hack: a Haskell Webserver Interface
Hack is a brain-dead port of the brilliant Ruby Rack webserver interface.
Introduction
Idea
Separation of concerns:
- hack: the spec
- hack-middleware: building blocks
- hack-handler: back-ends
Design
type Application = Env -> IO Response
Demo
import Hack
import Hack.Handler.Happstack
import Data.ByteString.Lazy.Char8 (pack)
app :: Application
app = \env -> return $
Response 200 [ ("Content-Type", "text/plain") ] (pack "Hello World")
main = run app
Spec
The Environment
- requestMethod: The HTTP request method, e.g.
GET,POST. - scriptName: The initial portion of the request URL‘s “path” that corresponds to the application object, so that the application knows its virtual “location”. This may be an empty string, if the application corresponds to the “root” of the server.
- pathInfo: The remainder of the request URL‘s “path”, designating the virtual “location” of the request‘s target within the application. This may be an empty string, if the request URL targets the application root and does not have a trailing slash. This value may be percent-encoded when I originating from a URL.
- queryString: The portion of the request URL that follows the ?, if any. May be empty, but is always required!
- serverName, serverPort: When combined with scriptName and pathInfo, these variables can be used to complete the URL. Note, however, that
Hostin http field, if present, should be used in preference to serverName for reconstructing the request URL. serverName and serverPort can never be empty, and so are always required. - http: Variables corresponding to the client-supplied HTTP request headers (e.g. "Accept"). The presence or absence of these variables should correspond with the presence or absence of the appropriate HTTP header in the request.
- hackVersion: The list of
Int, representing this version of Hack - hackUrlScheme:
HTTPorHTTPS, depending on the request URL. - hackInput: The body of the request.
- hackErrors: The error stream.
- hackHeaders: None http headers, intended to be used by handlers and middleware.
The Response
- status: This is an HTTP status. It must be greater than or equal to 100.
- headers: The header must not contain a Status key, contain keys with : or newlines in their name, contain keys names that end in - or _, but only contain keys that consist of letters, digits, _ or - and start with a letter. The values of the header must be Strings, consisting of lines (for multiple header values) separated by “\n”. The lines must not contain characters below 037.
- body: The body of the response.
Properties
- The scriptName, if non-empty, must start with /
- The pathInfo, if non-empty, must start with /
- One of scriptName or pathInfo must be set. pathInfo should be / if scriptName is empty. scriptName never should be /, but instead be empty.
1 minute tutorial
update cabal
cabal update
install hack
cabal install hack
pick a backend
cabal install hack-handler-happstack
Create a Hack app
put the following code in src/Main.hs
import Hack
import Hack.Handler.Happstack
import Data.ByteString.Lazy.Char8 (pack)
app :: Application
app = \env -> return $ Response
{ status = 200
, headers = [ ("Content-Type", "text/plain") ]
, body = pack "Hello World"
}
main = run app
run
ghc --make -O2 Main.hs
./Main
It should be running on http://127.0.0.1:3000 now.
Middleware
demo usage of middleware
install hack-contrib:
cabal install happy
cabal install hack-contrib
put the following in Main.hs. This code uses the URLMap middleware to route both /hello and /there to the hello application.
import Hack
import Hack.Handler.Happstack
import Hack.Contrib.Utils
import Hack.Contrib.Middleware.URLMap
import Data.ByteString.Lazy.Char8 (pack)
import Data.Default
hello :: Application
hello = \env -> return $ def {body = pack $ show env}
app :: Application
app = url_map [("/hello", hello), ("/there", hello)] empty_app
main = run app
create a middleware
inside Hack.hs:
type Middleware = Application -> Application
since Haskell has curry, middleware api can be of type
Anything -> Application -> Application
just pass an applied middleware into a chain.
finally the source code of URLMap.hs:
module Hack.Contrib.Middleware.URLMap (url_map) where
import Hack
import Hack.Contrib.Utils
import MPSUTF8
import Prelude hiding ((.), (^), (>))
import List (find, isPrefixOf)
type RoutePath = (String, Application)
url_map :: [RoutePath] -> Middleware
url_map h app = \env ->
let path = env.path_info
script = env.script_name
mod_env location = env
{ scriptName = script ++ location
, pathInfo = path.drop (location.length)
}
in
case h.find (fst > (`isPrefixOf` path) ) of
Nothing -> app env
Just (location, app') -> app' (mod_env location)
Use the middleware stack
From Hack.Contrib.Utils:
-- usage: app.use [content_type, cache]
use :: [Middleware] -> Middleware
use = reduce (<<<)
Handlers
Once an application is written using Hack, it should work on any web server that provides a Hack handler.
The handler should expose only one function of type:
run :: Application -> IO ()
Links
Discuss
Resources
- hack-contrib
- hack-handler-happstack
- hack-handler-cgi
- hack-handler-fcgi
- hack-handler-kibro
- hack-handler-hyena
- hack-frontend-monadcgi
- hack-frontend-happstack
- hack-middleware-cleanpath
- hack-middleware-clientsession
- hack-middleware-gzip
- hack-middleware-jsonp

