on master help sign in
Added documentation
commit1205634255988bcabebc8b08e60bee3c25e1ab7d authorMichael Snoyman <michael@snoyman.com> dateMon 1 Feb 2010, 01:24 (17 years ago) parentdc830bd
2 files changed+147−16
Network/Wai.hs+133−10 Network/Wai/Enumerator.hs+14−6
Network/Wai.hs+133−10
@@ -1,6 +1,38 @@
11{-# LANGUAGE Rank2Types #-}
2+{-|
3+
4+This module defines a generic web application interface. It is a common
5+protocol between web servers and web applications.
6+
7+The overriding design principles here are performance, generality and type
8+safety. To address performance, this library is built on 'Enumerator's for the
9+request and response bodies. The advantages of this approach over lazy IO has
10+been debated elsewhere.
11+
12+Nonetheless, many people find 'Enumerator's difficult to work with. For that
13+reason, this library includes the "Network.Wai.Enumerator" module to provide
14+more familiar abstractions, including lazy IO.
15+
16+Generality is achieved by removing many variables commonly found in similar
17+projects that are not universal to all servers. The goal is that the 'Request'
18+object contains only data which is meaningful in all circumstances.
19+
20+Unlike other approaches, this package declares many data types to assist in
21+type safety. This feels more in the general Haskell spirit.
22+
23+A final note: please remember when using this package that, while your
24+application my compile without a hitch against many different servers, there
25+are other considerations to be taken when moving to a new backend. For example,
26+if you transfer from a CGI application to a FastCGI one, you might suddenly
27+find you have a memory leak. Conversely, a FastCGI application would be
28+well served to preload all templates from disk when first starting; this
29+would kill the performance of a CGI application.
30+
31+-}
232module Network.Wai
333 ( -- * Data types
34+
35+ -- $show_read
436 -- ** Request method
537 Method (..)
638 , methodFromBS
@@ -35,9 +67,16 @@ module Network.Wai
3567import qualified Data.ByteString as B
3668import qualified Data.ByteString.Char8 as B8
3769
38−-- | Please do not use the Show and Read instances for anything other than
39−-- debugging purposes. Instead, the 'methodFromBS' and 'methodToBS' provide a
40−-- more appropriate interface.
70+-- $show_read
71+--
72+-- For the data types below, you should only use the 'Show' and 'Read'
73+-- instances for debugging purposes. Each datatype (excepting 'UrlScheme') has
74+-- associated functions for converting to and from strict 'B.ByteString's;
75+-- these are approrpiate for generating content.
76+
77+-- | HTTP request method. This data type is extensible via the Method
78+-- constructor. Request methods are case-sensitive, and comparison is achieved
79+-- by converting to a 'B.ByteString' via 'methodToBS'.
4180data Method =
4281 OPTIONS
4382 | GET
@@ -48,7 +87,10 @@ data Method =
4887 | TRACE
4988 | CONNECT
5089 | Method B.ByteString
51− deriving (Show, Read, Eq)
90+ deriving (Show, Read)
91+
92+instance Eq Method where
93+ x == y = methodToBS x == methodToBS y
5294
5395methodFromBS :: B.ByteString -> Method
5496methodFromBS bs
@@ -75,16 +117,25 @@ methodToBS TRACE = B8.pack "TRACE"
75117methodToBS CONNECT = B8.pack "CONNECT"
76118methodToBS (Method bs) = bs
77119
78−data UrlScheme = HTTP | HTTPS deriving (Show, Eq)
120+data UrlScheme = HTTP | HTTPS
121+ deriving (Show, Eq)
79122
80−-- | The value of HttpVersion should be the information trailing HTTP/.
123+-- | Version of HTTP protocol used in current request. This data type is
124+-- extensible via the HttpVersion constructor. Comparison is achieved by
125+-- converting to a 'B.ByteString' via 'httpVersionToBS'.
81126data HttpVersion =
82127 Http09
83128 | Http10
84129 | Http11
85130 | HttpVersion B.ByteString
86− deriving (Show, Eq)
131+ deriving (Show, Read)
132+
133+instance Eq HttpVersion where
134+ x == y = httpVersionToBS x == httpVersionToBS y
87135
136+-- | This function takes the information after \"HTTP\/\". For example:
137+--
138+-- @ 'httpVersionFromBS' ('B8.pack' \"1.0\") == 'Http10' @
88139httpVersionFromBS :: B.ByteString -> HttpVersion
89140httpVersionFromBS bs
90141 | B.length bs == 3 = case B8.unpack bs of
@@ -94,12 +145,25 @@ httpVersionFromBS bs
94145 _ -> HttpVersion bs
95146 | otherwise = HttpVersion bs
96147
148+-- | Returns the version number, for example:
149+--
150+-- @ 'B8.unpack' ('httpVersionToBS' 'Http10') == \"1.0\" @
97151httpVersionToBS :: HttpVersion -> B.ByteString
98152httpVersionToBS Http09 = B8.pack "0.9"
99153httpVersionToBS Http10 = B8.pack "1.0"
100154httpVersionToBS Http11 = B8.pack "1.1"
101155httpVersionToBS (HttpVersion bs) = bs
102156
157+-- | Headers sent from the client to the server. Clearly, this is not a
158+-- complete list of all possible headers, but rather a selection of common
159+-- ones. If other headers are required, they can be created with the
160+-- RequestHeader constructor.
161+--
162+-- The naming rules are simple: removing any hyphens from the actual name, and
163+-- if there is a naming conflict with a 'ResponseHeader', prefix with Req.
164+--
165+-- Equality determined by conversion via 'requestHeaderToBS'. Remember, headers
166+-- are case sensitive.
103167data RequestHeader =
104168 Accept
105169 | AcceptCharset
@@ -112,7 +176,7 @@ data RequestHeader =
112176 | Host
113177 | Referer
114178 | RequestHeader B.ByteString
115− deriving (Show)
179+ deriving (Show, Read)
116180
117181instance Eq RequestHeader where
118182 x == y = requestHeaderToBS x == requestHeaderToBS y
@@ -144,6 +208,16 @@ requestHeaderToBS Host = B8.pack "Host"
144208requestHeaderToBS Referer = B8.pack "Referer"
145209requestHeaderToBS (RequestHeader bs) = bs
146210
211+
212+-- | Headers sent from the server to the client. Clearly, this is not a
213+-- complete list of all possible headers, but rather a selection of common
214+-- ones. If other headers are required, they can be created with the
215+-- ResponseHeader constructor.
216+--
217+-- if there is a naming conflict with a 'ResponseHeader', prefix with Req.
218+--
219+-- Equality determined by conversion via 'responseHeaderToBS'. Remember,
220+-- headers are case sensitive.
147221data ResponseHeader =
148222 ContentEncoding
149223 | ContentLanguage
@@ -160,7 +234,6 @@ data ResponseHeader =
160234instance Eq ResponseHeader where
161235 x == y = responseHeaderToBS x == responseHeaderToBS y
162236
163−
164237responseHeaderFromBS :: B.ByteString -> ResponseHeader
165238responseHeaderFromBS bs = case B8.unpack bs of
166239 "Content-Encoding" -> ContentEncoding
@@ -187,7 +260,7 @@ responseHeaderToBS SetCookie = B8.pack "Set-Cookie"
187260responseHeaderToBS (ResponseHeader bs) = bs
188261
189262-- | This attempts to provide the most common HTTP status codes, not all of
190−-- them. Use the 'Status' constructor when you want to create a status code not
263+-- them. Use the Status constructor when you want to create a status code not
191264-- provided.
192265--
193266-- The 'Eq' instance tests equality based only on the numeric status code
@@ -235,13 +308,47 @@ statusMessage Status405 = B8.pack "Method Not Allowed"
235308statusMessage Status500 = B8.pack "Internal Server Error"
236309statusMessage (Status _ m) = m
237310
311+-- | An enumerator is a data producer. It takes two arguments: a function to
312+-- enumerate over (the iteratee) and an accumulating parameter. As the
313+-- enumerator produces output, it calls the iteratee, thereby avoiding the need
314+-- to allocate large amounts of memory for storing the entire piece of data.
315+--
316+-- Normally in Haskell, we can achieve the same results with laziness. For
317+-- example, an inifinite list does not require inifinite memory storage; we
318+-- simply get away with thunks. However, when operating in the IO monad, we do
319+-- not have this luxury. There are other approaches, such as lazy I\/O. If you
320+-- would like to program in this manner, please see
321+-- "Network.Wai.Enumerator", in particular toLBS.
322+--
323+-- That said, let's address the details of this particular enumerator
324+-- implementation. You'll notice that the iteratee is a function that takes two
325+-- arguments and returns an 'Either' value. The second argument is simply the
326+-- piece of data generated by the enumerator. The 'Either' value at the end is
327+-- a means to alert the enumerator whether to continue or not. If it returns
328+-- 'Left', then the enumeration should cease. If it returns 'Right', it should
329+-- continue.
330+--
331+-- The accumulating parameter (a) has meaning only to the iteratee; the
332+-- enumerator simply passes it around. The enumerator itself also returns an
333+-- 'Either' value; a 'Right' means the enumerator ran to completion, while a
334+-- 'Left' indicates early termination was requested by the iteratee.
335+--
336+-- 'Enumerator's are used twice in the WAI: once in the 'Request' datatype to
337+-- access the request body, and once in the 'Response' datatype for the
338+-- application to return the body of the response.
238339type Enumerator a = (a -> B.ByteString -> IO (Either a a))
239340 -> a
240341 -> IO (Either a a)
241342
343+-- | Information on the request sent by the client. This abstracts away the
344+-- details of the underlying implementation.
242345data Request = Request
243346 { requestMethod :: Method
244347 , httpVersion :: HttpVersion
348+ -- | Extra path information sent by the client. The meaning varies slightly
349+ -- depending on backend; in a standalone server setting, this is most likely
350+ -- all information after the domain name. In a CGI application, this would be
351+ -- the information following the path to the CGI executable itself.
245352 , pathInfo :: B.ByteString
246353 , queryString :: B.ByteString
247354 , serverName :: B.ByteString
@@ -250,15 +357,31 @@ data Request = Request
250357 , urlScheme :: UrlScheme
251358 , requestBody :: forall a. Enumerator a
252359 , errorHandler :: String -> IO ()
360+ -- | The client\'s host information.
253361 , remoteHost :: B.ByteString
254362 }
255363
256364data Response = Response
257365 { status :: Status
258366 , headers :: [(ResponseHeader, B.ByteString)]
367+ -- | A common optimization is to use the sendfile system call when sending
368+ -- files from the disk. This datatype facilitates this optimization; if
369+ -- 'Left' is returns, the server will send the file from the disk by whatever
370+ -- means it wishes. If 'Right', it will call the 'Enumerator'.
259371 , body :: forall a. Either FilePath (Enumerator a)
260372 }
261373
262374type Application = Request -> IO Response
263375
376+-- | 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.
377+--
378+-- As an example of an alternate type for middleware, suppose you write a
379+-- function to load up session information. The session information is simply a
380+-- string map \[(String, String)\]. A logical type signatures for this middleware
381+-- might be:
382+--
383+-- @ loadSession :: ([(String, String)] -> Application) -> Application @
384+--
385+-- Here, instead of taking a standard 'Application' as its first argument, the
386+-- middleware takes a function which consumes the session information as well.
264387type Middleware = Application -> Application
Network/Wai/Enumerator.hs+14−6
@@ -22,6 +22,7 @@ import qualified Data.ByteString.Lazy as L
2222import qualified Data.ByteString as B
2323import System.IO.Unsafe (unsafeInterleaveIO)
2424import System.IO (withBinaryFile, IOMode (ReadMode), Handle, hIsEOF)
25+import Data.ByteString.Lazy.Internal (defaultChunkSize)
2526
2627-- | Performs a specified conversion on each 'B.ByteString' output by an
2728-- enumerator.
@@ -41,6 +42,7 @@ toLBS e = L.fromChunks `fmap` helper where
4142 xs <- helper
4243 return $ x' : xs
4344
45+-- | This function safely converts a lazy bytestring into an enumerator.
4446fromLBS :: L.ByteString -> (forall a. Enumerator a)
4547fromLBS lbs iter a0 = helper a0 $ L.toChunks lbs where
4648 helper a [] = return $ Right a
@@ -50,6 +52,9 @@ fromLBS lbs iter a0 = helper a0 $ L.toChunks lbs where
5052 Left a' -> return $ Left a'
5153 Right a' -> helper a' xs
5254
55+-- | Same as 'fromLBS', but the lazy bytestring is in the IO monad. This allows
56+-- you to lazily read a file into memory, perform some mapping on the data and
57+-- convert it into an enumerator.
5358fromLBS' :: IO L.ByteString -> (forall a. Enumerator a)
5459fromLBS' lbs' iter a0 = lbs' >>= \lbs -> fromLBS lbs iter a0
5560
@@ -59,24 +64,27 @@ fromLBS' lbs' iter a0 = lbs' >>= \lbs -> fromLBS lbs iter a0
5964toSource :: Enumerator (Maybe B.ByteString) -> IO (Maybe B.ByteString)
6065toSource e = fmap (either id id) $ e (const $ return . Left . Just) Nothing
6166
62−chunkSize :: Int
63−chunkSize = 1024 -- FIXME
64−
67+-- | Read a chunk of data from the given 'Handle' at a time. We use
68+-- 'defaultChunkSize' from the bytestring package to determine the largest
69+-- chunk to take.
6570fromHandle :: Handle -> Enumerator a
6671fromHandle h iter a = do
6772 eof <- hIsEOF h
6873 if eof
6974 then return $ Right a
7075 else do
71− bs <- B.hGet h chunkSize
76+ bs <- B.hGet h defaultChunkSize
7277 ea' <- iter a bs
7378 case ea' of
7479 Left a' -> return $ Left a'
7580 Right a' -> fromHandle h iter a'
7681
82+-- | A little wrapper around 'fromHandle' which first opens a file for reading.
7783fromFile :: FilePath -> Enumerator a
7884fromFile fp iter a0 = withBinaryFile fp ReadMode $ \h -> fromHandle h iter a0
7985
86+-- | Since the response body is defined as an 'Either' 'FilePath' 'Enumerator',
87+-- this function simply reduces the whole operator to an enumerator. This can
88+-- be convenient for server implementations not optimizing file sending.
8089fromEitherFile :: Either FilePath (Enumerator a) -> Enumerator a
81−fromEitherFile (Left fp) = fromFile fp
82−fromEitherFile (Right e) = e
90+fromEitherFile = either fromFile id