on master help sign in
responseRaw
commitc4ab46e429fe469634e2d1a791b9d28bce341878 authorMichael Snoyman <michael@snoyman.com> dateWed 5 Mar 2014, 18:46 (13 years ago) parent2c752be
27 files changed+157−97
wai-app-static/tests.hs+1−1 wai-app-static/wai-app-static.cabal+1−1 wai-eventsource/wai-eventsource.cabal+1−1 wai-extra/Network/Wai/Middleware/Autohead.hs+5−4 wai-extra/Network/Wai/Middleware/RequestLogger.hs−1 wai-extra/Network/Wai/Middleware/RequestLogger/Internal.hs+2 wai-extra/Network/Wai/Parse.hs+1−1 wai-extra/test/WaiExtraTest.hs+1−2 wai-extra/wai-extra.cabal+2−2 wai-frontend-monadcgi/wai-frontend-monadcgi.cabal+1−1 wai-handler-fastcgi/wai-handler-fastcgi.cabal+1−1 wai-handler-launch/wai-handler-launch.cabal+2−2 wai-handler-scgi/wai-handler-scgi.cabal+2−2 wai-test/wai-test.cabal+1−1 wai-websockets/Network/Wai/Handler/WebSockets.hs+48−48 wai-websockets/server.lhs+1−2 wai-websockets/wai-websockets.cabal+4−3 wai/Network/Wai.hs+17 wai/Network/Wai/Internal.hs+1 wai/wai.cabal+1−1 warp-tls/warp-tls.cabal+2−2 warp/Network/Wai/Handler/Warp/Request.hs+10−8 warp/Network/Wai/Handler/Warp/Response.hs+22−1 warp/Network/Wai/Handler/Warp/Run.hs+8−7 warp/test/RequestSpec.hs+2−2 warp/test/RunSpec.hs+18−1 warp/warp.cabal+2−2
wai-app-static/tests.hs+1−1
@@ -1,6 +1,6 @@
11import WaiAppStaticTest (spec)
22import WaiAppEmbeddedTest (embSpec)
3−import Test.Hspec.Monadic
3+import Test.Hspec
44
55main :: IO ()
66main = hspec $ spec >> embSpec
wai-app-static/wai-app-static.cabal+1−1
@@ -24,7 +24,7 @@ Flag print
2424
2525library
2626 build-depends: base >= 4 && < 5
27− , wai >= 2.0 && < 2.1
27+ , wai >= 2.0 && < 2.2
2828 , bytestring >= 0.9.1.4
2929 , http-types >= 0.7
3030 , transformers >= 0.2.2
wai-eventsource/wai-eventsource.cabal+1−1
@@ -19,7 +19,7 @@ Library
1919 , blaze-builder >= 0.3 && < 0.4
2020 , conduit >= 0.5 && < 1.1
2121 , http-types >= 0.7
22− , wai >= 2.0 && < 2.1
22+ , wai >= 2.0 && < 2.2
2323 , transformers
2424
2525 ghc-options: -Wall
wai-extra/Network/Wai/Middleware/Autohead.hs+5−4
@@ -11,9 +11,10 @@ autohead :: Middleware
1111autohead app req
1212 | requestMethod req == "HEAD" = do
1313 res <- app req { requestMethod = "GET" }
14− case res of
15− ResponseFile s hs _ _ -> return $ ResponseBuilder s hs mempty
16− ResponseBuilder s hs _ -> return $ ResponseBuilder s hs mempty
17− ResponseSource s hs _ -> return $ ResponseBuilder s hs mempty
14+ let go (ResponseFile s hs _ _) = ResponseBuilder s hs mempty
15+ go (ResponseBuilder s hs _) = ResponseBuilder s hs mempty
16+ go (ResponseSource s hs _) = ResponseBuilder s hs mempty
17+ go (ResponseRaw raw r) = ResponseRaw raw (go r)
18+ return (go res)
1819 | otherwise = app req
1920
wai-extra/Network/Wai/Middleware/RequestLogger.hs−1
@@ -28,7 +28,6 @@ import Network.Wai (Request(..), Middleware, responseStatus, Response, responseH
2828import System.Log.FastLogger
2929import Network.HTTP.Types as H
3030import Data.Maybe (fromMaybe)
31−import Control.Monad.Trans.Resource (withInternalState)
3231import Data.Monoid (mconcat)
3332
3433import Network.Wai.Parse (sinkRequestBody, lbsBackEnd, fileName, Param, File, getRequestBodyType)
wai-extra/Network/Wai/Middleware/RequestLogger/Internal.hs+2
@@ -4,7 +4,9 @@
44-- https://github.com/yesodweb/wai/issues/192
55module Network.Wai.Middleware.RequestLogger.Internal
66 ( module Network.Wai.Middleware.RequestLogger.Internal
7+#if !MIN_VERSION_fast_logger(2, 1, 0)
78 , logToByteString
9+#endif
810 ) where
911
1012import Data.ByteString (ByteString)
wai-extra/Network/Wai/Parse.hs+1−1
@@ -51,7 +51,7 @@ import qualified Network.HTTP.Types as H
5151import Data.Either (partitionEithers)
5252import Control.Monad (when, unless)
5353import Control.Monad.Trans.Class (lift)
54−import Control.Monad.Trans.Resource (allocate, release, register, InternalState, withInternalState, runInternalState)
54+import Control.Monad.Trans.Resource (allocate, release, register, InternalState, runInternalState)
5555import Data.Conduit.Internal (Pipe (NeedInput, HaveOutput), (>+>), withUpstream, injectLeftovers, ConduitM (..))
5656import Data.Void (Void)
5757
wai-extra/test/WaiExtraTest.hs+1−2
@@ -4,7 +4,6 @@ module WaiExtraTest (specs) where
44import Test.Hspec
55import Test.HUnit hiding (Test)
66import Data.Monoid (mappend, mempty)
7−import Blaze.ByteString.Builder (toByteString)
87
98import Network.Wai
109import Network.Wai.Test
@@ -19,7 +18,7 @@ import qualified Data.Text as TS
1918import qualified Data.Text.Encoding as TE
2019import Control.Arrow
2120import Control.Applicative
22−import Control.Monad.Trans.Resource (getInternalState, withInternalState, runResourceT)
21+import Control.Monad.Trans.Resource (withInternalState, runResourceT)
2322
2423import Network.Wai.Middleware.Jsonp
2524import Network.Wai.Middleware.Gzip
wai-extra/wai-extra.cabal+2−2
@@ -1,5 +1,5 @@
11Name: wai-extra
2−Version: 2.0.3.3
2+Version: 2.0.3.4
33Synopsis: Provides some basic WAI handlers and middleware.
44Description: The goal here is to provide common features without many dependencies.
55License: MIT
@@ -22,7 +22,7 @@ extra-source-files:
2222Library
2323 Build-Depends: base >= 4 && < 5
2424 , bytestring >= 0.9.1.4
25− , wai >= 2.0 && < 2.1
25+ , wai >= 2.0 && < 2.2
2626 , old-locale >= 1.0.0.2 && < 1.1
2727 , time >= 1.1.4
2828 , network >= 2.2.1.5
wai-frontend-monadcgi/wai-frontend-monadcgi.cabal+1−1
@@ -20,7 +20,7 @@ library
2020 , http-types
2121 , transformers
2222 , case-insensitive
23− , wai >= 2.0 && < 2.1
23+ , wai >= 2.0 && < 2.2
2424 exposed-modules: Network.Wai.Frontend.MonadCGI
2525 ghc-options: -Wall
2626
wai-handler-fastcgi/wai-handler-fastcgi.cabal+1−1
@@ -15,7 +15,7 @@ description: Calls out to the libfcgi C library.
1515
1616library
1717 build-depends: base >= 4 && < 5
18− , wai >= 2.0 && < 2.1
18+ , wai >= 2.0 && < 2.2
1919 , wai-extra >= 2.0 && < 2.1
2020 , bytestring >= 0.9.1.4
2121 exposed-modules: Network.Wai.Handler.FastCGI
wai-handler-launch/wai-handler-launch.cabal+2−2
@@ -1,5 +1,5 @@
11Name: wai-handler-launch
2−Version: 2.0.1
2+Version: 2.0.1.1
33Synopsis: Launch a web app in the default browser.
44Description: This handles cross-platform launching and inserts Javascript code to ping the server. When the server no longer receives pings, it shuts down.
55License: MIT
@@ -13,7 +13,7 @@ Cabal-version: >=1.6
1313Library
1414 Exposed-modules: Network.Wai.Handler.Launch
1515 build-depends: base >= 4 && < 5
16− , wai >= 2.0 && < 2.1
16+ , wai >= 2.0 && < 2.2
1717 , warp >= 2.0 && < 2.2
1818 , http-types >= 0.7
1919 , transformers >= 0.2.2 && < 0.4
wai-handler-scgi/wai-handler-scgi.cabal+2−2
@@ -1,5 +1,5 @@
11name: wai-handler-scgi
2−version: 2.0.0
2+version: 2.0.0.1
33license: MIT
44license-file: LICENSE
55author: Michael Snoyman <michael@snoyman.com>
@@ -14,7 +14,7 @@ description: Wai handler to SCGI
1414
1515library
1616 build-depends: base >= 4 && < 5
17− , wai >= 2.0 && < 2.1
17+ , wai >= 2.0 && < 2.2
1818 , wai-extra >= 2.0 && < 2.1
1919 , bytestring >= 0.9.1.4
2020 exposed-modules: Network.Wai.Handler.SCGI
wai-test/wai-test.cabal+1−1
@@ -14,7 +14,7 @@ description: Unit test framework (built on HUnit) for WAI applications.
1414
1515library
1616 build-depends: base >= 4 && < 5
17− , wai >= 2.0 && < 2.1
17+ , wai >= 2.0 && < 2.2
1818 , bytestring >= 0.9.1.4
1919 , text >= 0.7
2020 , blaze-builder >= 0.2.1.4 && < 0.4
wai-websockets/Network/Wai/Handler/WebSockets.hs+48−48
@@ -1,46 +1,48 @@
11{-# LANGUAGE OverloadedStrings #-}
22module Network.Wai.Handler.WebSockets
3− ( intercept
4− , interceptWith
3+ ( websocketsApp
4+ , websocketsOr
55 ) where
66
7−import Control.Monad.IO.Class (liftIO)
8−import Blaze.ByteString.Builder (Builder)
9−import qualified Blaze.ByteString.Builder as Builder
107import Data.ByteString (ByteString)
118import qualified Data.ByteString.Char8 as BC
12−import Data.Char (toLower)
9+import qualified Data.CaseInsensitive as CI
1310import Data.Conduit
11+import Data.IORef (newIORef, readIORef, writeIORef)
12+import Network.HTTP.Types (status500)
1413import qualified Network.Wai as Wai
15−import qualified Network.Wai.Handler.Warp as Warp
1614import qualified Network.WebSockets as WS
1715import qualified Network.WebSockets.Connection as WS
1816import System.IO.Streams (InputStream, OutputStream)
1917import qualified System.IO.Streams as Streams
2018
2119--------------------------------------------------------------------------------
22−-- | For use with 'settingsIntercept' from the Warp web server.
23−intercept :: WS.ServerApp
24− -> Wai.Request
25− -> Maybe (Source IO ByteString -> Warp.Connection -> IO ())
26−intercept = interceptWith WS.defaultConnectionOptions
20+websocketsOr :: WS.ConnectionOptions
21+ -> WS.ServerApp
22+ -> Wai.Application
23+ -> Wai.Application
24+websocketsOr opts app backup req =
25+ case websocketsApp opts app req of
26+ Nothing -> backup req
27+ Just res -> return res
2728
2829--------------------------------------------------------------------------------
29−-- | Variation of 'intercept' which allows custom options.
30−interceptWith :: WS.ConnectionOptions
30+websocketsApp :: WS.ConnectionOptions
3131 -> WS.ServerApp
3232 -> Wai.Request
33− -> Maybe (Source IO ByteString -> Warp.Connection -> IO ())
34−interceptWith opts app req = case lookup "upgrade" (Wai.requestHeaders req) of
35− Just s
36− | BC.map toLower s == "websocket" -> Just $ runWebSockets opts req' app
37− | otherwise -> Nothing
38− _ -> Nothing
39− where
40− req' = WS.RequestHead
41− (Wai.rawPathInfo req `BC.append` Wai.rawQueryString req)
42− (Wai.requestHeaders req)
43− (Wai.isSecure req)
33+ -> Maybe Wai.Response
34+websocketsApp opts app req
35+ | fmap CI.mk (lookup "upgrade" $ Wai.requestHeaders req) == Just "websocket" =
36+ Just $ flip Wai.responseRaw backup $ \src sink ->
37+ runWebSockets opts req' app src sink
38+ | otherwise = Nothing
39+ where
40+ req' = WS.RequestHead
41+ (Wai.rawPathInfo req `BC.append` Wai.rawQueryString req)
42+ (Wai.requestHeaders req)
43+ (Wai.isSecure req)
44+ backup = Wai.responseLBS status500 [("Content-Type", "text/plain")]
45+ "The web application attempted to send a WebSockets response, but WebSockets are not supported by your WAI handler."
4446
4547--------------------------------------------------------------------------------
4648---- | Internal function to run the WebSocket io-streams using the conduit library
@@ -48,13 +50,14 @@ runWebSockets :: WS.ConnectionOptions
4850 -> WS.RequestHead
4951 -> WS.ServerApp
5052 -> Source IO ByteString
51− -> Warp.Connection
53+ -> Sink ByteString IO ()
5254 -> IO ()
53−runWebSockets opts req app _ conn = do
55+runWebSockets opts req app src sink = do
5456
55− (is, os) <- liftIO $ connectionToStreams conn
57+ is <- srcToInput src
58+ os <- sinkToOutput sink >>= Streams.builderStream
5659
57− let pc = WS.PendingConnection
60+ let pc = WS.PendingConnection
5861 { WS.pendingOptions = opts
5962 , WS.pendingRequest = req
6063 , WS.pendingOnAccept = \_ -> return ()
@@ -62,24 +65,21 @@ runWebSockets opts req app _ conn = do
6265 , WS.pendingOut = os
6366 }
6467
65− liftIO $ app pc
68+ app pc
6669
67−------------------------------------------------------------------------------
68−-- | Converts a 'Connection' to an 'InputStream' \/ 'OutputStream' pair. Note that,
69−-- as is usually the case in @io-streams@, writing a 'Nothing' to the generated
70−-- 'OutputStream' does not cause the underlying 'Connection' to be closed.
71−connectionToStreams :: Warp.Connection
72− -> IO (InputStream ByteString, OutputStream Builder)
73−connectionToStreams connection = do
74− is <- Streams.makeInputStream input
75− os <- Streams.makeOutputStream output
76− return $! (is, os)
77−
78− where
79− input = do
80− s <- Warp.connRecv connection
81− return $! if BC.null s then Nothing else Just s
70+srcToInput :: Source IO ByteString -> IO (InputStream ByteString)
71+srcToInput src0 = do
72+ (rsrc0, ()) <- src0 $$+ return ()
73+ ref <- newIORef rsrc0
74+ Streams.makeInputStream $ do
75+ rsrc <- readIORef ref
76+ (rsrc', mbs) <- rsrc $$++ await
77+ writeIORef ref rsrc'
78+ return mbs
8279
83− output Nothing = return $! ()
84− output (Just s') = if BC.null s then return $! () else Warp.connSendAll connection s
85− where s = Builder.toByteString s'
80+sinkToOutput :: Sink ByteString IO () -> IO (OutputStream ByteString)
81+sinkToOutput sink =
82+ Streams.makeOutputStream output
83+ where
84+ output Nothing = return ()
85+ output (Just bs) = yield bs $$ sink
wai-websockets/server.lhs+1−2
@@ -78,8 +78,7 @@ actual server. For this purpose, we use the simple server provided by
7878> state <- newMVar newServerState
7979> Warp.runSettings Warp.defaultSettings
8080> { Warp.settingsPort = 9160
81−> , Warp.settingsIntercept = return . WaiWS.intercept (application state)
82−> } staticApp
81+> } $ WaiWS.websocketsOr WS.defaultConnectionOptions (application state) staticApp
8382
8483> staticApp :: Network.Wai.Application
8584> staticApp = Static.staticApp $ Static.embeddedSettings $(embedDir "static")
wai-websockets/wai-websockets.cabal+4−3
@@ -1,5 +1,5 @@
11Name: wai-websockets
2−Version: 2.0.1
2+Version: 2.1.0
33Synopsis: Provide a bridge betweeen WAI and the websockets package.
44License: MIT
55License-file: LICENSE
@@ -20,14 +20,14 @@ Library
2020 Build-Depends: base >= 3 && < 5
2121 , bytestring >= 0.9.1.4
2222 , conduit >= 0.5 && < 1.1
23− , wai >= 2.0 && < 2.1
23+ , wai >= 2.1 && < 2.2
2424 , blaze-builder >= 0.2.1.4 && < 0.4
2525 , case-insensitive >= 0.2
2626 , network >= 2.2.1.5
2727 , transformers >= 0.2 && < 0.4
2828 , websockets >= 0.8
29− , warp >= 2.0 && < 2.2
3029 , io-streams >= 1.1 && < 1.2
30+ , http-types
3131 Exposed-modules: Network.Wai.Handler.WebSockets
3232 ghc-options: -Wall
3333
@@ -51,6 +51,7 @@ Executable wai-websockets-example
5151 , text
5252 , file-embed
5353 , io-streams
54+ , http-types
5455
5556 ghc-options: -Wall -threaded
5657 main-is: server.lhs
wai/Network/Wai.hs+17
@@ -69,6 +69,7 @@ module Network.Wai
6969 , responseLBS
7070 , responseSource
7171 , responseSourceBracket
72+ , responseRaw
7273 -- * Response accessors
7374 , responseStatus
7475 , responseHeaders
@@ -153,6 +154,19 @@ responseSourceBracket setup teardown action =
153154 return $ ResponseSource st hdr $ \f ->
154155 bracket (return resource) teardown (\_ -> f src)
155156
157+-- | Create a response for a raw application. This is useful for \"upgrade\"
158+-- situations such as WebSockets, where an application requests for the server
159+-- to grant it raw network access.
160+--
161+-- This function requires a backup response to be provided, for the case where
162+-- the handler in question does not support such upgrading (e.g., CGI apps).
163+--
164+-- Since 2.1.0
165+responseRaw :: (C.Source IO B.ByteString -> C.Sink B.ByteString IO () -> IO ())
166+ -> Response
167+ -> Response
168+responseRaw = ResponseRaw
169+
156170----------------------------------------------------------------
157171
158172-- | Accessing 'H.Status' in 'Response'.
@@ -160,12 +174,14 @@ responseStatus :: Response -> H.Status
160174responseStatus (ResponseFile s _ _ _) = s
161175responseStatus (ResponseBuilder s _ _ ) = s
162176responseStatus (ResponseSource s _ _ ) = s
177+responseStatus (ResponseRaw _ res ) = responseStatus res
163178
164179-- | Accessing 'H.Status' in 'Response'.
165180responseHeaders :: Response -> H.ResponseHeaders
166181responseHeaders (ResponseFile _ hs _ _) = hs
167182responseHeaders (ResponseBuilder _ hs _ ) = hs
168183responseHeaders (ResponseSource _ hs _ ) = hs
184+responseHeaders (ResponseRaw _ res) = responseHeaders res
169185
170186-- | Converting the body information in 'Response' to 'Source'.
171187responseToSource :: Response
@@ -177,6 +193,7 @@ responseToSource (ResponseFile s h fp Nothing) =
177193 (s, h, \f -> IO.withFile fp IO.ReadMode $ \handle -> f $ CB.sourceHandle handle C.$= CL.map (C.Chunk . fromByteString))
178194responseToSource (ResponseBuilder s h b) =
179195 (s, h, ($ CL.sourceList [C.Chunk b]))
196+responseToSource (ResponseRaw _ res) = responseToSource res
180197
181198sourceFilePart :: IO.Handle -> FilePart -> C.Source IO B.ByteString
182199sourceFilePart handle (FilePart offset count _) =
wai/Network/Wai/Internal.hs+1
@@ -87,6 +87,7 @@ data Response
8787 = ResponseFile H.Status H.ResponseHeaders FilePath (Maybe FilePart)
8888 | ResponseBuilder H.Status H.ResponseHeaders Builder
8989 | ResponseSource H.Status H.ResponseHeaders (forall b. WithSource IO (C.Flush Builder) b)
90+ | ResponseRaw (C.Source IO B.ByteString -> C.Sink B.ByteString IO () -> IO ()) Response
9091 deriving Typeable
9192
9293-- | Auxiliary type for 'ResponseSource'.
wai/wai.cabal+1−1
@@ -1,5 +1,5 @@
11Name: wai
2−Version: 2.0.0
2+Version: 2.1.0
33Synopsis: Web Application Interface.
44Description: Provides a common protocol for communication between web applications and web servers.
55License: MIT
warp-tls/warp-tls.cabal+2−2
@@ -1,5 +1,5 @@
11Name: warp-tls
2−Version: 2.0.3
2+Version: 2.0.3.1
33Synopsis: HTTP over SSL/TLS support for Warp via the TLS package
44License: MIT
55License-file: LICENSE
@@ -15,7 +15,7 @@ Description: HTTP over SSL/TLS support for Warp via the TLS package.
1515Library
1616 Build-Depends: base >= 4 && < 5
1717 , bytestring >= 0.9
18− , wai >= 2.0 && < 2.1
18+ , wai >= 2.0 && < 2.2
1919 , warp >= 2.0.2 && < 2.2
2020 , transformers >= 0.2
2121 , conduit >= 0.5 && < 1.1
warp/Network/Wai/Handler/Warp/Request.hs+10−8
@@ -49,13 +49,15 @@ recvRequest :: Settings
4949 -> Source IO ByteString -- ^ Where HTTP request comes from.
5050 -> IO (Request
5151 ,IndexedHeader
52− ,IO (ResumableSource IO ByteString)) -- ^
52+ ,IO (ResumableSource IO ByteString)
53+ ,Maybe ByteString) -- ^
5354 -- 'Request' passed to 'Application',
54− -- 'IndexedHeader' of HTTP request for internal use, and
55− -- leftover source (i.e. body and other HTTP reqeusts in HTTP pipelining).
55+ -- 'IndexedHeader' of HTTP request for internal use,
56+ -- leftover source (i.e. body and other HTTP reqeusts in HTTP pipelining),
57+ -- leftovers from request header parsing (used for raw responses)
5658
5759recvRequest settings conn ii addr src0 = do
58− (src, hdrlines) <- src0 $$+ headerLines
60+ (src, (leftover', hdrlines)) <- src0 $$+ headerLines
5961 (method, unparsedPath, path, query, httpversion, hdr) <- parseHeaderLines hdrlines
6062 let idxhdr = indexRequestHeader hdr
6163 expect = idxhdr ! idxExpect
@@ -79,13 +81,13 @@ recvRequest settings conn ii addr src0 = do
7981 , requestHeaderHost = idxhdr ! idxHost
8082 , requestHeaderRange = idxhdr ! idxRange
8183 }
82− return (req, idxhdr, getSource)
84+ return (req, idxhdr, getSource, leftover')
8385 where
8486 th = threadHandle ii
8587
8688----------------------------------------------------------------
8789
88−headerLines :: Sink ByteString IO [ByteString]
90+headerLines :: Sink ByteString IO (Maybe ByteString, [ByteString])
8991headerLines =
9092 await >>= maybe (throwIO (NotEnoughLines [])) (push (THStatus 0 id id))
9193
@@ -161,7 +163,7 @@ data THStatus = THStatus
161163close :: Sink ByteString IO a
162164close = throwIO IncompleteHeaders
163165
164−push :: THStatus -> ByteString -> Sink ByteString IO [ByteString]
166+push :: THStatus -> ByteString -> Sink ByteString IO (Maybe ByteString, [ByteString])
165167push (THStatus len lines prepend) bs
166168 -- Too many bytes
167169 | len > maxTotalHeaderLength = throwIO OverLargeHeader
@@ -205,7 +207,7 @@ push (THStatus len lines prepend) bs
205207 Just (SU.unsafeDrop start bs)
206208 else
207209 Nothing
208− in maybe (return ()) leftover rest >> return lines'
210+ in maybe (return ()) leftover rest >> return (rest, lines')
209211 -- more headers
210212 | otherwise = let len' = len + start
211213 lines' = lines . (line:)
warp/Network/Wai/Handler/Warp/Response.hs+22−1
@@ -18,9 +18,12 @@ import Control.Exception
1818import Control.Monad.IO.Class (liftIO)
1919import Data.Array ((!))
2020import Data.ByteString (ByteString)
21+import qualified Data.ByteString as S
22+import Control.Monad (unless)
2123import qualified Data.ByteString.Char8 as B (pack)
2224import qualified Data.CaseInsensitive as CI
2325import Data.Conduit
26+import qualified Data.Conduit.List as CL
2427import Data.Conduit.Blaze (unsafeBuilderToByteString)
2528import Data.Function (on)
2629import Data.List (deleteBy)
@@ -150,9 +153,10 @@ sendResponse :: Connection
150153 -> (forall a. IO a -> IO a) -- ^ Restore masking state.
151154 -> Request -- ^ HTTP request.
152155 -> IndexedHeader -- ^ Indexed header of HTTP request.
156+ -> Maybe ByteString -- ^ leftovers from header parsing
153157 -> Response -- ^ HTTP response including status code and response header.
154158 -> IO Bool -- ^ Returing True if the connection is persistent.
155−sendResponse conn ii restore req reqidxhdr response = restore $ do
159+sendResponse conn ii restore req reqidxhdr leftover' response = restore $ do
156160 hs <- addServerAndDate hs0
157161 if hasBody s req then do
158162 sendRsp conn ver s hs rsp
@@ -177,16 +181,19 @@ sendResponse conn ii restore req reqidxhdr response = restore $ do
177181 ResponseFile _ _ path mPart -> RspFile path mPart mRange (T.tickle th)
178182 ResponseBuilder _ _ b -> RspBuilder b needsChunked
179183 ResponseSource _ _ fb -> RspSource fb needsChunked th
184+ ResponseRaw raw _ -> RspRaw raw leftover'
180185 ret = case response of
181186 ResponseFile _ _ _ _ -> isPersist
182187 ResponseBuilder _ _ _ -> isKeepAlive
183188 ResponseSource _ _ _ -> isKeepAlive
189+ ResponseRaw _ _ -> False
184190
185191----------------------------------------------------------------
186192
187193data Rsp = RspFile FilePath (Maybe FilePart) (Maybe HeaderValue) (IO ())
188194 | RspBuilder Builder Bool
189195 | RspSource (forall b. WithSource IO (Flush Builder) b) Bool T.Handle
196+ | RspRaw (Source IO ByteString -> Sink ByteString IO () -> IO ()) (Maybe ByteString)
190197
191198----------------------------------------------------------------
192199
@@ -240,6 +247,20 @@ sendRsp conn ver s hs (RspSource withBodyFlush needsChunked th) = withBodyFlush
240247
241248----------------------------------------------------------------
242249
250+sendRsp conn _ _ _ (RspRaw app mbs) =
251+ app src sink
252+ where
253+ sink = CL.mapM_ (connSendAll conn)
254+ src = do
255+ maybe (return ()) yield mbs
256+ loop
257+ where
258+ loop = do
259+ bs <- liftIO $ connRecv conn
260+ unless (S.null bs) $ yield bs >> loop
261+
262+----------------------------------------------------------------
263+
243264sendResponseNoBody :: Connection
244265 -> H.HttpVersion
245266 -> H.Status
warp/Network/Wai/Handler/Warp/Run.hs+8−7
@@ -231,14 +231,14 @@ serveConnection conn ii addr settings app = do
231231 sendErrorResponse istatus e = do
232232 status <- readIORef istatus
233233 when status $ void $ mask $ \restore ->
234− sendResponse conn ii restore dummyreq defaultIndexRequestHeader (errorResponse e)
234+ sendResponse conn ii restore dummyreq defaultIndexRequestHeader Nothing (errorResponse e)
235235
236236 dummyreq = defaultRequest { remoteHost = addr }
237237
238238 errorResponse e = settingsOnExceptionResponse settings e
239239
240240 recvSendLoop istatus fromClient = do
241− (req, idxhdr, getSource) <- recvRequest settings conn ii addr fromClient
241+ (req, idxhdr, getSource, leftover') <- recvRequest settings conn ii addr fromClient
242242 intercept' <- settingsIntercept settings req
243243 case intercept' of
244244 Nothing -> do
@@ -255,7 +255,7 @@ serveConnection conn ii addr settings app = do
255255 -- send more meaningful error messages to the user.
256256 -- However, it may affect performance.
257257 writeIORef istatus False
258− sendResponse conn ii restore req idxhdr res
258+ sendResponse conn ii restore req idxhdr leftover' res
259259
260260 -- We just send a Response and it takes a time to
261261 -- receive a Request again. If we immediately call recv,
@@ -266,11 +266,12 @@ serveConnection conn ii addr settings app = do
266266 -- This improves performance at least when
267267 -- the number of cores is small.
268268 Conc.yield
269− -- flush the rest of the request body
270− requestBody req $$ CL.sinkNull
271− ResumableSource fromClient' _ <- getSource
272269
273− when keepAlive $ recvSendLoop istatus fromClient'
270+ when keepAlive $ do
271+ -- flush the rest of the request body
272+ requestBody req $$ CL.sinkNull
273+ ResumableSource fromClient' _ <- getSource
274+ recvSendLoop istatus fromClient'
274275 Just intercept -> do
275276 T.pause th
276277 ResumableSource fromClient' _ <- getSource
warp/test/RequestSpec.hs+2−2
@@ -23,9 +23,9 @@ spec :: Spec
2323spec = do
2424 describe "headerLines" $ do
2525 it "takes until blank" $
26− blankSafe >>= (`shouldBe` ["foo", "bar", "baz"])
26+ blankSafe >>= (`shouldBe` (Nothing, ["foo", "bar", "baz"]))
2727 it "ignored leading whitespace in bodies" $
28− whiteSafe >>= (`shouldBe` ["foo", "bar", "baz"])
28+ whiteSafe >>= (`shouldBe` (Just " hi there", ["foo", "bar", "baz"]))
2929 it "throws OverLargeHeader when too many" $
3030 tooMany `shouldThrow` overLargeHeader
3131 it "throws OverLargeHeader when too large" $
warp/test/RunSpec.hs+18−1
@@ -11,7 +11,7 @@ import Data.ByteString (ByteString, hPutStr, hGetSome)
1111import qualified Data.ByteString as S
1212import qualified Data.ByteString.Char8 as S8
1313import qualified Data.ByteString.Lazy as L
14−import Data.Conduit (($$))
14+import Data.Conduit (($$), (=$))
1515import qualified Data.Conduit.List
1616import qualified Data.IORef as I
1717import Network (connectTo, PortID (PortNumber))
@@ -293,3 +293,20 @@ spec = do
293293 threadDelay 5000000
294294 front <- I.readIORef ifront
295295 S.concat (front []) `shouldBe` bs
296+ describe "raw body" $ do
297+ it "works" $ do
298+ let app _req = do
299+ let backup = responseLBS status200 [] "Not raw"
300+ return $ flip responseRaw backup $ \src sink ->
301+ src
302+ $$ Data.Conduit.List.map doubleBS
303+ =$ sink
304+ doubleBS = S.concatMap $ \w -> S.pack [w, w]
305+ withApp defaultSettings app $ \port -> do
306+ handle <- connectTo "127.0.0.1" $ PortNumber $ fromIntegral port
307+ hPutStr handle "POST / HTTP/1.1\r\n\r\n12345"
308+ hFlush handle
309+ timeout 100000 (S.hGet handle 10) >>= (`shouldBe` Just "1122334455")
310+ hPutStr handle "67890"
311+ hFlush handle
312+ timeout 100000 (S.hGet handle 10) >>= (`shouldBe` Just "6677889900")
warp/warp.cabal+2−2
@@ -45,7 +45,7 @@ Library
4545 , transformers >= 0.2.2 && < 0.4
4646 , unix-compat >= 0.2
4747 , void
48− , wai >= 2.0 && < 2.1
48+ , wai >= 2.1 && < 2.2
4949 if flag(network-bytestring)
5050 Build-Depends: network >= 2.2.1.5 && < 2.2.3
5151 , network-bytestring >= 0.1.3 && < 0.1.4
@@ -126,7 +126,7 @@ Test-Suite spec
126126 , transformers >= 0.2.2 && < 0.4
127127 , unix-compat >= 0.2
128128 , void
129− , wai >= 2.0 && < 2.1
129+ , wai
130130 , network
131131 , HUnit
132132 , QuickCheck