on master help sign in
Preserve connections in some exceptional situations for HTTP 1.x
commitfd62edc6982e7deb2fb96af17d93c3c4c94ee1b8 authorIan Duncan <ian@iankduncan.com> dateThu 18 Apr 2019, 21:34 (7 years ago) parent82f7a63
3 files changed+49−20
warp/Network/Wai/Handler/Warp/Response.hs+12−9 warp/Network/Wai/Handler/Warp/Run.hs+23−11 warp/Network/Wai/Handler/Warp/Types.hs+14
warp/Network/Wai/Handler/Warp/Response.hs+12−9
@@ -11,6 +11,7 @@ module Network.Wai.Handler.Warp.Response (
1111 , hasBody
1212 , replaceHeader
1313 , addServer -- testing
14+ , infoFromRequest -- for determining whether to close connections on error.
1415 ) where
1516
1617import Data.ByteString.Builder.HTTP.Chunked (chunkedTransferEncoding, chunkedTransferTerminator)
@@ -211,13 +212,14 @@ sendRsp conn _ ver s hs RspNoBody = do
211212
212213sendRsp conn _ ver s hs (RspBuilder body needsChunked) = do
213214 header <- composeHeaderBuilder ver s hs needsChunked
214− let hdrBdy
215− | needsChunked = header <> chunkedTransferEncoding body
216− <> chunkedTransferTerminator
217− | otherwise = header <> body
215+ let bdy
216+ | needsChunked = chunkedTransferEncoding body <> chunkedTransferTerminator
217+ | otherwise = body
218218 buffer = connWriteBuffer conn
219219 size = connBufferSize conn
220− toBufIOWith buffer size (connSendAll conn) hdrBdy
220+ toBufIOWith buffer size (connSendAll conn) header
221+ E.handle (E.throwIO . ExceptionInsideResponseBody) $
222+ toBufIOWith buffer size (connSendAll conn) bdy
221223 return (Just s, Nothing) -- fixme: can we tell the actual sent bytes?
222224
223225----------------------------------------------------------------
@@ -238,10 +240,11 @@ sendRsp conn _ ver s hs (RspStream streamingBody needsChunked th) = do
238240 | needsChunked = send . chunkedTransferEncoding
239241 | otherwise = send
240242 send header
241− streamingBody sendChunk (sendChunk flush)
242− when needsChunked $ send chunkedTransferTerminator
243− mbs <- finish
244− maybe (return ()) (sendFragment conn th) mbs
243+ E.handle (E.throwIO . ExceptionInsideResponseBody) $ do
244+ streamingBody sendChunk (sendChunk flush)
245+ when needsChunked $ send chunkedTransferTerminator
246+ mbs <- finish
247+ maybe (return ()) (sendFragment conn th) mbs
245248 return (Just s, Nothing) -- fixme: can we tell the actual sent bytes?
246249
247250----------------------------------------------------------------
warp/Network/Wai/Handler/Warp/Run.hs+23−11
@@ -293,7 +293,7 @@ fork set mkConn addr app counter ii0 = settingsFork set $ \unmask ->
293293 serve unmask ref (conn, transport) = bracket register cancel $ \th -> do
294294 let ii1 = toInternalInfo1 ii0 th
295295 -- We now have fully registered a connection close handler in
296− -- the case of all exceptions, so it is safe to one again
296+ -- the case of all exceptions, so it is safe to once again
297297 -- allow async exceptions.
298298 unmask .
299299 -- Call the user-supplied code for connection open and
@@ -409,11 +409,22 @@ serveConnection conn ii1 origAddr transport settings app = do
409409 let req = req' { isSecure = isTransportSecure transport }
410410 keepAlive <- processRequest istatus src req mremainingRef idxhdr nextBodyFlush ii
411411 `E.catch` \e -> do
412− -- Call the user-supplied exception handlers, passing the request.
413− sendErrorResponse req istatus e
414− settingsOnException settings (Just req) e
415− -- Don't throw the error again to prevent calling settingsOnException twice.
416− return False
412+ -- Attempt to unwrap exception and use it to determine
413+ -- safety of keeping exception alive
414+ case fromException e of
415+ Nothing -> do
416+ -- Call the user-supplied exception handlers, passing the request.
417+ sendErrorResponse req istatus e
418+ settingsOnException settings (Just req) e
419+ -- Don't throw the error again to prevent calling settingsOnException twice.
420+ let (isPersist, _) = infoFromRequest req idxhdr
421+ return isPersist
422+ Just (ExceptionInsideResponseBody e') -> do
423+ -- Call the user-supplied exception handlers, passing the request.
424+ sendErrorResponse req istatus e'
425+ settingsOnException settings (Just req) e'
426+ -- Don't throw the error again to prevent calling settingsOnException twice.
427+ return False
417428
418429 -- When doing a keep-alive connection, the other side may just
419430 -- close the connection. We don't want to treat that as an
@@ -446,17 +457,16 @@ serveConnection conn ii1 origAddr transport settings app = do
446457
447458 -- We just send a Response and it takes a time to
448459 -- receive a Request again. If we immediately call recv,
449− -- it is likely to fail and the IO manager works.
450− -- It is very costly. So, we yield to another Haskell
460+ -- it is likely to fail and cause the IO manager to do some work.
461+ -- It is very costly, so we yield to another Haskell
451462 -- thread hoping that the next Request will arrive
452463 -- when this Haskell thread will be re-scheduled.
453464 -- This improves performance at least when
454465 -- the number of cores is small.
455466 Conc.yield
456467
457− if not keepAlive then
458− return False
459− else
468+ if keepAlive
469+ then
460470 -- If there is an unknown or large amount of data to still be read
461471 -- from the request body, simple drop this connection instead of
462472 -- reading it all in to satisfy a keep-alive request.
@@ -482,6 +492,8 @@ serveConnection conn ii1 origAddr transport settings app = do
482492 else
483493 return False
484494 Nothing -> tryKeepAlive
495+ else
496+ return False
485497
486498flushEntireBody :: IO ByteString -> IO ()
487499flushEntireBody src =
warp/Network/Wai/Handler/Warp/Types.hs+14
@@ -52,6 +52,20 @@ instance Exception InvalidRequest
5252
5353----------------------------------------------------------------
5454
55+-- | Exception thrown if something goes wrong while in the midst of
56+-- sending a response, since the status code can't be altered at that
57+-- point.
58+--
59+-- Used to determine whether keeping the HTTP1.1 connection / HTTP2 stream alive is safe
60+-- or irrecoverable.
61+
62+newtype ExceptionInsideResponseBody = ExceptionInsideResponseBody SomeException
63+ deriving (Show, Typeable)
64+
65+instance Exception ExceptionInsideResponseBody
66+
67+----------------------------------------------------------------
68+
5569-- | Data type to abstract file identifiers.
5670-- On Unix, a file descriptor would be specified to make use of
5771-- the file descriptor cache.