warp-quic 0.0.4 → 0.0.5
raw patch · 3 files changed
+108/−18 lines, 3 filesdep +stmPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: stm
API changes (from Hackage documentation)
+ Network.Wai.Handler.WarpQUIC: Draining :: TVar Bool -> TVar Int -> Draining
+ Network.Wai.Handler.WarpQUIC: [activeVar] :: Draining -> TVar Int
+ Network.Wai.Handler.WarpQUIC: [drainingVar] :: Draining -> TVar Bool
+ Network.Wai.Handler.WarpQUIC: data Draining
+ Network.Wai.Handler.WarpQUIC: h3NoError :: ApplicationProtocolError
+ Network.Wai.Handler.WarpQUIC: newDraining :: IO Draining
+ Network.Wai.Handler.WarpQUIC: stoppableBy :: Settings -> Draining -> QUICSettings -> QUICSettings
- Network.Wai.Handler.WarpQUIC: quicApp :: Settings -> Application -> InternalInfo -> Connection -> IO ()
+ Network.Wai.Handler.WarpQUIC: quicApp :: Settings -> Draining -> Application -> InternalInfo -> Connection -> IO ()
Files
- ChangeLog.md +10/−0
- Network/Wai/Handler/WarpQUIC.hs +96/−17
- warp-quic.cabal +2/−1
ChangeLog.md view
@@ -1,5 +1,15 @@ # Revision history for warp-quic +## 0.0.5++A QUIC server is stopped through `settingsInstallShutdownHandler`, as a TCP+one is, and lets its HTTP/3 connections answer what they have taken first.+Requires quic 0.3.15 and http3 0.1.10 for these; not on Windows yet.++* Event poll for accept loop.+ [#1123](https://github.com/yesodweb/wai/pull/1123)+* Let HTTP/3 connections finish when the server stops.+ ## 0.0.4 * Adjusted code to fit new range of dependency constraints. [#1043](https://github.com/yesodweb/wai/pull/1043)
Network/Wai/Handler/WarpQUIC.hs view
@@ -4,12 +4,16 @@ -- | WAI handler for HTTP/3 based on QUIC. module Network.Wai.Handler.WarpQUIC where +import Control.Concurrent (forkIO)+import Control.Concurrent.STM+import Control.Monad (void) import qualified Data.ByteString as BS import qualified Network.HQ.Server as HQ import qualified Network.HTTP3.Server as H3 import Network.QUIC import Network.QUIC.Server as Q-import Network.Socket (Socket)+import qualified Control.Exception as E+import Network.Socket (Socket, close) import Network.TLS (cipherID) import Network.Wai import Network.Wai.Handler.Warp hiding (run)@@ -19,28 +23,94 @@ type QUICSettings = ServerConfig runQUICSocket :: QUICSettings -> Settings -> Socket -> Application -> IO ()-runQUICSocket quicsettings settings sock app =- withII settings $ \ii ->- Q.runWithSockets [sock] quicsettings $ quicApp settings app ii+runQUICSocket quicsettings settings sock app = runQUICSockets quicsettings settings [sock] app +-- | The sockets are closed when the server has stopped, as+-- 'Network.Wai.Handler.Warp.runSettingsSocket' closes the one it is given:+-- by then the peers have been told the server is going and the ports are a+-- successor's to take. runQUICSockets :: QUICSettings -> Settings -> [Socket] -> Application -> IO () runQUICSockets quicsettings settings ss app =- withII settings $ \ii ->- Q.runWithSockets ss quicsettings $ quicApp settings app ii+ ( do+ drn <- newDraining+ withII settings $ \ii ->+ Q.runWithSockets ss (stoppableBy settings drn quicsettings) $+ quicApp settings drn app ii+ )+ `E.finally` mapM_ close ss -- | Running warp with HTTP/3 on QUIC. runQUIC :: QUICSettings -> Settings -> Application -> IO ()-runQUIC quicsettings settings app =+runQUIC quicsettings settings app = do+ drn <- newDraining withII settings $ \ii ->- Q.run quicsettings $ quicApp settings app ii+ Q.run (stoppableBy settings drn quicsettings) $ quicApp settings drn app ii +-- | Whether the server is stopping, and how many HTTP/3 connections are+-- still answering what they have taken.+data Draining = Draining+ { drainingVar :: TVar Bool+ , activeVar :: TVar Int+ }++newDraining :: IO Draining+newDraining = Draining <$> newTVarIO False <*> newTVarIO 0++-- | Handing the action that stops the QUIC server to whoever installs+-- shutdown handlers for this warp.+--+-- The two hooks are the same hook: each is handed the action that stops its+-- server, once, as the server starts, and keeping it is what lets a caller+-- stop a server that has no connection to stop it through. So an+-- 'Application' served over TCP and over QUIC is stopped the same way on+-- both, by a caller that need not know which it is holding.+--+-- A caller that installs nothing is left as it was: the QUIC server is+-- stopped by whatever its sockets are, being closed.+--+-- Stopping first tells every HTTP/3 connection to go away: GOAWAY, and the+-- requests it has taken are answered. The server is stopped once they are+-- done, or once 'settingsGracefulShutdownTimeout' has passed, and closes+-- what is left. The wait is in a thread of its own, so that the caller,+-- which may be stopping other servers too, is not held up.+stoppableBy :: Settings -> Draining -> QUICSettings -> QUICSettings+#if MIN_VERSION_quic(0,3,15)+stoppableBy settings (Draining draining active) quicsettings =+ quicsettings+ { Q.scInstallShutdownHandler = settingsInstallShutdownHandler settings . drainThen+ , Q.scCloseReason = (h3NoError, "server is closing")+ }+ where+ drainThen stopServer = do+ atomically $ writeTVar draining True+ void $ forkIO $ do+ late <- case settingsGracefulShutdownTimeout settings of+ Nothing -> newTVarIO False+ Just sec -> registerDelay $ sec * 1000000+ atomically $+ (readTVar active >>= check . (== 0)) `orElse` (readTVar late >>= check)+ stopServer++-- | H3_NO_ERROR (RFC 9114 Sec 8.1): the connection is being closed and+-- nothing went wrong. What a stopping server tells its peers, and said in+-- the application protocol because the transport's own NO_ERROR is what a+-- connection whose application has finished normally says.+h3NoError :: ApplicationProtocolError+h3NoError = ApplicationProtocolError 0x100+#else+-- quic before 0.3.15 has no hook to install this on, and no way to stop a+-- server from out here at all.+stoppableBy _ _ quicsettings = quicsettings+#endif+ quicApp :: Settings+ -> Draining -> Application -> InternalInfo -> Connection -> IO ()-quicApp settings app ii conn = do+quicApp settings drn app ii conn = do info <- getConnectionInfo conn mccc <- clientCertificateChain conn let addr = remoteSockAddr info@@ -62,13 +132,22 @@ , H3.confTimeoutManager = timmgr } #endif+ server label = http2server label settings ii transport addr app case malpn of Nothing -> return ()- Just appProto -> do- let runX- | "h3" `BS.isPrefixOf` appProto = H3.run- | otherwise = HQ.run- label- | "h3" `BS.isPrefixOf` appProto = "Warp HTTP/3"- | otherwise = "Warp HQ"- runX conn conf $ http2server label settings ii transport addr app+ Just appProto+ | "h3" `BS.isPrefixOf` appProto ->+ -- Counted while it answers, so that stopping waits for it.+ E.bracket_ (counting 1) (counting (-1)) $+ runH3 conn conf $ server "Warp HTTP/3"+ -- HTTP/0.9 has no GOAWAY: it is closed when the server stops.+ | otherwise -> HQ.run conn conf $ server "Warp HQ"+ where+ counting n = atomically $ modifyTVar' (activeVar drn) (+ n)+#if MIN_VERSION_http3(0,1,10)+ -- GOAWAY once the server is stopping, then the requests taken are+ -- answered and the connection ends.+ runH3 = H3.runWith $ readTVar (drainingVar drn) >>= check+#else+ runH3 = H3.run+#endif
warp-quic.cabal view
@@ -1,6 +1,6 @@ cabal-version: >=1.10 name: warp-quic-version: 0.0.4+version: 0.0.5 license: BSD3 license-file: LICENSE maintainer: Kazu Yamamoto <kazu@iij.ad.jp>@@ -22,6 +22,7 @@ http3 <0.2, network, quic >=0.2 && <0.4,+ stm, tls >=0.8 && < 3, wai, warp >=3.4.4