packages feed

wai (empty) → 0.0.0

raw patch · 6 files changed

+613/−0 lines, 6 filesdep +basedep +bytestringsetup-changed

Dependencies added: base, bytestring

Files

+ LICENSE view
@@ -0,0 +1,25 @@+The following license covers this documentation, and the source code, except+where otherwise indicated.++Copyright 2010, Michael Snoyman. 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.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "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 HOLDERS 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.
+ Network/Wai.hs view
@@ -0,0 +1,406 @@+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE ExistentialQuantification #-}+{-|++This module defines a generic web application interface. It is a common+protocol between web servers and web applications.++The overriding design principles here are performance, generality and type+safety. To address performance, this library is built on 'Source' for the+request body and 'Enumerator' for the response bodies. The advantages of this+approach over lazy IO have been debated elsewhere.++Nonetheless, many people find these data structures difficult to work with. For+that reason, this library includes the "Network.Wai.Enumerator" module to+provide more familiar abstractions, including lazy IO.++Generality is achieved by removing many variables commonly found in similar+projects that are not universal to all servers. The goal is that the 'Request'+object contains only data which is meaningful in all circumstances.++Unlike other approaches, this package declares many data types to assist in+type safety. This feels more inline with the general Haskell spirit.++A final note: please remember when using this package that, while your+application my compile without a hitch against many different servers, there+are other considerations to be taken when moving to a new backend. For example,+if you transfer from a CGI application to a FastCGI one, you might suddenly+find you have a memory leak. Conversely, a FastCGI application would be+well served to preload all templates from disk when first starting; this+would kill the performance of a CGI application.++-}+module Network.Wai+    ( -- * Data types++      -- $show_read+      -- ** Request method+      Method (..)+    , methodFromBS+    , methodToBS+      -- ** URL scheme (http versus https)+    , UrlScheme (..)+      -- ** HTTP protocol versions+    , HttpVersion (..)+    , httpVersionFromBS+    , httpVersionToBS+      -- ** Request header names+    , RequestHeader (..)+    , requestHeaderFromBS+    , requestHeaderToBS+      -- ** Response header names+    , ResponseHeader (..)+    , responseHeaderFromBS+    , responseHeaderToBS+      -- ** Response status code+    , Status (..)+    , statusCode+    , statusMessage+      -- ** Source+    , Source (..)+      -- * Enumerator+    , Enumerator (..)+      -- * WAI interface+    , Request (..)+    , Response (..)+    , Application+    , Middleware+    ) where++import qualified Data.ByteString as B+import qualified Data.ByteString.Char8 as B8++-- $show_read+--+-- For the data types below, you should only use the 'Show' and 'Read'+-- instances for debugging purposes. Each datatype (excepting 'UrlScheme') has+-- associated functions for converting to and from strict 'B.ByteString's;+-- these are approrpiate for generating content.++-- | HTTP request method. This data type is extensible via the Method+-- constructor. Request methods are case-sensitive, and comparison is achieved+-- by converting to a 'B.ByteString' via 'methodToBS'.+data Method =+    OPTIONS+  | GET+  | HEAD+  | POST+  | PUT+  | DELETE+  | TRACE+  | CONNECT+  | Method B.ByteString+  deriving (Show, Read)++instance Eq Method where+    x == y = methodToBS x == methodToBS y++methodFromBS :: B.ByteString -> Method+methodFromBS bs+    | B.length bs <= 7 = case B8.unpack bs of+        "OPTIONS" -> OPTIONS+        "GET" -> GET+        "HEAD" -> HEAD+        "POST" -> POST+        "PUT" -> PUT+        "DELETE" -> DELETE+        "TRACE" -> TRACE+        "CONNECT" -> CONNECT+        _ -> Method bs+    | otherwise = Method bs++methodToBS :: Method -> B.ByteString+methodToBS OPTIONS = B8.pack "OPTIONS"+methodToBS GET = B8.pack "GET"+methodToBS HEAD = B8.pack "HEAD"+methodToBS POST = B8.pack "POST"+methodToBS PUT = B8.pack "PUT"+methodToBS DELETE = B8.pack "DELETE"+methodToBS TRACE = B8.pack "TRACE"+methodToBS CONNECT = B8.pack "CONNECT"+methodToBS (Method bs) = bs++data UrlScheme = HTTP | HTTPS+    deriving (Show, Eq)++-- | Version of HTTP protocol used in current request. This data type is+-- extensible via the HttpVersion constructor. Comparison is achieved by+-- converting to a 'B.ByteString' via 'httpVersionToBS'.+data HttpVersion =+      Http09+    | Http10+    | Http11+    | HttpVersion B.ByteString+    deriving (Show, Read)++instance Eq HttpVersion where+    x == y = httpVersionToBS x == httpVersionToBS y++-- | This function takes the information after \"HTTP\/\". For example:+--+-- @ 'httpVersionFromBS' ('B8.pack' \"1.0\") == 'Http10' @+httpVersionFromBS :: B.ByteString -> HttpVersion+httpVersionFromBS bs+    | B.length bs == 3 = case B8.unpack bs of+        "0.9" -> Http09+        "1.0" -> Http10+        "1.1" -> Http11+        _ -> HttpVersion bs+    | otherwise = HttpVersion bs++-- | Returns the version number, for example:+--+-- @ 'B8.unpack' ('httpVersionToBS' 'Http10') == \"1.0\" @+httpVersionToBS :: HttpVersion -> B.ByteString+httpVersionToBS Http09 = B8.pack "0.9"+httpVersionToBS Http10 = B8.pack "1.0"+httpVersionToBS Http11 = B8.pack "1.1"+httpVersionToBS (HttpVersion bs) = bs++-- | Headers sent from the client to the server. Clearly, this is not a+-- complete list of all possible headers, but rather a selection of common+-- ones. If other headers are required, they can be created with the+-- RequestHeader constructor.+--+-- The naming rules are simple: removing any hyphens from the actual name, and+-- if there is a naming conflict with a 'ResponseHeader', prefix with Req.+--+-- Equality determined by conversion via 'requestHeaderToBS'. Remember, headers+-- are case sensitive.+data RequestHeader =+      Accept+    | AcceptCharset+    | AcceptEncoding+    | AcceptLanguage+    | Authorization+    | Cookie+    | ReqContentLength+    | ReqContentType+    | Host+    | Referer+    | RequestHeader B.ByteString+    deriving (Show, Read)++instance Eq RequestHeader where+    x == y = requestHeaderToBS x == requestHeaderToBS y++requestHeaderFromBS :: B.ByteString -> RequestHeader+requestHeaderFromBS bs = case B8.unpack bs of+    "Accept" -> Accept+    "Accept-Charset" -> AcceptCharset+    "Accept-Encoding" -> AcceptEncoding+    "Accept-Language" -> AcceptLanguage+    "Authorization" -> Authorization+    "Cookie" -> Cookie+    "Content-Length" -> ReqContentLength+    "Content-Type" -> ReqContentType+    "Host" -> Host+    "Referer" -> Referer+    _ -> RequestHeader bs++requestHeaderToBS :: RequestHeader -> B.ByteString+requestHeaderToBS Accept = B8.pack "Accept"+requestHeaderToBS AcceptCharset = B8.pack "Accept-Charset"+requestHeaderToBS AcceptEncoding = B8.pack "Accept-Encoding"+requestHeaderToBS AcceptLanguage = B8.pack "Accept-Language"+requestHeaderToBS Authorization = B8.pack "Authorization"+requestHeaderToBS Cookie = B8.pack "Cookie"+requestHeaderToBS ReqContentLength = B8.pack "Content-Length"+requestHeaderToBS ReqContentType = B8.pack "Content-Type"+requestHeaderToBS Host = B8.pack "Host"+requestHeaderToBS Referer = B8.pack "Referer"+requestHeaderToBS (RequestHeader bs) = bs+++-- | Headers sent from the server to the client. Clearly, this is not a+-- complete list of all possible headers, but rather a selection of common+-- ones. If other headers are required, they can be created with the+-- ResponseHeader constructor.+--+-- if there is a naming conflict with a 'ResponseHeader', prefix with Req.+--+-- Equality determined by conversion via 'responseHeaderToBS'. Remember,+-- headers are case sensitive.+data ResponseHeader =+      ContentEncoding+    | ContentLanguage+    | ContentLength+    | ContentDisposition+    | ContentType+    | Expires+    | Location+    | Server+    | SetCookie+    | ResponseHeader B.ByteString+     deriving (Show)++instance Eq ResponseHeader where+    x == y = responseHeaderToBS x == responseHeaderToBS y++responseHeaderFromBS :: B.ByteString -> ResponseHeader+responseHeaderFromBS bs = case B8.unpack bs of+    "Content-Encoding" -> ContentEncoding+    "Content-Language" -> ContentLanguage+    "Content-Length" -> ContentLength+    "Content-Disposition" -> ContentDisposition+    "Content-Type" -> ContentType+    "Expires" -> Expires+    "Location" -> Location+    "Server" -> Server+    "Set-Cookie" -> SetCookie+    _ -> ResponseHeader bs++responseHeaderToBS :: ResponseHeader -> B.ByteString+responseHeaderToBS ContentEncoding = B8.pack "Content-Encoding"+responseHeaderToBS ContentLanguage = B8.pack "Content-Language"+responseHeaderToBS ContentLength = B8.pack "Content-Length"+responseHeaderToBS ContentDisposition = B8.pack "Content-Disposition"+responseHeaderToBS ContentType = B8.pack "Content-Type"+responseHeaderToBS Expires = B8.pack "Expires"+responseHeaderToBS Location = B8.pack "Location"+responseHeaderToBS Server = B8.pack "Server"+responseHeaderToBS SetCookie = B8.pack "Set-Cookie"+responseHeaderToBS (ResponseHeader bs) = bs++-- | This attempts to provide the most common HTTP status codes, not all of+-- them. Use the Status constructor when you want to create a status code not+-- provided.+--+-- The 'Eq' instance tests equality based only on the numeric status code+-- value. See 'statusCode'.+data Status =+      Status200+    | Status301+    | Status302+    | Status303+    | Status400+    | Status401+    | Status403+    | Status404+    | Status405+    | Status500+    | Status Int B.ByteString+    deriving Show++instance Eq Status where+    x == y = statusCode x == statusCode y++statusCode :: Status -> Int+statusCode Status200 = 200+statusCode Status301 = 301+statusCode Status302 = 302+statusCode Status303 = 303+statusCode Status400 = 400+statusCode Status401 = 401+statusCode Status403 = 403+statusCode Status404 = 404+statusCode Status405 = 405+statusCode Status500 = 500+statusCode (Status i _) = i++statusMessage :: Status -> B.ByteString+statusMessage Status200 = B8.pack "OK"+statusMessage Status301 = B8.pack "Moved Permanently"+statusMessage Status302 = B8.pack "Found"+statusMessage Status303 = B8.pack "See Other"+statusMessage Status400 = B8.pack "Bad Request"+statusMessage Status401 = B8.pack "Unauthorized"+statusMessage Status403 = B8.pack "Forbidden"+statusMessage Status404 = B8.pack "Not Found"+statusMessage Status405 = B8.pack "Method Not Allowed"+statusMessage Status500 = B8.pack "Internal Server Error"+statusMessage (Status _ m) = m++-- | This is a source for 'B.ByteString's. It is a function (wrapped in a+-- newtype) that will return Nothing if the data has been completely consumed,+-- or return the next 'B.ByteString' from the source along with a new 'Source'+-- to continue reading from.+--+-- Be certain not to reuse a 'Source'! It might work fine with some+-- implementations of 'Source', while causing bugs with others.+newtype Source = Source { runSource :: IO (Maybe (B.ByteString, Source)) }++-- | An enumerator is a data producer. It takes two arguments: a function to+-- enumerate over (the iteratee) and an accumulating parameter. As the+-- enumerator produces output, it calls the iteratee, thereby avoiding the need+-- to allocate large amounts of memory for storing the entire piece of data.+--+-- Normally in Haskell, we can achieve the same results with laziness. For+-- example, an inifinite list does not require inifinite memory storage; we+-- simply get away with thunks. However, when operating in the IO monad, we do+-- not have this luxury. There are other approaches, such as lazy I\/O. If you+-- would like to program in this manner, please see+-- "Network.Wai.Enumerator", in particular toLBS.+--+-- That said, let's address the details of this particular enumerator+-- implementation. You'll notice that the iteratee is a function that takes two+-- arguments and returns an 'Either' value. The second argument is simply the+-- piece of data generated by the enumerator. The 'Either' value at the end is+-- a means to alert the enumerator whether to continue or not. If it returns+-- 'Left', then the enumeration should cease. If it returns 'Right', it should+-- continue.+--+-- The accumulating parameter (a) has meaning only to the iteratee; the+-- enumerator simply passes it around. The enumerator itself also returns an+-- 'Either' value; a 'Right' means the enumerator ran to completion, while a+-- 'Left' indicates early termination was requested by the iteratee.+--+-- 'Enumerator's are not required to be resumable. That is to say, the+-- 'Enumerator' may only be called once. While this requirement puts a bit of a+-- strain on the caller in some situations, it saves a large amount of+-- complication- and thus performance- on the producer.+newtype Enumerator = Enumerator { runEnumerator :: forall a.+              (a -> B.ByteString -> IO (Either a a))+                 -> a+                 -> IO (Either a a)+}++-- | Information on the request sent by the client. This abstracts away the+-- details of the underlying implementation.+data Request = Request+  {  requestMethod  :: Method+  ,  httpVersion    :: HttpVersion+  -- | Extra path information sent by the client. The meaning varies slightly+  -- depending on backend; in a standalone server setting, this is most likely+  -- all information after the domain name. In a CGI application, this would be+  -- the information following the path to the CGI executable itself.+  ,  pathInfo       :: B.ByteString+  -- | If no query string was specified, this should be empty.+  ,  queryString    :: B.ByteString+  ,  serverName     :: B.ByteString+  ,  serverPort     :: Int+  ,  requestHeaders :: [(RequestHeader, B.ByteString)]+  ,  urlScheme      :: UrlScheme+  ,  requestBody    :: Source+  ,  errorHandler   :: String -> IO ()+  -- | The client\'s host information.+  ,  remoteHost     :: B.ByteString+  }++data Response = Response+  { status          :: Status+  , responseHeaders :: [(ResponseHeader, B.ByteString)]+  -- | A common optimization is to use the sendfile system call when sending+  -- files from the disk. This datatype facilitates this optimization; if+  -- 'Left' is returned, the server will send the file from the disk by+  -- whatever means it wishes. If 'Right', it will call the 'Enumerator'.+  , responseBody    :: Either FilePath Enumerator+  }++type Application = Request -> IO Response++-- | Middleware is a component that sits between the server and application. It+-- can do such tasks as GZIP encoding or response caching. What follows is the+-- general definition of middleware, though a middleware author should feel+-- free to modify this.+--+-- As an example of an alternate type for middleware, suppose you write a+-- function to load up session information. The session information is simply a+-- string map \[(String, String)\]. A logical type signatures for this middleware+-- might be:+--+-- @ loadSession :: ([(String, String)] -> Application) -> Application @+--+-- Here, instead of taking a standard 'Application' as its first argument, the+-- middleware takes a function which consumes the session information as well.+type Middleware = Application -> Application
+ Network/Wai/Enumerator.hs view
@@ -0,0 +1,117 @@+{-# LANGUAGE Rank2Types #-}+-- | A collection of utility functions for dealing with 'Enumerator's.+module Network.Wai.Enumerator+    ( -- * Utilities+      mapE+      -- * Conversions+    , -- ** Lazy byte strings+      toLBS+    , fromLBS+    , fromLBS'+      -- ** Source+    , toSource+      -- ** Handle+    , fromHandle+      -- ** FilePath+    , fromFile+    , fromEitherFile+    ) where++import Network.Wai (Enumerator (..), Source (..))+import qualified Network.Wai.Source as Source+import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString as B+import System.IO (withBinaryFile, IOMode (ReadMode), Handle, hIsEOF)+import Data.ByteString.Lazy.Internal (defaultChunkSize)+import Control.Concurrent (forkIO)+import Control.Concurrent.MVar+import Control.Monad ((<=<))++-- | Performs a specified conversion on each 'B.ByteString' output by an+-- enumerator.+mapE :: (B.ByteString -> B.ByteString) -> Enumerator -> Enumerator+mapE f (Enumerator e) = Enumerator $ \iter -> e (iter' iter) where+    iter' iter a = iter a . f++-- | This uses 'unsafeInterleaveIO' to lazily read from an enumerator. All+-- normal lazy I/O warnings apply. In addition, since it is based on+-- 'toSource', please observe all precautions for that function.+toLBS :: Enumerator -> IO L.ByteString+toLBS = Source.toLBS <=< toSource++-- | This function safely converts a lazy bytestring into an enumerator.+fromLBS :: L.ByteString -> Enumerator+fromLBS lbs = Enumerator $ \iter a0 -> helper iter a0 $ L.toChunks lbs where+    helper _ a [] = return $ Right a+    helper iter a (x:xs) = do+        ea <- iter a x+        case ea of+            Left a' -> return $ Left a'+            Right a' -> helper iter a' xs++-- | Same as 'fromLBS', but the lazy bytestring is in the IO monad. This allows+-- you to lazily read a file into memory, perform some mapping on the data and+-- convert it into an enumerator.+fromLBS' :: IO L.ByteString -> Enumerator+fromLBS' lbs' = Enumerator $ \iter a0 -> lbs' >>= \lbs ->+    runEnumerator (fromLBS lbs) iter a0++-- | This function uses another thread to convert an 'Enumerator' to a+-- 'Source'. In essence, this allows you to write code which \"pulls\" instead+-- of code which is pushed to. While this can be a useful technique, some+-- caveats apply:+--+-- * It will be more resource heavy than using the 'Enumerator' directly.+--+-- * You *must* consume all input. If you do not, then the other thread will be+-- deadlocked.+toSource :: Enumerator -> IO Source+toSource (Enumerator e) = do+    buffer <- newEmptyMVar+    _ <- forkIO $ e (helper buffer) () >> putMVar buffer Nothing+    return $ source buffer+      where+        helper :: MVar (Maybe B.ByteString)+               -> ()+               -> B.ByteString+               -> IO (Either () ())+        helper buffer _ bs = do+            putMVar buffer $ Just bs+            return $ Right ()+        source :: MVar (Maybe B.ByteString)+               -> Source+        source mmbs = Source $ do+            mbs <- takeMVar mmbs+            case mbs of+                Nothing -> do+                    -- By putting Nothing back in, the source can be called+                    -- again without causing a deadlock.+                    putMVar mmbs Nothing+                    return Nothing+                Just bs -> return $ Just (bs, source mmbs)++-- | Read a chunk of data from the given 'Handle' at a time. We use+-- 'defaultChunkSize' from the bytestring package to determine the largest+-- chunk to take.+fromHandle :: Handle -> Enumerator+fromHandle h = Enumerator $ \iter a -> do+    eof <- hIsEOF h+    if eof+        then return $ Right a+        else do+            bs <- B.hGet h defaultChunkSize+            ea' <- iter a bs+            case ea' of+                Left a' -> return $ Left a'+                Right a' -> runEnumerator (fromHandle h) iter a'++-- | A little wrapper around 'fromHandle' which first opens a file for reading.+fromFile :: FilePath -> Enumerator+fromFile fp = Enumerator $ \iter a0 -> withBinaryFile fp ReadMode $ \h ->+    runEnumerator (fromHandle h) iter a0++-- | Since the response body is defined as an 'Either' 'FilePath' 'Enumerator',+-- this function simply reduces the whole operator to an enumerator. This can+-- be convenient for server implementations not optimizing file sending.+fromEitherFile :: Either FilePath Enumerator -> Enumerator+fromEitherFile = either fromFile id
+ Network/Wai/Source.hs view
@@ -0,0 +1,37 @@+module Network.Wai.Source+    (+    -- * Conversions+      toEnumerator+    , toLBS+    ) where++import Network.Wai+import qualified Data.ByteString.Lazy as L+import System.IO.Unsafe (unsafeInterleaveIO)++-- | This function safely converts a 'Source' (where you pull data) to an+-- 'Enumerator' (which pushes the data to you). There should be no significant+-- performance impact from its use, and it uses no unsafe functions.+toEnumerator :: Source -> Enumerator+toEnumerator source0 = Enumerator $ helper source0 where+    helper source iter a = do+        next <- runSource source+        case next of+            Nothing -> return $ Right a+            Just (bs, source') -> do+                res <- iter a bs+                case res of+                    Left a' -> return $ Left a'+                    Right a' -> helper source' iter a'++-- | Uses lazy I\/O (via 'unsafeInterleaveIO') to provide a lazy interface to+-- the given 'Source'. Normal lazy I\/O warnings apply.+toLBS :: Source -> IO L.ByteString+toLBS source0 = L.fromChunks `fmap` helper source0 where+    helper source = unsafeInterleaveIO $ do+        next <- runSource source+        case next of+            Nothing -> return []+            Just (bs, source') -> do+                rest <- helper source'+                return $ bs : rest
+ Setup.lhs view
@@ -0,0 +1,7 @@+#!/usr/bin/env runhaskell++> module Main where+> import Distribution.Simple++> main :: IO ()+> main = defaultMain
+ wai.cabal view
@@ -0,0 +1,21 @@+Name:                wai+Version:             0.0.0+Synopsis:            Web Application Interface.+Description:         Provides a common protocol for communication between web aplications and web servers.+License:             BSD3+License-file:        LICENSE+Author:              Michael Snoyman+Maintainer:          michael@snoyman.com+Homepage:            http://github.com/snoyberg/wai+Category:            Web+Build-Type:          Simple+Cabal-Version:       >=1.2+Stability:           Stable++Library+  Build-Depends:     base >= 3 && < 5,+                     bytestring >= 0.9 && < 0.10+  Exposed-modules:   Network.Wai+                     Network.Wai.Enumerator+                     Network.Wai.Source+  ghc-options:       -Wall