on master help sign in
Modify request body flushing behavior.

See explanation in ChangeLog.md. Pinging @kazu-yamamoto: do you see any problems with this change?

commit38934e7eccbf8fded47d3e3cb5ff3fd36b27ee82 authorMichael Snoyman <michael@snoyman.com> dateWed 26 Nov 2014, 13:53 (12 years ago) parent5043338
4 files changed+49−15
warp/ChangeLog.md+13 warp/Network/Wai/Handler/Warp/Request.hs+8−5 warp/Network/Wai/Handler/Warp/Run.hs+27−9 warp/warp.cabal+1−1
warp/ChangeLog.md+13
@@ -1,3 +1,16 @@
1+## 3.0.2.4
2+
3+Modify flushing of request bodies. Previously, regardless of the size of the
4+request body, the entire body would be flushed. When uploading large files to a
5+web app that does not accept such files (e.g., returns a 413 too large status),
6+browsers would still send the entire request body and the servers will still
7+receive it.
8+
9+The new behavior is to detect if there is a large amount of data still to be
10+consumed and, if so, immediately terminate the connection. In the case of
11+chunked request bodies, up to a maximum number of bytes is consumed before the
12+connection is terminated.
13+
114## 3.0.0
215
316WAI no longer uses conduit for its streaming interface.
warp/Network/Wai/Handler/Warp/Request.hs+8−5
@@ -47,8 +47,10 @@ recvRequest :: Settings
4747 -> SockAddr -- ^ Peer's address.
4848 -> Source -- ^ Where HTTP request comes from.
4949 -> IO (Request
50+ ,Maybe (I.IORef Int)
5051 ,IndexedHeader) -- ^
5152 -- 'Request' passed to 'Application',
53+ -- how many bytes remain to be consumed, if known
5254 -- 'IndexedHeader' of HTTP request for internal use,
5355
5456recvRequest settings conn ii addr src = do
@@ -59,7 +61,7 @@ recvRequest settings conn ii addr src = do
5961 cl = idxhdr ! idxContentLength
6062 te = idxhdr ! idxTransferEncoding
6163 handleExpect conn httpversion expect
62− (rbody, bodyLength) <- bodyAndSource src cl te
64+ (rbody, remainingRef, bodyLength) <- bodyAndSource src cl te
6365 rbody' <- timeoutBody th rbody
6466 let req = Request {
6567 requestMethod = method
@@ -77,7 +79,7 @@ recvRequest settings conn ii addr src = do
7779 , requestHeaderHost = idxhdr ! idxHost
7880 , requestHeaderRange = idxhdr ! idxRange
7981 }
80− return (req, idxhdr)
82+ return (req, remainingRef, idxhdr)
8183 where
8284 th = threadHandle ii
8385
@@ -111,15 +113,16 @@ bodyAndSource :: Source
111113 -> Maybe HeaderValue -- ^ content length
112114 -> Maybe HeaderValue -- ^ transfer-encoding
113115 -> IO (IO ByteString
116+ ,Maybe (I.IORef Int)
114117 ,RequestBodyLength
115118 )
116119bodyAndSource src cl te
117120 | chunked = do
118121 csrc <- mkCSource src
119− return (readCSource csrc, ChunkedBody)
122+ return (readCSource csrc, Nothing, ChunkedBody)
120123 | otherwise = do
121− isrc <- mkISource src len
122− return (readISource isrc, bodyLen)
124+ isrc@(ISource _ remaining) <- mkISource src len
125+ return (readISource isrc, Just remaining, bodyLen)
123126 where
124127 len = toLength cl
125128 bodyLen = KnownLength $ fromIntegral len
warp/Network/Wai/Handler/Warp/Run.hs+27−9
@@ -293,7 +293,7 @@ serveConnection conn ii addr isSecure' settings app = do
293293 errorResponse e = settingsOnExceptionResponse settings e
294294
295295 recvSendLoop istatus fromClient = do
296− (req', idxhdr) <- recvRequest settings conn ii addr fromClient
296+ (req', mremainingRef, idxhdr) <- recvRequest settings conn ii addr fromClient
297297 let req = req' { isSecure = isSecure' }
298298 -- Let the application run for as long as it wants
299299 T.pause th
@@ -326,18 +326,36 @@ serveConnection conn ii addr isSecure' settings app = do
326326 Conc.yield
327327
328328 when keepAlive $ do
329− -- flush the rest of the request body
330− flushBody $ requestBody req
331− T.resume th
332− recvSendLoop istatus fromClient
333−
334−flushBody :: IO ByteString -> IO ()
329+ -- If there is an unknown or large amount of data to still be read
330+ -- from the request body, simple drop this connection instead of
331+ -- reading it all in to satisfy a keep-alive request.
332+ let maxToRead = 8192 -- FIXME too low?
333+ tryKeepAlive = do
334+ -- flush the rest of the request body
335+ isComplete <- flushBody (requestBody req) maxToRead
336+ when isComplete $ do
337+ T.resume th
338+ recvSendLoop istatus fromClient
339+ case mremainingRef of
340+ Just ref -> do
341+ remaining <- readIORef ref
342+ when (remaining <= maxToRead) tryKeepAlive
343+ Nothing -> tryKeepAlive
344+
345+flushBody :: IO ByteString -- ^ get next chunk
346+ -> Int -- ^ maximum to flush
347+ -> IO Bool -- ^ True == flushed the entire body, False == we didn't
335348flushBody src =
336349 loop
337350 where
338− loop = do
351+ loop toRead = do
339352 bs <- src
340− unless (S.null bs) loop
353+ let toRead' = toRead - S.length bs
354+ case () of
355+ ()
356+ | S.null bs -> return True
357+ | toRead' >= 0 -> loop toRead'
358+ | otherwise -> return False
341359
342360connSource :: Connection -> T.Handle -> IORef Bool -> IO ByteString
343361connSource Connection { connRecv = recv } th istatus = do
warp/warp.cabal+1−1
@@ -1,5 +1,5 @@
11Name: warp
2−Version: 3.0.2.3
2+Version: 3.0.2.4
33Synopsis: A fast, light-weight web server for WAI applications.
44License: MIT
55License-file: LICENSE