on master help sign in
Merge PR #740
commit2b07b8f500b952daf22458a574a3d0867b5d8e96 authorKazu Yamamoto <kazu@iij.ad.jp> dateThu 25 Apr 2019, 09:43 (7 years ago) parent82f7a630585865
6 files changed+49−18
warp/ChangeLog.md+3−1 warp/Network/Wai/Handler/Warp/HTTP2/Receiver.hs+1−1 warp/Network/Wai/Handler/Warp/HTTP2/Worker.hs+7−2 warp/Network/Wai/Handler/Warp/Run.hs+22−12 warp/Network/Wai/Handler/Warp/Types.hs+14 warp/test/ResponseHeaderSpec.hs+2−2
warp/ChangeLog.md+3−1
@@ -3,10 +3,12 @@
33* Internally, use `lookupEnv` instead of `getEnvironment` to get the
44 value of the `PORT` environment variable
55 [#736](https://github.com/yesodweb/wai/pull/736)
6−
76* Throw 413 for too large payload
87* Throw 431 for too large headers
98 [#741](https://github.com/yesodweb/wai/pull/741)
9+* Use exception response handler in HTTP/2 & improve connection preservation
10+ in HTTP/1.x if uncaught exceptions are thrown in an `Application`.
11+ [#738](https://github.com/yesodweb/wai/pull/738)
1012
1113## 3.2.26
1214
warp/Network/Wai/Handler/Warp/HTTP2/Receiver.hs+1−1
@@ -171,7 +171,7 @@ frameReceiver ctx mkreq recvN = loop 0 `E.catch` sendGoaway
171171 E.throwIO $ ConnectionError ProtocolError "stream identifier must not decrease"
172172 else do -- consider the stream idle
173173 when (ftyp `notElem` [FrameHeaders,FramePriority]) $
174− E.throwIO $ ConnectionError ProtocolError $ "this frame is not allowed in an idel stream: " `BS.append` C8.pack (show ftyp)
174+ E.throwIO $ ConnectionError ProtocolError $ "this frame is not allowed in an idle stream: " `BS.append` C8.pack (show ftyp)
175175 when (ftyp == FrameHeaders) $ do
176176 writeIORef clientStreamId streamId
177177 cnt <- readIORef concurrency
warp/Network/Wai/Handler/Warp/HTTP2/Worker.hs+7−2
@@ -119,7 +119,7 @@ pushStream ctx@Context{http2settings,outputQ,streamTable}
119119-- They also pass 'Response's from 'Application's to this function.
120120-- This function enqueues commands for the HTTP/2 sender.
121121response :: S.Settings -> Context -> Manager -> Responder
122−response settings ctx@Context{outputQ} mgr ii reqvt tconf strm req rsp = case rsp of
122+response settings ctx@Context{outputQ} mgr ii reqvt tconf strm req rsp = E.handle (E.throwIO . ExceptionInsideResponseBody) $ case rsp of
123123 ResponseStream s0 hs0 strmbdy
124124 | noBody s0 -> responseNoBody s0 hs0
125125 | isHead -> responseNoBody s0 hs0
@@ -255,7 +255,12 @@ worker ctx@Context{inputQ,controlQ} set app responder tm = do
255255 return bs
256256 !vaultValue = Vault.insert pauseTimeoutKey (Timeout.pause th) $ vault req
257257 !req' = req { vault = vaultValue, requestBody = body' }
258− app req' $ responder ii' reqvt tcont strm req'
258+ mr <- E.try $ app req' $ responder ii' reqvt tcont strm req'
259+ case mr of
260+ Right ok -> return ok
261+ Left e@(SomeException _)
262+ | Just (ExceptionInsideResponseBody e') <- E.fromException e -> E.throwIO e'
263+ | otherwise -> responder ii' reqvt tcont strm req' (S.settingsOnExceptionResponse set e)
259264 cont1 <- case ex of
260265 Right ResponseReceived -> return True
261266 Left e@(SomeException _)
warp/Network/Wai/Handler/Warp/Run.hs+22−12
@@ -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
@@ -396,9 +396,11 @@ serveConnection conn ii1 origAddr transport settings app = do
396396
397397 sendErrorResponse req istatus e = do
398398 status <- readIORef istatus
399− when (shouldSendErrorResponse e && status) $ do
400− let ii = toInternalInfo ii1 0 -- dummy
401− void $ sendResponse settings conn ii req defaultIndexRequestHeader (return S.empty) (errorResponse e)
399+ if (shouldSendErrorResponse e && status)
400+ then do
401+ let ii = toInternalInfo ii1 0 -- dummy
402+ sendResponse settings conn ii req defaultIndexRequestHeader (return S.empty) (errorResponse e)
403+ else return False
402404
403405 dummyreq addr = defaultRequest { remoteHost = addr }
404406
@@ -409,8 +411,6 @@ serveConnection conn ii1 origAddr transport settings app = do
409411 let req = req' { isSecure = isTransportSecure transport }
410412 keepAlive <- processRequest istatus src req mremainingRef idxhdr nextBodyFlush ii
411413 `E.catch` \e -> do
412− -- Call the user-supplied exception handlers, passing the request.
413− sendErrorResponse req istatus e
414414 settingsOnException settings (Just req) e
415415 -- Don't throw the error again to prevent calling settingsOnException twice.
416416 return False
@@ -433,7 +433,7 @@ serveConnection conn ii1 origAddr transport settings app = do
433433 -- creating the request, we need to make sure that we don't get
434434 -- an async exception before calling the ResponseSource.
435435 keepAliveRef <- newIORef $ error "keepAliveRef not filled"
436− _ <- app req $ \res -> do
436+ r <- E.try $ app req $ \res -> do
437437 T.resume th
438438 -- FIXME consider forcing evaluation of the res here to
439439 -- send more meaningful error messages to the user.
@@ -442,21 +442,29 @@ serveConnection conn ii1 origAddr transport settings app = do
442442 keepAlive <- sendResponse settings conn ii req idxhdr (readSource src) res
443443 writeIORef keepAliveRef keepAlive
444444 return ResponseReceived
445+ case r of
446+ Right ResponseReceived -> return ()
447+ Left e@(SomeException _)
448+ | Just (ExceptionInsideResponseBody e') <- fromException e -> throwIO e'
449+ | otherwise -> do
450+ keepAlive <- sendErrorResponse req istatus e
451+ settingsOnException settings (Just req) e
452+ writeIORef keepAliveRef keepAlive
453+
445454 keepAlive <- readIORef keepAliveRef
446455
447456 -- We just send a Response and it takes a time to
448457 -- 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
458+ -- it is likely to fail and cause the IO manager to do some work.
459+ -- It is very costly, so we yield to another Haskell
451460 -- thread hoping that the next Request will arrive
452461 -- when this Haskell thread will be re-scheduled.
453462 -- This improves performance at least when
454463 -- the number of cores is small.
455464 Conc.yield
456465
457− if not keepAlive then
458− return False
459− else
466+ if keepAlive
467+ then
460468 -- If there is an unknown or large amount of data to still be read
461469 -- from the request body, simple drop this connection instead of
462470 -- reading it all in to satisfy a keep-alive request.
@@ -482,6 +490,8 @@ serveConnection conn ii1 origAddr transport settings app = do
482490 else
483491 return False
484492 Nothing -> tryKeepAlive
493+ else
494+ return False
485495
486496flushEntireBody :: IO ByteString -> IO ()
487497flushEntireBody 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.
warp/test/ResponseHeaderSpec.hs+2−2
@@ -71,11 +71,11 @@ spec = do
7171headers :: H.ResponseHeaders
7272headers = [
7373 ("Date", "Mon, 13 Aug 2012 04:22:55 GMT")
74− , ("Content-Lenght", "151")
74+ , ("Content-Length", "151")
7575 , ("Server", "Mighttpd/2.5.8")
7676 , ("Last-Modified", "Fri, 22 Jun 2012 01:18:08 GMT")
7777 , ("Content-Type", "text/html")
7878 ]
7979
8080composedHeader :: ByteString
81−composedHeader = "HTTP/1.1 200 OK\r\nDate: Mon, 13 Aug 2012 04:22:55 GMT\r\nContent-Lenght: 151\r\nServer: Mighttpd/2.5.8\r\nLast-Modified: Fri, 22 Jun 2012 01:18:08 GMT\r\nContent-Type: text/html\r\n\r\n"
81+composedHeader = "HTTP/1.1 200 OK\r\nDate: Mon, 13 Aug 2012 04:22:55 GMT\r\nContent-Length: 151\r\nServer: Mighttpd/2.5.8\r\nLast-Modified: Fri, 22 Jun 2012 01:18:08 GMT\r\nContent-Type: text/html\r\n\r\n"