wai 0.2.2.1 → 0.3.0
raw patch · 4 files changed
+42/−282 lines, 4 filesdep +blaze-builderdep +enumeratordep +networkdep −directorydep ~base
Dependencies added: blaze-builder, enumerator, network
Dependencies removed: directory
Dependency ranges changed: base
Files
- Network/Wai.hs +36/−89
- Network/Wai/Enumerator.hs +0/−141
- Network/Wai/Source.hs +0/−46
- wai.cabal +6/−6
Network/Wai.hs view
@@ -43,8 +43,10 @@ , mkCIByteString -- ** Request header names , RequestHeader+ , RequestHeaders -- ** Response header names , ResponseHeader+ , ResponseHeaders -- ** Response status code , Status (..) , status200@@ -57,17 +59,15 @@ , status404 , status405 , status500- -- ** Response body- , ResponseBody (..)- -- ** Source- , Source (..)- -- * Enumerator- , Enumerator (..) -- * WAI interface , Request (..) , Response (..)+ , ResponseEnumerator+ , responseEnumerator , Application , Middleware+ -- * Response body smart constructors+ , responseLBS ) where import qualified Data.ByteString as B@@ -76,6 +76,12 @@ import Data.Char (toLower) import Data.String (IsString (..)) import Data.Typeable (Typeable)+import Data.Enumerator (Iteratee, ($$), joinI, run_)+import qualified Data.Enumerator as E+import Data.Enumerator.IO (enumFile)+import Blaze.ByteString.Builder (Builder, fromByteString, fromLazyByteString)+import Data.Data (Data)+import Network.Socket (SockAddr) -- | HTTP request method. Since the HTTP protocol allows arbitrary request -- methods, we leave this open as a 'B.ByteString'. Please note the request@@ -111,6 +117,7 @@ { ciOriginal :: !B.ByteString , ciLowerCase :: !B.ByteString }+ deriving (Data, Typeable) -- | Convert a regular bytestring to a case-insensitive bytestring. mkCIByteString :: B.ByteString -> CIByteString@@ -118,6 +125,8 @@ instance Show CIByteString where show = show . ciOriginal+instance Read CIByteString where+ readsPrec i = map (\(x, y) -> (mkCIByteString x, y)) . readsPrec i instance Eq CIByteString where x == y = ciLowerCase x == ciLowerCase y instance Ord CIByteString where@@ -128,10 +137,12 @@ -- | Headers sent from the client to the server. Note that this is a -- case-insensitive string, as the HTTP spec specifies. type RequestHeader = CIByteString+type RequestHeaders = [(RequestHeader, B.ByteString)] -- | Headers sent from the server to the client. Note that this is a -- case-insensitive string, as the HTTP spec specifies. type ResponseHeader = CIByteString+type ResponseHeaders = [(ResponseHeader, B.ByteString)] -- | HTTP status code; a combination of the integral code and a status message. -- Equality is determined solely on the basis of the integral code.@@ -181,60 +192,6 @@ status500 :: Status status500 = Status 500 $ B8.pack "Internal Server Error" --- | 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.------ This datatype is used by WAI to represent a request body. We choose this--- over an enumerator in that it gives the application power over control flow.--- This not only makes it easier to use in many situations, but also allows--- implementation of some features such as a backtracking parser which doesn't--- read the entire body into memory.-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.------ In WAI, an Enumerator is used to represent the response body. We have--- specifically chosen one of the simplest representations of an enumerator to--- avoid coding complication and performance overhead.-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@@ -252,45 +209,35 @@ , requestHeaders :: [(RequestHeader, B.ByteString)] -- ^ Was this request made over an SSL connection? , isSecure :: Bool- , requestBody :: Source -- ^ Log the given line in some method; how this is accomplished is -- server-dependant. , errorHandler :: String -> IO () -- | The client\'s host information.- , remoteHost :: B.ByteString+ , remoteHost :: SockAddr } deriving Typeable --- | The response body returned to the server from the application. We provide--- three separate constructors as optimizations:------ * 'ResponseEnumerator' is the most general type, allowing constant-memory--- production of a response, even in the presence of interleaved I\/O actions.------ * 'ResponseFile' serves a static file from the filesystem. Many servers use--- a sendfile system call to optimize this type of serving, making this a huge--- performance gain.------ * 'ResponseLBS'. Often times, we wish to return a response that includes no--- interleaved I\/O. In this case, we can use Haskell's natural laziness to our--- advantage, and represent the response as a lazy bytestring.-data ResponseBody = ResponseFile FilePath- | ResponseEnumerator Enumerator- | ResponseLBS L.ByteString+data Response+ = ResponseFile Status ResponseHeaders FilePath+ | ResponseBuilder Status ResponseHeaders Builder+ | ResponseEnumerator (forall a. ResponseEnumerator a) deriving Typeable -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 :: ResponseBody- }- deriving Typeable+type ResponseEnumerator a =+ (Status -> ResponseHeaders -> Iteratee Builder IO a) -> IO a -type Application = Request -> IO Response+responseEnumerator :: Response -> ResponseEnumerator a+responseEnumerator (ResponseEnumerator e) f = e f+responseEnumerator (ResponseFile s h fp) f =+ run_ $ enumFile fp $$ joinI $ E.map fromByteString $$ f s h+responseEnumerator (ResponseBuilder s h b) f = run_ $ do+ E.yield () $ E.Chunks [b]+ f s h++responseLBS :: Status -> ResponseHeaders -> L.ByteString -> Response+responseLBS s h = ResponseBuilder s h . fromLazyByteString++type Application = Request -> Iteratee B.ByteString 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
− Network/Wai/Enumerator.hs
@@ -1,141 +0,0 @@-{-# 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- , fromHandleFinally- -- ** FilePath- , fromFile- , fromFileFinally- , fromTempFile- , fromResponseBody- ) where--import Network.Wai (Enumerator (..), Source (..), ResponseBody (..))-import qualified Network.Wai.Source as Source-import qualified Data.ByteString.Lazy as L-import qualified Data.ByteString as B-import System.Directory (removeFile)-import System.IO (withBinaryFile, IOMode (ReadMode), Handle, hIsEOF)-import Data.ByteString.Lazy.Internal (defaultChunkSize)-import Control.Concurrent (forkIO)-import Control.Concurrent.MVar-import Control.Exception-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- buff <- newEmptyMVar- _ <- forkIO $ e (helper buff) () >> putMVar buff Nothing- return $ source buff- where- helper :: MVar (Maybe B.ByteString)- -> ()- -> B.ByteString- -> IO (Either () ())- helper buff _ bs = do- putMVar buff $ 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'---- | Wrapper around fromHandle to perform an action after EOF or an exception.-fromHandleFinally :: Handle -> IO a -> Enumerator-fromHandleFinally h onEOF = Enumerator $ \iter a0 ->- finally (runEnumerator (fromHandle h) iter a0)- onEOF---- | 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---- | Wrapper around fromFile to perform an action after the file is closed.-fromFileFinally :: FilePath -> IO a -> Enumerator-fromFileFinally fp onClose = Enumerator $ \iter a0 ->- finally (runEnumerator (fromFile fp) iter a0)- onClose---- | Enumerator to read and remove a file. Being based on 'fromFileFinally', it--- ensures the file is removed, even in the presence of exceptions.-fromTempFile :: FilePath -> Enumerator-fromTempFile fp = fromFileFinally fp $ removeFile fp---- | Since the response body is defined as a 'ResponseBody', this function--- simply reduces the whole value to an enumerator. This can be convenient for--- server implementations not optimizing file sending.-fromResponseBody :: ResponseBody -> Enumerator-fromResponseBody (ResponseEnumerator e) = e-fromResponseBody (ResponseLBS lbs) = fromLBS lbs-fromResponseBody (ResponseFile fp) = fromFile fp
− Network/Wai/Source.hs
@@ -1,46 +0,0 @@-module Network.Wai.Source- (- -- * Conversions- toEnumerator- , toLBS- , fromLBS- ) 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---- | Convert a lazy bytestring to a 'Source'. This operation does not request lazy I\/O.-fromLBS :: L.ByteString -> Source-fromLBS =- go . L.toChunks- where- go [] = Source $ return Nothing- go (x:xs) = Source $ return $ Just (x, go xs)
wai.cabal view
@@ -1,5 +1,5 @@ Name: wai-Version: 0.2.2.1+Version: 0.3.0 Synopsis: Web Application Interface. Description: Provides a common protocol for communication between web aplications and web servers. License: BSD3@@ -17,10 +17,10 @@ location: git://github.com/snoyberg/wai.git Library- Build-Depends: base >= 3 && < 5,- bytestring >= 0.9 && < 0.10,- directory >= 1.0 && < 1.2+ Build-Depends: base >= 3 && < 5+ , bytestring >= 0.9 && < 0.10+ , blaze-builder >= 0.2 && < 0.3+ , enumerator >= 0.4 && < 0.5+ , network >= 2.2 && < 2.4 Exposed-modules: Network.Wai- Network.Wai.Enumerator- Network.Wai.Source ghc-options: -Wall