@@ -22,7 +22,7 @@ module Network.Wai.Handler.Warp
22 , parseRequest
22 , parseRequest
24 , takeLineMax
24 , takeLineMax
25− , takeUntilBlank
25+ , takeHeaders
26 , InvalidRequest (..)
26 , InvalidRequest (..)
@@ -59,16 +59,17 @@ import Blaze.ByteString.Builder.HTTP
59import Blaze.ByteString.Builder
59import Blaze.ByteString.Builder
60 (copyByteString, Builder, toLazyByteString, toByteStringIO)
60 (copyByteString, Builder, toLazyByteString, toByteStringIO)
61import Blaze.ByteString.Builder.Char8 (fromChar, fromShow)
61import Blaze.ByteString.Builder.Char8 (fromChar, fromShow)
62−import Data.Monoid (mconcat, mappend)
62+import Data.Monoid (mappend)
63import Network.Socket.SendFile (sendFile)
63import Network.Socket.SendFile (sendFile)
64import Network.Socket.Enumerator (iterSocket)
64import Network.Socket.Enumerator (iterSocket)
66import Control.Monad.IO.Class (liftIO)
66import Control.Monad.IO.Class (liftIO)
67import System.Timeout (timeout)
67import System.Timeout (timeout)
68import Data.Word (Word8)
68import Data.Word (Word8)
69+import Data.List (foldl')
70run :: Port -> Application -> IO ()
71run :: Port -> Application -> IO ()
71−run port = withSocketsDo .
72+run port = withSocketsDo . -- FIXME should this be called by client user instead?
73 (listenOn $ PortNumber $ fromIntegral port)
74 (listenOn $ PortNumber $ fromIntegral port)
@@ -100,7 +101,7 @@ serveConnection port app conn remoteHost' = do
101parseRequest :: Port -> SockAddr -> E.Iteratee S.ByteString IO (E.Enumeratee ByteString ByteString IO a, Request)
102parseRequest :: Port -> SockAddr -> E.Iteratee S.ByteString IO (E.Enumeratee ByteString ByteString IO a, Request)
102parseRequest port remoteHost' = do
103parseRequest port remoteHost' = do
103− headers' <- takeUntilBlank 0 id
104+ headers' <- takeHeaders
104 parseRequest' port headers' remoteHost'
105 parseRequest' port headers' remoteHost'
106-- FIXME come up with good values here
107-- FIXME come up with good values here
@@ -182,27 +183,37 @@ parseFirst s = do
182 let (rpath, qstring) = B.break (== '?') query
183 let (rpath, qstring) = B.break (== '?') query
183 in return (method, rpath, qstring, hsecond)
184 in return (method, rpath, qstring, hsecond)
184 else E.throwError NonHttp
185 else E.throwError NonHttp
186+{-# INLINE parseFirst #-} -- FIXME is this inline necessary? the function is only called from one place and not exported
188+httpBuilder, spaceBuilder, newlineBuilder, transferEncodingBuilder
189+ , colonSpaceBuilder :: Builder
190+httpBuilder = copyByteString "HTTP/"
191+spaceBuilder = fromChar ' '
192+newlineBuilder = copyByteString "\r\n"
193+transferEncodingBuilder = copyByteString "Transfer-Encoding: chunked\r\n\r\n"
194+colonSpaceBuilder = copyByteString ": "
186headers :: HttpVersion -> Status -> ResponseHeaders -> Bool -> Builder
196headers :: HttpVersion -> Status -> ResponseHeaders -> Bool -> Builder
187−headers httpversion status responseHeaders isChunked' =
197+headers !httpversion !status !responseHeaders !isChunked' = {-# SCC "headers" #-}
188− copyByteString "HTTP/"
198+ let !start = httpBuilder
189− `mappend` copyByteString httpversion
199+ `mappend` copyByteString httpversion
190− `mappend` fromChar ' '
200+ `mappend` spaceBuilder
191− `mappend` fromShow (statusCode status)
201+ `mappend` fromShow (statusCode status)
192− `mappend` fromChar ' '
202+ `mappend` spaceBuilder
193− `mappend` copyByteString (statusMessage status)
203+ `mappend` copyByteString (statusMessage status)
194− `mappend` copyByteString "\r\n"
204+ `mappend` newlineBuilder
195− `mappend` mconcat (map go responseHeaders)
205+ !start' = foldl' responseHeaderToBuilder start responseHeaders
197− if isChunked'
206+ !end = if isChunked'
198− then copyByteString "Transfer-Encoding: chunked\r\n\r\n"
207+ then transferEncodingBuilder
199− else copyByteString "\r\n"
208+ else newlineBuilder
200− where
209+ in start' `mappend` end
202− copyByteString (ciOriginal x)
211+responseHeaderToBuilder :: Builder -> (CIByteString, ByteString) -> Builder
203− `mappend` copyByteString ": "
212+responseHeaderToBuilder b (x, y) = b
213+ `mappend` (copyByteString $ ciOriginal x)
214+ `mappend` colonSpaceBuilder
204− `mappend` copyByteString y
215+ `mappend` copyByteString y
205− `mappend` copyByteString "\r\n"
216+ `mappend` newlineBuilder
207isChunked :: HttpVersion -> Bool
218isChunked :: HttpVersion -> Bool
208isChunked = (==) http11
219isChunked = (==) http11
@@ -300,6 +311,9 @@ enumSocket _ _ step = E.returnI step
300------ The functions below are not warp-specific and could be split out into a
311------ The functions below are not warp-specific and could be split out into a
301--separate package.
312--separate package.
314+takeHeaders :: E.Iteratee ByteString IO [ByteString]
315+takeHeaders = takeUntilBlank 0 id
303takeUntilBlank :: Int
317takeUntilBlank :: Int
304 -> ([ByteString] -> [ByteString])
318 -> ([ByteString] -> [ByteString])
305 -> E.Iteratee S.ByteString IO [ByteString]
319 -> E.Iteratee S.ByteString IO [ByteString]