on master help sign in
Added documentation
1205634Michael Snoyman
Network/Wai.hs+133−10
@@ -1,6 +1,38 @@
1{-# LANGUAGE Rank2Types #-}
1{-# 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+-}
2module Network.Wai
32module Network.Wai
3 ( -- * Data types
33 ( -- * Data types
34+
35+ -- $show_read
4 -- ** Request method
36 -- ** Request method
5 Method (..)
37 Method (..)
6 , methodFromBS
38 , methodFromBS
@@ -35,9 +67,16 @@ module Network.Wai
35import qualified Data.ByteString as B
67import qualified Data.ByteString as B
36import qualified Data.ByteString.Char8 as B8
68import qualified Data.ByteString.Char8 as B8
37
69
38−-- | Please do not use the Show and Read instances for anything other than
70+-- $show_read
39−-- debugging purposes. Instead, the 'methodFromBS' and 'methodToBS' provide a
71+--
40−-- more appropriate interface.
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'.
41data Method =
80data Method =
42 OPTIONS
81 OPTIONS
43 | GET
82 | GET
@@ -48,7 +87,10 @@ data Method =
48 | TRACE
87 | TRACE
49 | CONNECT
88 | CONNECT
50 | Method B.ByteString
89 | 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
52
94
53methodFromBS :: B.ByteString -> Method
95methodFromBS :: B.ByteString -> Method
54methodFromBS bs
96methodFromBS bs
@@ -75,16 +117,25 @@ methodToBS TRACE = B8.pack "TRACE"
75methodToBS CONNECT = B8.pack "CONNECT"
117methodToBS CONNECT = B8.pack "CONNECT"
76methodToBS (Method bs) = bs
118methodToBS (Method bs) = bs
77
119
78−data UrlScheme = HTTP | HTTPS deriving (Show, Eq)
120+data UrlScheme = HTTP | HTTPS
121+ deriving (Show, Eq)
79
122
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'.
81data HttpVersion =
126data HttpVersion =
82 Http09
127 Http09
83 | Http10
128 | Http10
84 | Http11
129 | Http11
85 | HttpVersion B.ByteString
130 | HttpVersion B.ByteString
86− deriving (Show, Eq)
131+ deriving (Show, Read)
132+
133+instance Eq HttpVersion where
134+ x == y = httpVersionToBS x == httpVersionToBS y
87
135
136+-- | This function takes the information after \"HTTP\/\". For example:
137+--
138+-- @ 'httpVersionFromBS' ('B8.pack' \"1.0\") == 'Http10' @
88httpVersionFromBS :: B.ByteString -> HttpVersion
139httpVersionFromBS :: B.ByteString -> HttpVersion
89httpVersionFromBS bs
140httpVersionFromBS bs
90 | B.length bs == 3 = case B8.unpack bs of
141 | B.length bs == 3 = case B8.unpack bs of
@@ -94,12 +145,25 @@ httpVersionFromBS bs
94 _ -> HttpVersion bs
145 _ -> HttpVersion bs
95 | otherwise = HttpVersion bs
146 | otherwise = HttpVersion bs
96
147
148+-- | Returns the version number, for example:
149+--
150+-- @ 'B8.unpack' ('httpVersionToBS' 'Http10') == \"1.0\" @
97httpVersionToBS :: HttpVersion -> B.ByteString
151httpVersionToBS :: HttpVersion -> B.ByteString
98httpVersionToBS Http09 = B8.pack "0.9"
152httpVersionToBS Http09 = B8.pack "0.9"
99httpVersionToBS Http10 = B8.pack "1.0"
153httpVersionToBS Http10 = B8.pack "1.0"
100httpVersionToBS Http11 = B8.pack "1.1"
154httpVersionToBS Http11 = B8.pack "1.1"
101httpVersionToBS (HttpVersion bs) = bs
155httpVersionToBS (HttpVersion bs) = bs
102
156
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.
103data RequestHeader =
167data RequestHeader =
104 Accept
168 Accept
105 | AcceptCharset
169 | AcceptCharset
@@ -112,7 +176,7 @@ data RequestHeader =
112 | Host
176 | Host
113 | Referer
177 | Referer
114 | RequestHeader B.ByteString
178 | RequestHeader B.ByteString
115− deriving (Show)
179+ deriving (Show, Read)
116
180
117instance Eq RequestHeader where
181instance Eq RequestHeader where
118 x == y = requestHeaderToBS x == requestHeaderToBS y
182 x == y = requestHeaderToBS x == requestHeaderToBS y
@@ -144,6 +208,16 @@ requestHeaderToBS Host = B8.pack "Host"
144requestHeaderToBS Referer = B8.pack "Referer"
208requestHeaderToBS Referer = B8.pack "Referer"
145requestHeaderToBS (RequestHeader bs) = bs
209requestHeaderToBS (RequestHeader bs) = bs
146
210
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.
147data ResponseHeader =
221data ResponseHeader =
148 ContentEncoding
222 ContentEncoding
149 | ContentLanguage
223 | ContentLanguage
@@ -160,7 +234,6 @@ data ResponseHeader =
160instance Eq ResponseHeader where
234instance Eq ResponseHeader where
161 x == y = responseHeaderToBS x == responseHeaderToBS y
235 x == y = responseHeaderToBS x == responseHeaderToBS y
162
236
163−
164responseHeaderFromBS :: B.ByteString -> ResponseHeader
237responseHeaderFromBS :: B.ByteString -> ResponseHeader
165responseHeaderFromBS bs = case B8.unpack bs of
238responseHeaderFromBS bs = case B8.unpack bs of
166 "Content-Encoding" -> ContentEncoding
239 "Content-Encoding" -> ContentEncoding
@@ -187,7 +260,7 @@ responseHeaderToBS SetCookie = B8.pack "Set-Cookie"
187responseHeaderToBS (ResponseHeader bs) = bs
260responseHeaderToBS (ResponseHeader bs) = bs
188
261
189-- | This attempts to provide the most common HTTP status codes, not all of
262-- | 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
191-- provided.
264-- provided.
192--
265--
193-- The 'Eq' instance tests equality based only on the numeric status code
266-- The 'Eq' instance tests equality based only on the numeric status code
@@ -235,13 +308,47 @@ statusMessage Status405 = B8.pack "Method Not Allowed"
235statusMessage Status500 = B8.pack "Internal Server Error"
308statusMessage Status500 = B8.pack "Internal Server Error"
236statusMessage (Status _ m) = m
309statusMessage (Status _ m) = m
237
310
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.
238type Enumerator a = (a -> B.ByteString -> IO (Either a a))
339type Enumerator a = (a -> B.ByteString -> IO (Either a a))
239 -> a
340 -> a
240 -> IO (Either a a)
341 -> IO (Either a a)
241
342
343+-- | Information on the request sent by the client. This abstracts away the
344+-- details of the underlying implementation.
242data Request = Request
345data Request = Request
243 { requestMethod :: Method
346 { requestMethod :: Method
244 , httpVersion :: HttpVersion
347 , 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.
245 , pathInfo :: B.ByteString
352 , pathInfo :: B.ByteString
246 , queryString :: B.ByteString
353 , queryString :: B.ByteString
247 , serverName :: B.ByteString
354 , serverName :: B.ByteString
@@ -250,15 +357,31 @@ data Request = Request
250 , urlScheme :: UrlScheme
357 , urlScheme :: UrlScheme
251 , requestBody :: forall a. Enumerator a
358 , requestBody :: forall a. Enumerator a
252 , errorHandler :: String -> IO ()
359 , errorHandler :: String -> IO ()
360+ -- | The client\'s host information.
253 , remoteHost :: B.ByteString
361 , remoteHost :: B.ByteString
254 }
362 }
255
363
256data Response = Response
364data Response = Response
257 { status :: Status
365 { status :: Status
258 , headers :: [(ResponseHeader, B.ByteString)]
366 , 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'.
259 , body :: forall a. Either FilePath (Enumerator a)
371 , body :: forall a. Either FilePath (Enumerator a)
260 }
372 }
261
373
262type Application = Request -> IO Response
374type Application = Request -> IO Response
263
375
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.
264type Middleware = Application -> Application
387type Middleware = Application -> Application
Network/Wai/Enumerator.hs+14−6
@@ -22,6 +22,7 @@ import qualified Data.ByteString.Lazy as L
22import qualified Data.ByteString as B
22import qualified Data.ByteString as B
23import System.IO.Unsafe (unsafeInterleaveIO)
23import System.IO.Unsafe (unsafeInterleaveIO)
24import System.IO (withBinaryFile, IOMode (ReadMode), Handle, hIsEOF)
24import System.IO (withBinaryFile, IOMode (ReadMode), Handle, hIsEOF)
25+import Data.ByteString.Lazy.Internal (defaultChunkSize)
25
26
26-- | Performs a specified conversion on each 'B.ByteString' output by an
27-- | Performs a specified conversion on each 'B.ByteString' output by an
27-- enumerator.
28-- enumerator.
@@ -41,6 +42,7 @@ toLBS e = L.fromChunks `fmap` helper where
41 xs <- helper
42 xs <- helper
42 return $ x' : xs
43 return $ x' : xs
43
44
45+-- | This function safely converts a lazy bytestring into an enumerator.
44fromLBS :: L.ByteString -> (forall a. Enumerator a)
46fromLBS :: L.ByteString -> (forall a. Enumerator a)
45fromLBS lbs iter a0 = helper a0 $ L.toChunks lbs where
47fromLBS lbs iter a0 = helper a0 $ L.toChunks lbs where
46 helper a [] = return $ Right a
48 helper a [] = return $ Right a
@@ -50,6 +52,9 @@ fromLBS lbs iter a0 = helper a0 $ L.toChunks lbs where
50 Left a' -> return $ Left a'
52 Left a' -> return $ Left a'
51 Right a' -> helper a' xs
53 Right a' -> helper a' xs
52
54
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.
53fromLBS' :: IO L.ByteString -> (forall a. Enumerator a)
58fromLBS' :: IO L.ByteString -> (forall a. Enumerator a)
54fromLBS' lbs' iter a0 = lbs' >>= \lbs -> fromLBS lbs iter a0
59fromLBS' lbs' iter a0 = lbs' >>= \lbs -> fromLBS lbs iter a0
55
60
@@ -59,24 +64,27 @@ fromLBS' lbs' iter a0 = lbs' >>= \lbs -> fromLBS lbs iter a0
59toSource :: Enumerator (Maybe B.ByteString) -> IO (Maybe B.ByteString)
64toSource :: Enumerator (Maybe B.ByteString) -> IO (Maybe B.ByteString)
60toSource e = fmap (either id id) $ e (const $ return . Left . Just) Nothing
65toSource e = fmap (either id id) $ e (const $ return . Left . Just) Nothing
61
66
62−chunkSize :: Int
67+-- | Read a chunk of data from the given 'Handle' at a time. We use
63−chunkSize = 1024 -- FIXME
68+-- 'defaultChunkSize' from the bytestring package to determine the largest
64−
69+-- chunk to take.
65fromHandle :: Handle -> Enumerator a
70fromHandle :: Handle -> Enumerator a
66fromHandle h iter a = do
71fromHandle h iter a = do
67 eof <- hIsEOF h
72 eof <- hIsEOF h
68 if eof
73 if eof
69 then return $ Right a
74 then return $ Right a
70 else do
75 else do
71− bs <- B.hGet h chunkSize
76+ bs <- B.hGet h defaultChunkSize
72 ea' <- iter a bs
77 ea' <- iter a bs
73 case ea' of
78 case ea' of
74 Left a' -> return $ Left a'
79 Left a' -> return $ Left a'
75 Right a' -> fromHandle h iter a'
80 Right a' -> fromHandle h iter a'
76
81
82+-- | A little wrapper around 'fromHandle' which first opens a file for reading.
77fromFile :: FilePath -> Enumerator a
83fromFile :: FilePath -> Enumerator a
78fromFile fp iter a0 = withBinaryFile fp ReadMode $ \h -> fromHandle h iter a0
84fromFile fp iter a0 = withBinaryFile fp ReadMode $ \h -> fromHandle h iter a0
79
85
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.
80fromEitherFile :: Either FilePath (Enumerator a) -> Enumerator a
89fromEitherFile :: Either FilePath (Enumerator a) -> Enumerator a
81−fromEitherFile (Left fp) = fromFile fp
90+fromEitherFile = either fromFile id
82−fromEitherFile (Right e) = e