on master help sign in
blaze-enumerator
commit98fc55619e3600df24144fec5a5fcdb3f909fe5b authorMichael Snoyman <michael@snoyman.com> dateTue 14 Dec 2010, 00:16 (16 years ago) parentfd4b6a4
5 files changed+13−439
Network/Wai.hs+7−62 Network/Wai/Enumerator.hs−141 Network/Wai/Handler/SimpleServer.hs−184 Network/Wai/Source.hs−46 wai.cabal+6−6
Network/Wai.hs+7−62
@@ -59,10 +59,6 @@ module Network.Wai
5959 , status500
6060 -- ** Response body
6161 , ResponseBody (..)
62− -- ** Source
63− , Source (..)
64− -- * Enumerator
65− , Enumerator (..)
6662 -- * WAI interface
6763 , Request (..)
6864 , Response (..)
@@ -76,6 +72,8 @@ import qualified Data.ByteString.Lazy as L
7672import Data.Char (toLower)
7773import Data.String (IsString (..))
7874import Data.Typeable (Typeable)
75+import Data.Enumerator (Enumerator, Iteratee)
76+import Blaze.ByteString.Builder (Builder)
7977
8078-- | HTTP request method. Since the HTTP protocol allows arbitrary request
8179-- methods, we leave this open as a 'B.ByteString'. Please note the request
@@ -181,60 +179,6 @@ status405 = Status 405 $ B8.pack "Method Not Allowed"
181179status500 :: Status
182180status500 = Status 500 $ B8.pack "Internal Server Error"
183181
184−-- | This is a source for 'B.ByteString's. It is a function (wrapped in a
185−-- newtype) that will return Nothing if the data has been completely consumed,
186−-- or return the next 'B.ByteString' from the source along with a new 'Source'
187−-- to continue reading from.
188−--
189−-- Be certain not to reuse a 'Source'! It might work fine with some
190−-- implementations of 'Source', while causing bugs with others.
191−--
192−-- This datatype is used by WAI to represent a request body. We choose this
193−-- over an enumerator in that it gives the application power over control flow.
194−-- This not only makes it easier to use in many situations, but also allows
195−-- implementation of some features such as a backtracking parser which doesn't
196−-- read the entire body into memory.
197−newtype Source = Source { runSource :: IO (Maybe (B.ByteString, Source)) }
198−
199−-- | An enumerator is a data producer. It takes two arguments: a function to
200−-- enumerate over (the iteratee) and an accumulating parameter. As the
201−-- enumerator produces output, it calls the iteratee, thereby avoiding the need
202−-- to allocate large amounts of memory for storing the entire piece of data.
203−--
204−-- Normally in Haskell, we can achieve the same results with laziness. For
205−-- example, an inifinite list does not require inifinite memory storage; we
206−-- simply get away with thunks. However, when operating in the IO monad, we do
207−-- not have this luxury. There are other approaches, such as lazy I\/O. If you
208−-- would like to program in this manner, please see
209−-- "Network.Wai.Enumerator", in particular toLBS.
210−--
211−-- That said, let's address the details of this particular enumerator
212−-- implementation. You'll notice that the iteratee is a function that takes two
213−-- arguments and returns an 'Either' value. The second argument is simply the
214−-- piece of data generated by the enumerator. The 'Either' value at the end is
215−-- a means to alert the enumerator whether to continue or not. If it returns
216−-- 'Left', then the enumeration should cease. If it returns 'Right', it should
217−-- continue.
218−--
219−-- The accumulating parameter (a) has meaning only to the iteratee; the
220−-- enumerator simply passes it around. The enumerator itself also returns an
221−-- 'Either' value; a 'Right' means the enumerator ran to completion, while a
222−-- 'Left' indicates early termination was requested by the iteratee.
223−--
224−-- 'Enumerator's are not required to be resumable. That is to say, the
225−-- 'Enumerator' may only be called once. While this requirement puts a bit of a
226−-- strain on the caller in some situations, it saves a large amount of
227−-- complication- and thus performance- on the producer.
228−--
229−-- In WAI, an Enumerator is used to represent the response body. We have
230−-- specifically chosen one of the simplest representations of an enumerator to
231−-- avoid coding complication and performance overhead.
232−newtype Enumerator = Enumerator { runEnumerator :: forall a.
233− (a -> B.ByteString -> IO (Either a a))
234− -> a
235− -> IO (Either a a)
236−}
237−
238182-- | Information on the request sent by the client. This abstracts away the
239183-- details of the underlying implementation.
240184data Request = Request
@@ -252,7 +196,7 @@ data Request = Request
252196 , requestHeaders :: [(RequestHeader, B.ByteString)]
253197 -- ^ Was this request made over an SSL connection?
254198 , isSecure :: Bool
255− , requestBody :: Source
199+ , requestBody :: forall a. Enumerator B.ByteString IO a
256200 -- ^ Log the given line in some method; how this is accomplished is
257201 -- server-dependant.
258202 , errorHandler :: String -> IO ()
@@ -274,9 +218,10 @@ data Request = Request
274218-- * 'ResponseLBS'. Often times, we wish to return a response that includes no
275219-- interleaved I\/O. In this case, we can use Haskell's natural laziness to our
276220-- advantage, and represent the response as a lazy bytestring.
277−data ResponseBody = ResponseFile FilePath
278− | ResponseEnumerator Enumerator
279− | ResponseLBS L.ByteString
221+data ResponseBody
222+ = ResponseFile FilePath
223+ | ResponseEnumerator (Iteratee Builder IO () -> IO ())
224+ | ResponseLBS L.ByteString
280225 deriving Typeable
281226
282227data Response = Response
Network/Wai/Enumerator.hsdeleted−141
@@ -1,141 +0,0 @@
1−{-# LANGUAGE Rank2Types #-}
2−-- | A collection of utility functions for dealing with 'Enumerator's.
3−module Network.Wai.Enumerator
4− ( -- * Utilities
5− mapE
6− -- * Conversions
7− , -- ** Lazy byte strings
8− toLBS
9− , fromLBS
10− , fromLBS'
11− -- ** Source
12− , toSource
13− -- ** Handle
14− , fromHandle
15− , fromHandleFinally
16− -- ** FilePath
17− , fromFile
18− , fromFileFinally
19− , fromTempFile
20− , fromResponseBody
21− ) where
22−
23−import Network.Wai (Enumerator (..), Source (..), ResponseBody (..))
24−import qualified Network.Wai.Source as Source
25−import qualified Data.ByteString.Lazy as L
26−import qualified Data.ByteString as B
27−import System.Directory (removeFile)
28−import System.IO (withBinaryFile, IOMode (ReadMode), Handle, hIsEOF)
29−import Data.ByteString.Lazy.Internal (defaultChunkSize)
30−import Control.Concurrent (forkIO)
31−import Control.Concurrent.MVar
32−import Control.Exception
33−import Control.Monad ((<=<))
34−
35−-- | Performs a specified conversion on each 'B.ByteString' output by an
36−-- enumerator.
37−mapE :: (B.ByteString -> B.ByteString) -> Enumerator -> Enumerator
38−mapE f (Enumerator e) = Enumerator $ \iter -> e (iter' iter) where
39− iter' iter a = iter a . f
40−
41−-- | This uses 'unsafeInterleaveIO' to lazily read from an enumerator. All
42−-- normal lazy I/O warnings apply. In addition, since it is based on
43−-- 'toSource', please observe all precautions for that function.
44−toLBS :: Enumerator -> IO L.ByteString
45−toLBS = Source.toLBS <=< toSource
46−
47−-- | This function safely converts a lazy bytestring into an enumerator.
48−fromLBS :: L.ByteString -> Enumerator
49−fromLBS lbs = Enumerator $ \iter a0 -> helper iter a0 $ L.toChunks lbs where
50− helper _ a [] = return $ Right a
51− helper iter a (x:xs) = do
52− ea <- iter a x
53− case ea of
54− Left a' -> return $ Left a'
55− Right a' -> helper iter a' xs
56−
57−-- | Same as 'fromLBS', but the lazy bytestring is in the IO monad. This allows
58−-- you to lazily read a file into memory, perform some mapping on the data and
59−-- convert it into an enumerator.
60−fromLBS' :: IO L.ByteString -> Enumerator
61−fromLBS' lbs' = Enumerator $ \iter a0 -> lbs' >>= \lbs ->
62− runEnumerator (fromLBS lbs) iter a0
63−
64−-- | This function uses another thread to convert an 'Enumerator' to a
65−-- 'Source'. In essence, this allows you to write code which \"pulls\" instead
66−-- of code which is pushed to. While this can be a useful technique, some
67−-- caveats apply:
68−--
69−-- * It will be more resource heavy than using the 'Enumerator' directly.
70−--
71−-- * You *must* consume all input. If you do not, then the other thread will be
72−-- deadlocked.
73−toSource :: Enumerator -> IO Source
74−toSource (Enumerator e) = do
75− buff <- newEmptyMVar
76− _ <- forkIO $ e (helper buff) () >> putMVar buff Nothing
77− return $ source buff
78− where
79− helper :: MVar (Maybe B.ByteString)
80− -> ()
81− -> B.ByteString
82− -> IO (Either () ())
83− helper buff _ bs = do
84− putMVar buff $ Just bs
85− return $ Right ()
86− source :: MVar (Maybe B.ByteString)
87− -> Source
88− source mmbs = Source $ do
89− mbs <- takeMVar mmbs
90− case mbs of
91− Nothing -> do
92− -- By putting Nothing back in, the source can be called
93− -- again without causing a deadlock.
94− putMVar mmbs Nothing
95− return Nothing
96− Just bs -> return $ Just (bs, source mmbs)
97−
98−-- | Read a chunk of data from the given 'Handle' at a time. We use
99−-- 'defaultChunkSize' from the bytestring package to determine the largest
100−-- chunk to take.
101−fromHandle :: Handle -> Enumerator
102−fromHandle h = Enumerator $ \iter a -> do
103− eof <- hIsEOF h
104− if eof
105− then return $ Right a
106− else do
107− bs <- B.hGet h defaultChunkSize
108− ea' <- iter a bs
109− case ea' of
110− Left a' -> return $ Left a'
111− Right a' -> runEnumerator (fromHandle h) iter a'
112−
113−-- | Wrapper around fromHandle to perform an action after EOF or an exception.
114−fromHandleFinally :: Handle -> IO a -> Enumerator
115−fromHandleFinally h onEOF = Enumerator $ \iter a0 ->
116− finally (runEnumerator (fromHandle h) iter a0)
117− onEOF
118−
119−-- | A little wrapper around 'fromHandle' which first opens a file for reading.
120−fromFile :: FilePath -> Enumerator
121−fromFile fp = Enumerator $ \iter a0 -> withBinaryFile fp ReadMode $ \h ->
122− runEnumerator (fromHandle h) iter a0
123−
124−-- | Wrapper around fromFile to perform an action after the file is closed.
125−fromFileFinally :: FilePath -> IO a -> Enumerator
126−fromFileFinally fp onClose = Enumerator $ \iter a0 ->
127− finally (runEnumerator (fromFile fp) iter a0)
128− onClose
129−
130−-- | Enumerator to read and remove a file. Being based on 'fromFileFinally', it
131−-- ensures the file is removed, even in the presence of exceptions.
132−fromTempFile :: FilePath -> Enumerator
133−fromTempFile fp = fromFileFinally fp $ removeFile fp
134−
135−-- | Since the response body is defined as a 'ResponseBody', this function
136−-- simply reduces the whole value to an enumerator. This can be convenient for
137−-- server implementations not optimizing file sending.
138−fromResponseBody :: ResponseBody -> Enumerator
139−fromResponseBody (ResponseEnumerator e) = e
140−fromResponseBody (ResponseLBS lbs) = fromLBS lbs
141−fromResponseBody (ResponseFile fp) = fromFile fp
Network/Wai/Handler/SimpleServer.hsdeleted−184
@@ -1,184 +0,0 @@
1−{-# LANGUAGE DeriveDataTypeable #-}
2−{-# LANGUAGE FlexibleContexts #-}
3−{-# LANGUAGE OverloadedStrings #-}
4−---------------------------------------------------------
5−-- |
6−-- Module : Network.Wai.Handler.SimpleServer
7−-- Copyright : Michael Snoyman
8−-- License : BSD3
9−--
10−-- Maintainer : Michael Snoyman <michael@snoyman.com>
11−-- Stability : Stable
12−-- Portability : portable
13−--
14−-- A simplistic HTTP server handler for Wai.
15−--
16−---------------------------------------------------------
17−module Network.Wai.Handler.SimpleServer
18− ( run
19− ) where
20−
21−import Network.Wai
22−import qualified System.IO
23−
24−import qualified Data.ByteString as BS
25−import qualified Data.ByteString.Lazy as L
26−import qualified Data.ByteString.Char8 as B8
27−import Network
28− ( listenOn, accept, sClose, PortID(PortNumber), Socket
29− , withSocketsDo)
30−import Control.Exception (bracket, finally, Exception)
31−import System.IO (Handle, hClose)
32−import Control.Concurrent (forkIO)
33−import Control.Monad (unless)
34−import Data.Maybe (isJust, fromJust, fromMaybe)
35−
36−import Control.Failure
37−import Data.Typeable (Typeable)
38−
39−import Web.Encodings.StringLike (StringLike)
40−import qualified Web.Encodings.StringLike as SL
41−
42−import qualified Safe
43−import Network.Socket.SendFile
44−import Control.Arrow (first)
45−
46−run :: Port -> Application -> IO ()
47−run port = withSocketsDo .
48− bracket
49− (listenOn $ PortNumber $ fromIntegral port)
50− sClose .
51− serveConnections port
52−type Port = Int
53−
54−serveConnections :: Port -> Application -> Socket -> IO ()
55−serveConnections port app socket = do
56− (conn, remoteHost', _) <- accept socket
57− _ <- forkIO $ serveConnection port app conn remoteHost'
58− serveConnections port app socket
59−
60−serveConnection :: Port -> Application -> Handle -> String -> IO ()
61−serveConnection port app conn remoteHost' =
62− finally
63− serveConnection'
64− (hClose conn)
65− where
66− serveConnection' = do
67− env <- hParseRequest port conn remoteHost'
68− res <- app env
69− sendResponse conn res
70−
71−hParseRequest :: Port -> Handle -> String -> IO Request
72−hParseRequest port conn remoteHost' = do
73− headers' <- takeUntilBlank conn id
74− parseRequest port headers' conn remoteHost'
75−
76−takeUntilBlank :: Handle
77− -> ([BS.ByteString] -> [BS.ByteString])
78− -> IO [BS.ByteString]
79−takeUntilBlank h front = do
80− l <- stripCR `fmap` BS.hGetLine h
81− if BS.null l
82− then return $ front []
83− else takeUntilBlank h $ front . (:) l
84−
85−stripCR :: BS.ByteString -> BS.ByteString
86−stripCR bs
87− | BS.null bs = bs
88− | B8.last bs == '\r' = BS.init bs
89− | otherwise = bs
90−
91−data InvalidRequest =
92− NotEnoughLines [String]
93− | HostNotIncluded
94− | BadFirstLine String
95− | NonHttp11
96− deriving (Show, Typeable)
97−instance Exception InvalidRequest
98−
99−-- | Parse a set of header lines and body into a 'Request'.
100−parseRequest :: Port
101− -> [BS.ByteString]
102− -> Handle
103− -> String
104− -> IO Request
105−parseRequest port lines' handle remoteHost' = do
106− case lines' of
107− (_:_:_) -> return ()
108− _ -> failure $ NotEnoughLines $ map SL.unpack lines'
109− (method, rpath', gets) <- parseFirst $ head lines'
110− let rpath = '/' : case SL.unpack rpath' of
111− ('/':x) -> x
112− _ -> SL.unpack rpath'
113− let heads = map (first mkCIByteString . parseHeaderNoAttr) $ tail lines'
114− let host' = lookup "Host" heads
115− unless (isJust host') $ failure HostNotIncluded
116− let host = fromJust host'
117− let len = fromMaybe 0 $ do
118− bs <- lookup "Content-Length" heads
119− let str = SL.unpack bs
120− Safe.readMay str
121− let (serverName', _) = SL.breakChar ':' host
122− return $ Request
123− { requestMethod = method
124− , httpVersion = http11
125− , pathInfo = SL.pack rpath
126− , queryString = gets
127− , serverName = serverName'
128− , serverPort = port
129− , requestHeaders = heads
130− , isSecure = False
131− , requestBody = requestBodyHandle handle len
132− , errorHandler = System.IO.hPutStr System.IO.stderr
133− , remoteHost = B8.pack remoteHost'
134− }
135−
136−requestBodyHandle :: Handle -> Int -> Source
137−requestBodyHandle _ 0 = Source $ return Nothing
138−requestBodyHandle h len = Source $ do
139− let maxChunkSize = 1024
140− bs <- BS.hGet h $ min len maxChunkSize
141− let newLen = len - BS.length bs
142− putStrLn $ "reading a chunk of size " ++ show (BS.length bs)
143− return $ Just (bs, requestBodyHandle h newLen)
144−
145−parseFirst :: (StringLike s, Failure InvalidRequest m) =>
146− s
147− -> m (s, s, s)
148−parseFirst s = do
149− let pieces = SL.split ' ' s
150− (method, query, http') <-
151− case pieces of
152− [x, y, z] -> return (x, y, z)
153− _ -> failure $ BadFirstLine $ SL.unpack s
154− unless (http' == SL.pack "HTTP/1.1") $ failure NonHttp11
155− let (rpath, qstring) = SL.breakChar '?' query
156− return (method, rpath, qstring)
157−
158−sendResponse :: Handle -> Response -> IO ()
159−sendResponse h res = do
160− BS.hPut h $ SL.pack "HTTP/1.1 "
161− BS.hPut h $ SL.pack $ show $ statusCode $ status res
162− BS.hPut h $ statusMessage $ status res
163− BS.hPut h $ SL.pack "\r\n"
164− mapM_ putHeader $ map (first ciOriginal) $ responseHeaders res
165− BS.hPut h $ SL.pack "\r\n"
166− case responseBody res of
167− ResponseFile fp -> unsafeSendFile h fp
168− ResponseEnumerator (Enumerator enum) -> enum myPut h >> return ()
169− ResponseLBS lbs -> L.hPutStr h lbs
170− where
171− myPut _ bs = do
172− putStrLn $ "sending a chunk of size " ++ show (BS.length bs)
173− BS.hPut h bs
174− return (Right h)
175− putHeader (x, y) = do
176− BS.hPut h x
177− BS.hPut h $ SL.pack ": "
178− BS.hPut h y
179− BS.hPut h $ SL.pack "\r\n"
180−
181−parseHeaderNoAttr :: StringLike a => a -> (a, a)
182−parseHeaderNoAttr s =
183− let (k, rest) = SL.span (/= ':') s
184− in (k, SL.dropPrefix' (SL.pack ": ") rest)
Network/Wai/Source.hsdeleted−46
@@ -1,46 +0,0 @@
1−module Network.Wai.Source
2− (
3− -- * Conversions
4− toEnumerator
5− , toLBS
6− , fromLBS
7− ) where
8−
9−import Network.Wai
10−import qualified Data.ByteString.Lazy as L
11−import System.IO.Unsafe (unsafeInterleaveIO)
12−
13−-- | This function safely converts a 'Source' (where you pull data) to an
14−-- 'Enumerator' (which pushes the data to you). There should be no significant
15−-- performance impact from its use, and it uses no unsafe functions.
16−toEnumerator :: Source -> Enumerator
17−toEnumerator source0 = Enumerator $ helper source0 where
18− helper source iter a = do
19− next <- runSource source
20− case next of
21− Nothing -> return $ Right a
22− Just (bs, source') -> do
23− res <- iter a bs
24− case res of
25− Left a' -> return $ Left a'
26− Right a' -> helper source' iter a'
27−
28−-- | Uses lazy I\/O (via 'unsafeInterleaveIO') to provide a lazy interface to
29−-- the given 'Source'. Normal lazy I\/O warnings apply.
30−toLBS :: Source -> IO L.ByteString
31−toLBS source0 = L.fromChunks `fmap` helper source0 where
32− helper source = unsafeInterleaveIO $ do
33− next <- runSource source
34− case next of
35− Nothing -> return []
36− Just (bs, source') -> do
37− rest <- helper source'
38− return $ bs : rest
39−
40−-- | Convert a lazy bytestring to a 'Source'. This operation does not request lazy I\/O.
41−fromLBS :: L.ByteString -> Source
42−fromLBS =
43− go . L.toChunks
44− where
45− go [] = Source $ return Nothing
46− go (x:xs) = Source $ return $ Just (x, go xs)
wai.cabal+6−6
@@ -1,5 +1,5 @@
11Name: wai
2−Version: 0.2.2.1
2+Version: 0.3.0
33Synopsis: Web Application Interface.
44Description: Provides a common protocol for communication between web aplications and web servers.
55License: BSD3
@@ -17,10 +17,10 @@ Source-repository head
1717 location: git://github.com/snoyberg/wai.git
1818
1919Library
20− Build-Depends: base >= 3 && < 5,
21− bytestring >= 0.9 && < 0.10,
22− directory >= 1.0 && < 1.2
20+ Build-Depends: base >= 3 && < 5
21+ , bytestring >= 0.9 && < 0.10
22+ , directory >= 1.0 && < 1.2
23+ , blaze-builder >= 0.2 && < 0.3
24+ , enumerator >= 0.4 && < 0.5
2325 Exposed-modules: Network.Wai
24− Network.Wai.Enumerator
25− Network.Wai.Source
2626 ghc-options: -Wall