@@ -2,26 +2,26 @@
2{-# LANGUAGE ScopedTypeVariables #-}
2{-# LANGUAGE ScopedTypeVariables #-}
3{-# LANGUAGE BangPatterns #-}
3{-# LANGUAGE BangPatterns #-}
5−module Network.Wai.Handler.Warp.Response where
5+module Network.Wai.Handler.Warp.Response (
7−import Blaze.ByteString.Builder (copyByteString, Builder, toLazyByteString, toByteStringIO, flush)
9+import Blaze.ByteString.Builder (fromByteString, Builder, toByteStringIO, flush)
8−import Blaze.ByteString.Builder.Char8 (fromChar, fromShow)
9import Blaze.ByteString.Builder.HTTP (chunkedTransferEncoding, chunkedTransferTerminator)
10import Blaze.ByteString.Builder.HTTP (chunkedTransferEncoding, chunkedTransferTerminator)
10import Control.Applicative
11import Control.Applicative
11import Control.Exception
12import Control.Exception
12import Control.Monad.IO.Class (MonadIO, liftIO)
13import Control.Monad.IO.Class (MonadIO, liftIO)
13import qualified Data.ByteString.Char8 as B
14import qualified Data.ByteString.Char8 as B
14−import qualified Data.ByteString.Lazy as L
15import qualified Data.CaseInsensitive as CI
15import qualified Data.CaseInsensitive as CI
16import Data.Conduit
16import Data.Conduit
17import Data.Conduit.Blaze (builderToByteString)
17import Data.Conduit.Blaze (builderToByteString)
18import qualified Data.Conduit.List as CL
18import qualified Data.Conduit.List as CL
19−import Data.List (foldl')
20import Data.Maybe (isJust)
19import Data.Maybe (isJust)
21import Data.Monoid (mappend)
20import Data.Monoid (mappend)
22import qualified Network.HTTP.Types as H
21import qualified Network.HTTP.Types as H
23import Network.Wai
22import Network.Wai
24import Network.Wai.Handler.Warp.ReadInt
23import Network.Wai.Handler.Warp.ReadInt
24+import qualified Network.Wai.Handler.Warp.ResponseHeader as RH
25import qualified Network.Wai.Handler.Warp.Timeout as T
25import qualified Network.Wai.Handler.Warp.Timeout as T
26import Network.Wai.Handler.Warp.Types
26import Network.Wai.Handler.Warp.Types
27import qualified System.PosixCompat.Files as P
27import qualified System.PosixCompat.Files as P
@@ -41,27 +41,27 @@ sendResponse th req conn (ResponseFile s hs fp mpart) =
41 (Just cl, _) -> return $ Right (hs, cl)
41 (Just cl, _) -> return $ Right (hs, cl)
42 (Nothing, Nothing) -> liftIO . try $ do
42 (Nothing, Nothing) -> liftIO . try $ do
43 cl <- fromIntegral . P.fileSize <$> P.getFileStatus fp
43 cl <- fromIntegral . P.fileSize <$> P.getFileStatus fp
44− return $ (addLength cl hs, cl)
44+ return (addLength cl hs, cl)
45 (Nothing, Just part) -> do
45 (Nothing, Just part) -> do
46 let cl = fromIntegral $ filePartByteCount part
46 let cl = fromIntegral $ filePartByteCount part
47− return $ Right $ (addLength cl hs, cl)
47+ return $ Right (addLength cl hs, cl)
49 sendResponse' (Right (lengthyHeaders, cl))
49 sendResponse' (Right (lengthyHeaders, cl))
50 | hasBody s req = liftIO $ do
50 | hasBody s req = liftIO $ do
51− let (beg,end) = case mpart of
53− Just prt -> (filePartOffset prt, filePartByteCount prt)
54− connSendFile conn fp beg end (T.tickle th) headers'
51+ connSendFile conn fp beg end (T.tickle th) [lheader]
55 T.tickle th
52 T.tickle th
56 return isPersist
53 return isPersist
57 | otherwise = liftIO $ do
54 | otherwise = liftIO $ do
58− sendHeader conn $ headers version s hs False -- FIXME
55+ connSendAll conn $ composeHeader version s hs
59 T.tickle th
56 T.tickle th
60 return isPersist -- FIXME isKeepAlive?
57 return isPersist -- FIXME isKeepAlive?
59+ (beg,end) = case mpart of
61+ Just prt -> (filePartOffset prt, filePartByteCount prt)
62+ lheader = composeHeader version s lengthyHeaders
62 version = httpVersion req
63 version = httpVersion req
63 (isPersist,_) = infoFromRequest req
64 (isPersist,_) = infoFromRequest req
64− headers' = L.toChunks . toLazyByteString $ headers version s lengthyHeaders False
66 sendResponse' (Left (_ :: SomeException)) =
66 sendResponse' (Left (_ :: SomeException)) =
67 sendResponse th req conn notFound
67 sendResponse th req conn notFound
@@ -77,17 +77,15 @@ sendResponse th req conn (ResponseBuilder s hs b)
77 T.tickle th
77 T.tickle th
78 return isKeepAlive
78 return isKeepAlive
79 | otherwise = liftIO $ do
79 | otherwise = liftIO $ do
80− sendHeader conn $ headers' False
80+ connSendAll conn $ composeHeader version s hs
81 T.tickle th
81 T.tickle th
82 return isPersist
82 return isPersist
84− headers' = headers version s hs
84+ header = composeHeaderBuilder version s hs needsChunked
85− body = if needsChunked then
85+ body
86− headers' needsChunked
87− `mappend` chunkedTransferEncoding b
86+ | needsChunked = header `mappend` chunkedTransferEncoding b
88− `mappend` chunkedTransferTerminator
87+ `mappend` chunkedTransferTerminator
89− else
88+ | otherwise = header `mappend` b
90− headers' False `mappend` b
91 version = httpVersion req
89 version = httpVersion req
92 reqinfo@(isPersist,_) = infoFromRequest req
90 reqinfo@(isPersist,_) = infoFromRequest req
93 (isKeepAlive, needsChunked) = infoFromResponse hs reqinfo
91 (isKeepAlive, needsChunked) = infoFromResponse hs reqinfo
@@ -96,24 +94,23 @@ sendResponse th req conn (ResponseBuilder s hs b)
97sendResponse th req conn (ResponseSource s hs bodyFlush)
95sendResponse th req conn (ResponseSource s hs bodyFlush)
98 | hasBody s req = do
96 | hasBody s req = do
99− let src = CL.sourceList [headers' needsChunked] `mappend`
97+ let src = CL.sourceList [header] `mappend` cbody
100− (if needsChunked then body $= chunk else body)
101 src $$ builderToByteString =$ connSink conn th
98 src $$ builderToByteString =$ connSink conn th
102− return $ isKeepAlive
99+ return isKeepAlive
103 | otherwise = liftIO $ do
100 | otherwise = liftIO $ do
104− sendHeader conn $ headers' False
101+ connSendAll conn $ composeHeader version s hs
105 T.tickle th
102 T.tickle th
106 return isPersist
103 return isPersist
105+ header = composeHeaderBuilder version s hs needsChunked
108 body = mapOutput (\x -> case x of
106 body = mapOutput (\x -> case x of
109 Flush -> flush
107 Flush -> flush
110 Chunk builder -> builder) bodyFlush
108 Chunk builder -> builder) bodyFlush
111− headers' = headers version s hs
109+ cbody = if needsChunked then body $= chunk else body
112 -- FIXME perhaps alloca a buffer per thread and reuse that in all
110 -- FIXME perhaps alloca a buffer per thread and reuse that in all
113 -- functions below. Should lessen greatly the GC burden (I hope)
111 -- functions below. Should lessen greatly the GC burden (I hope)
114 chunk :: Conduit Builder (ResourceT IO) Builder
112 chunk :: Conduit Builder (ResourceT IO) Builder
115 chunk = await >>= maybe (yield chunkedTransferTerminator) (\x -> yield (chunkedTransferEncoding x) >> chunk)
113 chunk = await >>= maybe (yield chunkedTransferTerminator) (\x -> yield (chunkedTransferEncoding x) >> chunk)
117 version = httpVersion req
114 version = httpVersion req
118 reqinfo@(isPersist,_) = infoFromRequest req
115 reqinfo@(isPersist,_) = infoFromRequest req
119 (isKeepAlive, needsChunked) = infoFromResponse hs reqinfo
116 (isKeepAlive, needsChunked) = infoFromResponse hs reqinfo
@@ -138,9 +135,6 @@ connSink Connection { connSendAll = send } th =
138 -- also make sure to resume the timeout after the completion of user code
135 -- also make sure to resume the timeout after the completion of user code
139 -- so that we can kill idle connections.
136 -- so that we can kill idle connections.
141−sendHeader :: Connection -> Builder -> IO ()
142−sendHeader conn = connSendMany conn . L.toChunks . toLazyByteString
144----------------------------------------------------------------
138----------------------------------------------------------------
146infoFromRequest :: Request -> (Bool,Bool)
140infoFromRequest :: Request -> (Bool,Bool)
@@ -185,39 +179,6 @@ hasBody s req = s /= H.Status 204 ""
186----------------------------------------------------------------
180----------------------------------------------------------------
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 ": "
196−headers :: H.HttpVersion -> H.Status -> H.ResponseHeaders -> Bool -> Builder
197−headers !httpversion !status !responseHeaders !isChunked = {-# SCC "headers" #-}
198− let !start = httpBuilder
199− `mappend` copyByteString
200− (case httpversion of
201− H.HttpVersion 1 1 -> "1.1"
203− `mappend` spaceBuilder
204− `mappend` fromShow (H.statusCode status)
205− `mappend` spaceBuilder
206− `mappend` copyByteString (H.statusMessage status)
207− `mappend` newlineBuilder
208− !start' = foldl' responseHeaderToBuilder start (addServerHeader responseHeaders)
209− !end = if isChunked then transferEncodingBuilder else newlineBuilder
210− in start' `mappend` end
212−responseHeaderToBuilder :: Builder -> H.Header -> Builder
213−responseHeaderToBuilder b (x, y) = b
214− `mappend` copyByteString (CI.original x)
215− `mappend` colonSpaceBuilder
216− `mappend` copyByteString y
217− `mappend` newlineBuilder
219−----------------------------------------------------------------
221addLength :: Integer -> H.ResponseHeaders -> H.ResponseHeaders
182addLength :: Integer -> H.ResponseHeaders -> H.ResponseHeaders
222addLength cl hdrs = (H.hContentLength, B.pack $ show cl) : hdrs
183addLength cl hdrs = (H.hContentLength, B.pack $ show cl) : hdrs
@@ -233,3 +194,14 @@ warpVersionHeader :: H.Header
233warpVersionHeader = (hServer, ver)
194warpVersionHeader = (hServer, ver)
235 ver = B.pack $ "Warp/" ++ warpVersion
196 ver = B.pack $ "Warp/" ++ warpVersion
198+----------------------------------------------------------------
200+composeHeader :: H.HttpVersion -> H.Status -> H.ResponseHeaders -> B.ByteString
201+composeHeader version s hs = RH.composeHeader version s (addServerHeader hs)
203+composeHeaderBuilder :: H.HttpVersion -> H.Status -> H.ResponseHeaders -> Bool -> Builder
204+composeHeaderBuilder ver s hs True =
205+ fromByteString $ composeHeader ver s (addEncodingHeader hs)
206+composeHeaderBuilder ver s hs False =
207+ fromByteString $ composeHeader ver s hs