packages feed

warp 3.4.15 → 3.4.16

raw patch · 22 files changed

+764/−175 lines, 22 filesdep +arraydep ~auto-updatedep ~basedep ~bsb-http-chunkedPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: array

Dependency ranges changed: auto-update, base, bsb-http-chunked, bytestring, case-insensitive, http-types, network, recv, stm, streaming-commons, time-manager, vault, wai

API changes (from Hackage documentation)

+ Network.Wai.Handler.Warp: getOnConnectionException :: Settings -> SockAddr -> SomeException -> IO ()
+ Network.Wai.Handler.Warp: setOnConnectionException :: (SockAddr -> SomeException -> IO ()) -> Settings -> Settings
+ Network.Wai.Handler.Warp.Internal: [settingsOnConnectionException] :: Settings -> Maybe (SockAddr -> SomeException -> IO ())
- Network.Wai.Handler.Warp.Internal: Settings :: Port -> HostPreference -> (Maybe Request -> SomeException -> IO ()) -> (SomeException -> Response) -> (SockAddr -> IO Bool) -> (SockAddr -> IO ()) -> Int -> Maybe Manager -> Int -> Int -> IO () -> (((forall a. () => IO a -> IO a) -> IO ()) -> IO ()) -> (Socket -> IO (Socket, SockAddr)) -> Bool -> (IO () -> IO ()) -> ByteString -> Maybe Int -> ProxyProtocol -> Int -> Bool -> (Request -> Status -> Maybe Integer -> IO ()) -> (Request -> ByteString -> Integer -> IO ()) -> Maybe Int -> Int -> Int -> Int -> Maybe ByteString -> Int -> Maybe Counter -> Maybe ServerState -> Settings
+ Network.Wai.Handler.Warp.Internal: Settings :: Port -> HostPreference -> (Maybe Request -> SomeException -> IO ()) -> Maybe (SockAddr -> SomeException -> IO ()) -> (SomeException -> Response) -> (SockAddr -> IO Bool) -> (SockAddr -> IO ()) -> Int -> Maybe Manager -> Int -> Int -> IO () -> (((forall a. () => IO a -> IO a) -> IO ()) -> IO ()) -> (Socket -> IO (Socket, SockAddr)) -> Bool -> (IO () -> IO ()) -> ByteString -> Maybe Int -> ProxyProtocol -> Int -> Bool -> (Request -> Status -> Maybe Integer -> IO ()) -> (Request -> ByteString -> Integer -> IO ()) -> Maybe Int -> Int -> Int -> Int -> Maybe ByteString -> Int -> Maybe Counter -> Maybe ServerState -> Settings

Files

ChangeLog.md view
@@ -1,5 +1,26 @@ # ChangeLog for warp +## 3.4.16++* Graceful shutdown no longer stops while a connection it accepted is+  unserved. The connection counter it waits on is now raised when the accept+  loop accepts a connection rather than when the thread serving it is+  scheduled, closing a window in which an accepted connection was invisible+  to the shutdown.+  [#1104](https://github.com/yesodweb/wai/pull/1104).+* Slight performance increase by not blocking on receiving a request if the+  socket already has bytes waiting. (using `receiveNoWait` from `recv-0.1.2`)+  [#1107](https://github.com/yesodweb/wai/pull/1107).+* Reviewed when to introduce memory barriers when handling `IORef`s.+  Documented most usage and introduced memory barriers in situations that might+  possibly be used in more than one thread.+  [#1112](https://github.com/yesodweb/wai/pull/1112).+* Add `setOnConnectionException` and `getOnConnectionException` to expose the+  peer for exceptions escaping connection workers, including TLS setup failures+  before a request exists. The existing exception observer remains the default.+  [#1114](https://github.com/yesodweb/wai/pull/1114)+  (fixes [#1113](https://github.com/yesodweb/wai/issues/1113))+ ## 3.4.15  * Support `103 Early Hints` over HTTP/2: the HTTP/2 handler installs
Network/Wai/Handler/Warp.hs view
@@ -52,6 +52,7 @@     setPort,     setHost,     setOnException,+    setOnConnectionException,     setOnExceptionResponse,     setOnOpen,     setOnClose,@@ -86,6 +87,7 @@     getOnOpen,     getOnClose,     getOnException,+    getOnConnectionException,     getGracefulShutdownTimeout,     getGracefulCloseTimeout1,     getGracefulCloseTimeout2,@@ -192,24 +194,39 @@  -- | Port to listen on. Default value: 3000 ----- Since 2.1.0+-- @since 2.1.0 setPort :: Port -> Settings -> Settings setPort x y = y{settingsPort = x}  -- | Interface to bind to. Default value: HostIPv4 ----- Since 2.1.0+-- @since 2.1.0 setHost :: HostPreference -> Settings -> Settings setHost x y = y{settingsHost = x}  -- | What to do with exceptions thrown by either the application or server. -- Default: 'defaultOnException' ----- Since 2.1.0+-- @since 2.1.0 setOnException     :: (Maybe Request -> SomeException -> IO ()) -> Settings -> Settings setOnException x y = y{settingsOnException = x} +-- | Handle exceptions escaping a connection worker, with the address supplied+-- by its connection source. This includes connection creation (such as a TLS+-- handshake) and cleanup failures, even when no 'Request' exists. For socket+-- listeners this is the accepted TCP peer, before any PROXY protocol rewriting.+--+-- When installed, this handler receives these exceptions instead of the+-- handler configured with 'setOnException'. Request-specific exceptions and+-- accept-loop failures still use that handler. By default, worker exceptions+-- go to that handler too, with 'Nothing' as its @Maybe Request@ argument+-- because no request context is available at this boundary.+--+-- @since 3.4.16+setOnConnectionException :: (SockAddr -> SomeException -> IO ()) -> Settings -> Settings+setOnConnectionException report settings = settings{settingsOnConnectionException = Just report}+ -- | A function to create a `Response` when an exception occurs. -- Default: 'defaultOnExceptionResponse' --@@ -223,7 +240,7 @@ -- > response500 :: Request -> SomeException -> Response -- > response500 req someEx = responseLBS status500 -- ... ----- Since 2.1.0+-- @since 2.1.0 setOnExceptionResponse :: (SomeException -> Response) -> Settings -> Settings setOnExceptionResponse x y = y{settingsOnExceptionResponse = x} @@ -231,13 +248,13 @@ -- connection is closed immediately. Otherwise, the connection is going on. -- Default: always returns 'True'. ----- Since 2.1.0+-- @since 2.1.0 setOnOpen :: (SockAddr -> IO Bool) -> Settings -> Settings setOnOpen x y = y{settingsOnOpen = x}  -- | What to do when a connection is closed. Default: do nothing. ----- Since 2.1.0+-- @since 2.1.0 setOnClose :: (SockAddr -> IO ()) -> Settings -> Settings setOnClose x y = y{settingsOnClose = x} @@ -249,14 +266,14 @@ -- -- Default value: 30 ----- Since 2.1.0+-- @since 2.1.0 setTimeout :: Int -> Settings -> Settings setTimeout x y = y{settingsTimeout = x}  -- | Use an existing timeout manager instead of spawning a new one. If used, -- 'settingsTimeout' is ignored. ----- Since 2.1.0+-- @since 2.1.0 setManager :: Manager -> Settings -> Settings setManager x y = y{settingsManager = Just x} @@ -272,7 +289,7 @@ -- -- Default value: 0, was previously 10 ----- Since 3.0.13+-- @since 3.0.13 setFdCacheDuration :: Int -> Settings -> Settings setFdCacheDuration x y = y{settingsFdCacheDuration = x} @@ -296,7 +313,7 @@ -- -- Default: do nothing. ----- Since 2.1.0+-- @since 2.1.0 setBeforeMainLoop :: IO () -> Settings -> Settings setBeforeMainLoop x y = y{settingsBeforeMainLoop = x} @@ -306,19 +323,19 @@ -- -- Default: False ----- Since 2.1.0+-- @since 2.1.0 setNoParsePath :: Bool -> Settings -> Settings setNoParsePath x y = y{settingsNoParsePath = x}  -- | Get the listening port. ----- Since 2.1.1+-- @since 2.1.1 getPort :: Settings -> Port getPort = settingsPort  -- | Get the interface to bind to. ----- Since 2.1.1+-- @since 2.1.1 getHost :: Settings -> HostPreference getHost = settingsHost @@ -334,9 +351,18 @@ getOnException :: Settings -> Maybe Request -> SomeException -> IO () getOnException = settingsOnException +-- | Get the handler installed with 'setOnConnectionException'.+-- If none was installed, the returned function ignores the peer address and+-- calls the handler configured with 'setOnException', passing 'Nothing' as+-- its @Maybe Request@ argument and forwarding the exception.+--+-- @since 3.4.16+getOnConnectionException :: Settings -> SockAddr -> SomeException -> IO ()+getOnConnectionException = onConnectionException+ -- | Get the graceful shutdown timeout ----- Since 3.2.8+-- @since 3.2.8 getGracefulShutdownTimeout :: Settings -> Maybe Int getGracefulShutdownTimeout = settingsGracefulShutdownTimeout @@ -370,7 +396,7 @@ -- -- Default: does not install any code. ----- Since 3.0.1+-- @since 3.0.1 setInstallShutdownHandler :: (IO () -> IO ()) -> Settings -> Settings setInstallShutdownHandler x y = y{settingsInstallShutdownHandler = x} @@ -379,7 +405,7 @@ --   If an empty string is set, the \"Server:\" header is not sent. --   This is true even if an application set one. ----- Since 3.0.2+-- @since 3.0.2 setServerName :: ByteString -> Settings -> Settings setServerName x y = y{settingsServerName = x} @@ -394,7 +420,7 @@ -- -- Default: 8192 bytes. ----- Since 3.0.3+-- @since 3.0.3 setMaximumBodyFlush :: Maybe Int -> Settings -> Settings setMaximumBodyFlush x y     | Just x' <- x, x' < 0 = error "setMaximumBodyFlush: must be positive"@@ -407,7 +433,7 @@ -- -- Default: void . forkIOWithUnmask ----- Since 3.0.4+-- @since 3.0.4 setFork     :: (((forall a. IO a -> IO a) -> IO ()) -> IO ()) -> Settings -> Settings setFork fork' s = s{settingsFork = fork'}@@ -419,13 +445,13 @@ -- -- Default: 'defaultAccept' ----- Since 3.3.24+-- @since 3.3.24 setAccept :: (Socket -> IO (Socket, SockAddr)) -> Settings -> Settings setAccept accept' s = s{settingsAccept = accept'}  -- | Do not use the PROXY protocol. ----- Since 3.0.5+-- @since 3.0.5 setProxyProtocolNone :: Settings -> Settings setProxyProtocolNone y = y{settingsProxyProtocol = ProxyProtocolNone} @@ -441,7 +467,7 @@ -- Only the human-readable header format (version 1) is supported. The binary -- header format (version 2) is /not/ supported. ----- Since 3.0.5+-- @since 3.0.5 setProxyProtocolRequired :: Settings -> Settings setProxyProtocolRequired y = y{settingsProxyProtocol = ProxyProtocolRequired} @@ -457,25 +483,25 @@ -- HTTP without the PROXY header, but proxied -- connections /do/ include the PROXY header. ----- Since 3.0.5+-- @since 3.0.5 setProxyProtocolOptional :: Settings -> Settings setProxyProtocolOptional y = y{settingsProxyProtocol = ProxyProtocolOptional}  -- | Size in bytes read to prevent Slowloris attacks. Default value: 2048 ----- Since 3.1.2+-- @since 3.1.2 setSlowlorisSize :: Int -> Settings -> Settings setSlowlorisSize x y = y{settingsSlowlorisSize = x}  -- | Disable HTTP2. ----- Since 3.1.7+-- @since 3.1.7 setHTTP2Disabled :: Settings -> Settings setHTTP2Disabled y = y{settingsHTTP2Enabled = False}  -- | Setting a log function. ----- Since 3.X.X+-- @since 3.X.X setLogger     :: (Request -> H.Status -> Maybe Integer -> IO ())     -- ^ request, status, maybe file-size@@ -501,7 +527,7 @@ -- 'setInstallShutdownHandler' for an example of how this could be done in -- response to a UNIX signal. ----- Since 3.2.8+-- @since 3.2.8 setGracefulShutdownTimeout     :: Maybe Int     -> Settings@@ -510,7 +536,7 @@  -- | Set the maximum header size that Warp will tolerate when using HTTP/1.x. ----- Since 3.3.8+-- @since 3.3.8 setMaxTotalHeaderLength :: Int -> Settings -> Settings setMaxTotalHeaderLength maxTotalHeaderLength settings =     settings@@ -519,13 +545,13 @@  -- | Setting the header value of Alternative Services (AltSvc:). ----- Since 3.3.11+-- @since 3.3.11 setAltSvc :: ByteString -> Settings -> Settings setAltSvc altsvc settings = settings{settingsAltSvc = Just altsvc}  -- | Set the maximum buffer size for sending `Builder` responses. ----- Since 3.3.22+-- @since 3.3.22 setMaxBuilderResponseBufferSize :: Int -> Settings -> Settings setMaxBuilderResponseBufferSize maxRspBufSize settings = settings{settingsMaxBuilderResponseBufferSize = maxRspBufSize} @@ -534,7 +560,7 @@ -- This is useful for cases where you partially consume a request body. For -- more information, see <https://github.com/yesodweb/wai/issues/351> ----- Since 3.0.10+-- @since 3.0.10 pauseTimeout :: Request -> IO () pauseTimeout = fromMaybe (return ()) . Vault.lookup pauseTimeoutKey . vault @@ -553,7 +579,7 @@ --   If this function is used an a Request generated by a WAI --   backend besides Warp, it also throws an 'IO' exception. ----- Since 3.1.10+-- @since 3.1.10 getFileInfo :: Request -> FilePath -> IO FileInfo getFileInfo =     fromMaybe (\_ -> throwIO (userError "getFileInfo"))@@ -564,14 +590,14 @@ --   FIN for HTTP/1.x. 0 means uses immediate close. --   Default: 0. ----- Since 3.3.5+-- @since 3.3.5 setGracefulCloseTimeout1 :: Int -> Settings -> Settings setGracefulCloseTimeout1 x y = y{settingsGracefulCloseTimeout1 = x}  -- | A timeout to limit the time (in milliseconds) waiting for --   FIN for HTTP/1.x. 0 means uses immediate close. ----- Since 3.3.5+-- @since 3.3.5 getGracefulCloseTimeout1 :: Settings -> Int getGracefulCloseTimeout1 = settingsGracefulCloseTimeout1 @@ -579,14 +605,14 @@ --   FIN for HTTP/2. 0 means uses immediate close. --   Default: 2000. ----- Since 3.3.5+-- @since 3.3.5 setGracefulCloseTimeout2 :: Int -> Settings -> Settings setGracefulCloseTimeout2 x y = y{settingsGracefulCloseTimeout2 = x}  -- | A timeout to limit the time (in milliseconds) waiting for --   FIN for HTTP/2. 0 means uses immediate close. ----- Since 3.3.5+-- @since 3.3.5 getGracefulCloseTimeout2 :: Settings -> Int getGracefulCloseTimeout2 = settingsGracefulCloseTimeout2 @@ -597,7 +623,7 @@ -- -- /DEPRECATED in favor of 'getServerState'/ ----- Since 3.4.11+-- @since 3.4.11 getOpenConnectionCounter :: Settings -> Maybe Counter getOpenConnectionCounter = settingsConnectionCounter @@ -607,14 +633,14 @@ -- -- See 'makeSettingsAndServerState' to create 'Settings' with a 'ServerState'. ----- Since 3.4.12+-- @since 3.4.12 getServerState :: Settings -> Maybe ServerState getServerState = settingsServerState  #ifdef MIN_VERSION_crypton_x509 -- | Getting information of client certificate. ----- Since 3.3.5+-- @since 3.3.5 clientCertificate :: Request -> Maybe CertificateChain clientCertificate = join . Vault.lookup getClientCertificateKey . vault #endif
Network/Wai/Handler/Warp/Conduit.hs view
@@ -41,6 +41,10 @@                 -- How many bytes will still remain to be sent downstream                 count' = count - toSend +            -- [WRITE_IOREF_NOTE]+            -- This doesn't need to be "atomic", since it is only used in+            -- 'recvRequest', which creates the 'Source' and doesn't fork it,+            -- so the 'IORef' is not shared outside of 'recvRequest'.             I.writeIORef ref count'              if count' > 0@@ -86,7 +90,7 @@     withLen len bs         | S.null bs = do             -- FIXME should this throw an exception if len > 0?-            I.writeIORef ref DoneChunking+            I.writeIORef ref DoneChunking -- [WRITE_IOREF_NOTE]             return S.empty         | otherwise =             case S.length bs `compare` fromIntegral len of@@ -98,7 +102,7 @@                     yield' x NeedLenNewline      yield' bs mlen = do-        I.writeIORef ref mlen+        I.writeIORef ref mlen -- [WRITE_IOREF_NOTE]         return bs      dropCRLF = do@@ -124,7 +128,7 @@     go (HaveLen 0) = do         -- Drop the final CRLF         dropCRLF-        I.writeIORef ref DoneChunking+        I.writeIORef ref DoneChunking -- [WRITE_IOREF_NOTE]         return S.empty     go (HaveLen len) = do         bs <- readSource src@@ -136,7 +140,7 @@         bs <- readSource src         if S.null bs             then do-                I.writeIORef ref $ assert False $ HaveLen 0+                I.writeIORef ref $ assert False $ HaveLen 0 -- [WRITE_IOREF_NOTE]                 return S.empty             else do                 (x, y) <-
Network/Wai/Handler/Warp/Counter.hs view
@@ -47,12 +47,12 @@  -- | Get the current count of open connections. ----- Since 3.4.11+-- @since 3.4.11 getCount :: Counter -> IO Int getCount (Counter var) = readTVarIO var  -- | Get the current count in an 'STM' transaction. ----- Since 3.4.13+-- @since 3.4.13 getCountSTM :: Counter -> STM Int getCountSTM (Counter tvar) = readTVar tvar
Network/Wai/Handler/Warp/FdCache.hs view
@@ -46,12 +46,13 @@ #ifdef WINDOWS withFdCache _ action = action getFdNothing #else-withFdCache 0 action = action getFdNothing-withFdCache duration action =-    bracket-        (initialize duration)-        terminate-        (action . getFd)+withFdCache duration action+    | duration <= 0 = action getFdNothing+    | otherwise =+        bracket+            (initialize duration)+            terminate+            (action . getFd)  ---------------------------------------------------------------- @@ -66,10 +67,10 @@ newActiveStatus = MutableStatus <$> newIORef Active  refresh :: MutableStatus -> Refresh-refresh (MutableStatus ref) = writeIORef ref Active+refresh (MutableStatus ref) = atomicWriteIORef ref Active  inactive :: MutableStatus -> IO ()-inactive (MutableStatus ref) = writeIORef ref Inactive+inactive (MutableStatus ref) = atomicWriteIORef ref Inactive  ---------------------------------------------------------------- @@ -146,13 +147,16 @@  -- | Getting 'Fd' and 'Refresh' from the mutable Fd cacher. getFd :: MutableFdCache -> FilePath -> IO (Maybe Fd, Refresh)-getFd mfc@(MutableFdCache reaper) path = look mfc path >>= get+getFd mfc@(MutableFdCache reaper) path = do+    mEnt <- look mfc path+    entryToResult <$> get mEnt   where+    entryToResult (FdEntry fd mst) = (Just fd, refresh mst)     get Nothing = do-        ent@(FdEntry fd mst) <- newFdEntry path+        ent <- newFdEntry path         reaperAdd reaper (path, ent)-        return (Just fd, refresh mst)-    get (Just (FdEntry fd mst)) = do+        pure ent+    get (Just ent@(FdEntry _ mst)) = do         refresh mst-        return (Just fd, refresh mst)+        pure ent #endif
Network/Wai/Handler/Warp/HTTP2.hs view
@@ -86,7 +86,7 @@  -- | Converting WAI application to the server type of http2 library. ----- Since 3.3.11+-- @since 3.3.11 http2server     :: String     -> S.Settings
Network/Wai/Handler/Warp/HTTP2/File.hs view
@@ -15,7 +15,7 @@  -- | 'PositionReadMaker' based on file descriptor cache. ----- Since 3.3.13+-- @since 3.3.13 pReadMaker :: InternalInfo -> PositionReadMaker pReadMaker ii path = do     (mfd, refresh) <- getFd ii path
Network/Wai/Handler/Warp/HTTP2/Request.hs view
@@ -97,12 +97,21 @@     (unparsedPath, query) = C8.break (== '?') $ fromJust (mPath <|> mAuth)     !path = H.extractPath unparsedPath     !rawPath = if S.settingsNoParsePath settings then unparsedPath else path+    -- We use an "atomic" function here, because we can't influence when it+    -- will be used.+    modifyDataKey f = atomicModifyIORef' ref $ \mOldKey ->+        let !mNewKey = f mOldKey+         in (mNewKey, ())     -- fixme: pauseTimeout. th is not available here.-    !vaultValue =+    -- Lazy on purpose (~ defeats -XStrict): most handlers never touch+    -- 'vault', so don't pay for the inserts unless somebody looks.+    ~vaultValue =         Vault.insert getFileInfoKey (getFileInfo ii)             . Vault.insert getHTTP2DataKey (readIORef ref)-            . Vault.insert setHTTP2DataKey (writeIORef ref)-            . Vault.insert modifyHTTP2DataKey (modifyIORef' ref)+            -- We use 'atomicWriteIORef' here, because we don't expect it+            -- to be used often, and it's use is out of our control.+            . Vault.insert setHTTP2DataKey (atomicWriteIORef ref)+            . Vault.insert modifyHTTP2DataKey modifyDataKey             . Vault.insert pauseTimeoutKey (T.pause th) #ifdef MIN_VERSION_crypton_x509             . Vault.insert getClientCertificateKey (getTransportClientCertificate transport)
Network/Wai/Handler/Warp/IO.hs view
@@ -5,23 +5,47 @@ import Data.ByteString.Builder (Builder) import Data.ByteString.Builder.Extra (Next (Chunk, Done, More), runBuilder) import Data.IORef (IORef, readIORef, writeIORef)+import Foreign.Ptr (plusPtr) import Network.Wai.Handler.Warp.Buffer import Network.Wai.Handler.Warp.Imports import Network.Wai.Handler.Warp.Types  toBufIOWith     :: Int -> IORef WriteBuffer -> (ByteString -> IO ()) -> Builder -> IO Integer-toBufIOWith maxRspBufSize writeBufferRef io builder = do+toBufIOWith = unsafeToBufIOWithOffset 0++-- | Like 'toBufIOWith' but the first @offset@ bytes of the write buffer+-- are assumed to be already filled (e.g. with a response header composed+-- directly into the buffer). They are flushed together with the first+-- batch of builder output and included in the returned total.+--+-- === WARNING: @offset@ MUST NOT exceed the current buffer size!!!+--+-- This function performs NO bounds checking on @offset@. The builder is+-- handed the pointer @buffer + offset@ with @bufSize - offset@ bytes of+-- claimed free space, so an oversized @offset@ points past the end of the+-- allocation and advertises negative capacity, i.e. out-of-bounds writes+-- and memory corruption. Every caller MUST verify+-- @offset < bufSize@ of the current write buffer first (see the+-- @hdrLen@ check in 'Network.Wai.Handler.Warp.Response.sendRsp').+unsafeToBufIOWithOffset+    :: Int+    -> Int+    -> IORef WriteBuffer+    -> (ByteString -> IO ())+    -> Builder+    -> IO Integer+unsafeToBufIOWithOffset offset0 maxRspBufSize writeBufferRef io builder = do     writeBuffer <- readIORef writeBufferRef-    loop writeBuffer firstWriter 0+    loop writeBuffer offset0 firstWriter 0   where     firstWriter = runBuilder builder-    loop writeBuffer writer bytesSent = do+    loop writeBuffer offset writer bytesSent = do         let buf = bufBuffer writeBuffer             size = bufSize writeBuffer-        (len, signal) <- writer buf size-        bufferIO buf len io-        let totalBytesSent = toInteger len + bytesSent+        (len, signal) <- writer (buf `plusPtr` offset) (size - offset)+        bufferIO buf (offset + len) io+        let totalBytesSent = toInteger (offset + len) + bytesSent         case signal of             Done -> return totalBytesSent             More minSize next@@ -42,10 +66,15 @@                     biggerWriteBuffer <- mask_ $ do                         bufFree writeBuffer                         biggerWriteBuffer <- createWriteBuffer minSize+                        -- This doesn't need to be "atomic", since these two+                        -- functions are only used in 'sendResponse', which is+                        -- ultimately only used in 'serveConnection', which does+                        -- not share nor fork the created 'Connection'.                         writeIORef writeBufferRef biggerWriteBuffer                         return biggerWriteBuffer-                    loop biggerWriteBuffer next totalBytesSent-                | otherwise -> loop writeBuffer next totalBytesSent+                    loop biggerWriteBuffer 0 next totalBytesSent+                | otherwise -> loop writeBuffer 0 next totalBytesSent             Chunk bs next -> do                 io bs-                loop writeBuffer next $ totalBytesSent + fromIntegral (B.length bs)+                loop writeBuffer 0 next $+                    totalBytesSent + fromIntegral (B.length bs)
Network/Wai/Handler/Warp/Request.hs view
@@ -88,7 +88,9 @@     -- body producing function which will never produce 100-continue     rbodyFlush <- timeoutBody remainingRef th rbody (return ())     let rawPath = if settingsNoParsePath settings then unparsedPath else path-        vaultValue =+        -- Lazy on purpose (~ defeats -XStrict): most handlers never touch+        -- 'vault', so don't pay for the inserts unless somebody looks.+        ~vaultValue =             Vault.insert pauseTimeoutKey (Timeout.pause th)                 . Vault.insert getFileInfoKey (getFileInfo ii) #ifdef MIN_VERSION_crypton_x509@@ -216,6 +218,10 @@             -- headers. Now we need to resume it to avoid a slowloris             -- attack during request body sending.             Timeout.resume timeoutHandle+            -- This doesn't need to be "atomic", since this is only used in+            -- 'recvRequest' to create the 'requestBody' function. And getting+            -- chunks of the request in a concurrent setting is asking for+            -- trouble anyway.             I.writeIORef isFirstRef False          bs <- rbody
Network/Wai/Handler/Warp/Response.hs view
@@ -44,7 +44,7 @@ import qualified Network.Wai.Handler.Warp.Date as D import Network.Wai.Handler.Warp.File import Network.Wai.Handler.Warp.Header-import Network.Wai.Handler.Warp.IO (toBufIOWith)+import Network.Wai.Handler.Warp.IO (toBufIOWith, unsafeToBufIOWithOffset) import Network.Wai.Handler.Warp.Imports import Network.Wai.Handler.Warp.ResponseHeader import Network.Wai.Handler.Warp.Settings@@ -289,22 +289,42 @@ ----------------------------------------------------------------  sendRsp conn _ th ver s hs rspidxhdr maxRspBufSize _ (RspBuilder body needsChunked) = do-    (header, hdrLen) <- composeHeaderBuilder ver s hs rspidxhdr needsChunked-    let hdrBdy-            | needsChunked =-                header-                    <> chunkedTransferEncoding body-                    <> chunkedTransferTerminator-            | otherwise = header <> body-        writeBufferRef = connWriteBuffer conn+    writeBuffer <- readIORef writeBufferRef     len <--        toBufIOWith-            maxRspBufSize-            writeBufferRef-            (\bs -> connSendAll conn bs >> T.tickle th)-            hdrBdy+        -- SAFETY: this check is what makes the unchecked writes below+        -- memory-safe, do not weaken it. composeHeaderPtr writes hdrLen+        -- bytes into the write buffer with no bounds checking of its own,+        -- and unsafeToBufIOWithOffset requires an offset within the+        -- buffer (it hands the builder buffer + offset with+        -- bufSize - offset bytes of claimed free space). If hdrLen could+        -- reach the buffer size, either write would run past the end of+        -- the allocation and corrupt memory, so oversized headers must+        -- take the composeHeaderBuilder fallback.+        if hdrLen < bufSize writeBuffer+            then do+                -- Compose the header directly into the connection write+                -- buffer and run the body builder right after it, saving+                -- a copy of the header bytes through an intermediate+                -- ByteString.+                _ <- composeHeaderPtr (bufBuffer writeBuffer) ver s hs'+                unsafeToBufIOWithOffset hdrLen maxRspBufSize writeBufferRef send bdy+            else do+                -- Huge headers: fall back to composing a separate header+                -- ByteString and letting the builder machinery copy it.+                (header, _) <- composeHeaderBuilder ver s hs rspidxhdr needsChunked+                toBufIOWith maxRspBufSize writeBufferRef send (header <> bdy)     --              small adjustment to only count the body     return (Just s, Just $ len - fromIntegral hdrLen)+  where+    hs'+        | needsChunked = addTransferEncoding rspidxhdr hs+        | otherwise = hs+    hdrLen = composeHeaderLength s hs'+    bdy+        | needsChunked = chunkedTransferEncoding body <> chunkedTransferTerminator+        | otherwise = body+    writeBufferRef = connWriteBuffer conn+    send bs = connSendAll conn bs >> T.tickle th  ---------------------------------------------------------------- 
Network/Wai/Handler/Warp/ResponseHeader.hs view
@@ -1,7 +1,11 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE OverloadedStrings #-} -module Network.Wai.Handler.Warp.ResponseHeader (composeHeader) where+module Network.Wai.Handler.Warp.ResponseHeader (+    composeHeader,+    composeHeaderPtr,+    composeHeaderLength,+) where  import qualified Data.ByteString as S import Data.ByteString.Internal (create)@@ -18,14 +22,32 @@ ----------------------------------------------------------------  composeHeader :: H.HttpVersion -> H.Status -> H.ResponseHeaders -> IO ByteString-composeHeader !httpversion !status !responseHeaders = create len $ \ptr -> do-    ptr1 <- copyStatus ptr httpversion status-    ptr2 <- copyHeaders ptr1 responseHeaders-    void $ copyCRLF ptr2+composeHeader !httpversion !status !responseHeaders =+    create len $ \ptr ->+        void $ composeHeaderPtr ptr httpversion status responseHeaders   where-    !len = 17 + slen + List.foldl' fieldLength 0 responseHeaders+    !len = composeHeaderLength status responseHeaders++-- | The exact number of bytes 'composeHeaderPtr' writes for this+-- status line and header list (including the final CRLF).+composeHeaderLength :: H.Status -> H.ResponseHeaders -> Int+composeHeaderLength !status !responseHeaders =+    17 + slen + List.foldl' fieldLength 0 responseHeaders+  where     fieldLength !l (!k, !v) = l + S.length (CI.original k) + S.length v + 4     !slen = S.length $ H.statusMessage status++-- | Compose the response header directly into the given buffer,+-- returning the number of bytes written. The buffer must have room+-- for at least 'composeHeaderLength' bytes.+composeHeaderPtr+    :: Ptr Word8 -> H.HttpVersion -> H.Status -> H.ResponseHeaders -> IO Int+{-# INLINE composeHeaderPtr #-}+composeHeaderPtr !ptr !httpversion !status !responseHeaders = do+    ptr1 <- copyStatus ptr httpversion status+    ptr2 <- copyHeaders ptr1 responseHeaders+    ptr3 <- copyCRLF ptr2+    return $! ptr3 `minusPtr` ptr  httpVer11 :: ByteString httpVer11 = "HTTP/1.1 "
Network/Wai/Handler/Warp/Run.hs view
@@ -21,7 +21,7 @@ import qualified Control.Exception as E import qualified Data.ByteString as S import Data.Functor (($>))-import Data.IORef (newIORef, readIORef, IORef, writeIORef)+import Data.IORef (IORef, newIORef, readIORef, writeIORef) import Data.Streaming.Network (bindPortTCP) import Foreign.C.Error (Errno (..), eCONNABORTED, eMFILE) import GHC.Conc.Sync (labelThread, myThreadId)@@ -63,7 +63,7 @@ import Network.Wai.Handler.Warp.Imports hiding (readInt) import Network.Wai.Handler.Warp.SendFile (sendFile) import Network.Wai.Handler.Warp.Settings-import Network.Wai.Handler.Warp.ShuttingDown (writeShuttingDown)+import Network.Wai.Handler.Warp.ShuttingDown (readShuttingDown, writeShuttingDown) import Network.Wai.Handler.Warp.Types  -- | Creating 'Connection' for plain HTTP based on a given socket.@@ -91,7 +91,7 @@                         if h2                             then settingsGracefulCloseTimeout2 set                             else settingsGracefulCloseTimeout1 set-                if tm == 0+                if tm <= 0                     then close s                     else gracefulClose s tm `E.catch` throughAsync (return ()) #else@@ -126,9 +126,7 @@             hook             headers -    sendall = sendAll' s--    sendAll' sock bs =+    sendall bs =         E.handleJust             ( \e ->                 if ioeGetErrorType e == ResourceVanished@@ -136,7 +134,7 @@                     else Nothing             )             E.throwIO-            $ Sock.sendAll sock bs+            $ Sock.sendAll s bs  -- | Create a 'Recv' using 'Network.Socket.BufferPool.Recv.receive', but make -- it non-blocking with 'waitReadSocketSTM' /AND/ cut off receiving any bytes@@ -144,6 +142,18 @@ -- actively using this 'Socket'. makeGracefulRecv :: Socket -> BufferPool -> ServerState -> TVar Int -> Recv makeGracefulRecv sock pool ss appsInProgress = do+    tryFastPath <- not <$> readShuttingDown (serverShuttingDown ss)+    if tryFastPath then do+        mbs <- receiveNoWait sock pool+        case mbs of+          Just bs -> return bs+          Nothing -> slowPath+      else slowPath+  where+    slowPath = makeGracefulRecvSlow sock pool ss appsInProgress++makeGracefulRecvSlow :: Socket -> BufferPool -> ServerState -> TVar Int -> Recv+makeGracefulRecvSlow sock pool ss appsInProgress = do     sockWait <- #if !WINDOWS && MIN_VERSION_network(3,2,2)         waitReadSocketSTM sock@@ -174,7 +184,7 @@ -- environment variable. Uses the 'Port' given when the variable is unset. -- This calls 'runSettings' with 'defaultSettings'. ----- Since 3.0.9+-- @since 3.0.9 runEnv :: Port -> Application -> IO () runEnv p app = do     mp <- lookupEnv "PORT"@@ -235,7 +245,7 @@ -- This allows the expensive computations to be performed -- in a separate worker thread instead of the main server loop. ----- Since 1.3.5+-- @since 1.3.5 runSettingsConnection     :: Settings -> IO (Connection, SockAddr) -> Application -> IO () runSettingsConnection set getConn app = runSettingsConnectionMaker set getConnMaker app@@ -260,7 +270,7 @@ -- The connection maker can return a connection of either plain HTTP -- or HTTP over TLS. ----- Since 2.1.4+-- @since 2.1.4 runSettingsConnectionMakerSecure     :: Settings -> IO (IO (Connection, Transport), SockAddr) -> Application -> IO () runSettingsConnectionMakerSecure oldSettings getConnMaker app = do@@ -272,7 +282,7 @@  -- | Running an action with internal info. ----- Since 3.3.11+-- @since 3.3.11 withII :: Settings -> (InternalInfo -> IO a) -> IO a withII set action =     withTimeoutManager $ \tm ->@@ -398,29 +408,37 @@     -> Counter     -> InternalInfo     -> IO ()-fork set mkConn addr app counter ii = settingsFork set $ \unmask -> do-    tid <- myThreadId-    labelThread tid "Warp just forked"-    -- Call the user-supplied on exception code if any-    -- exceptions are thrown.-    ---    -- Intentionally using Control.Exception.handle, since we want to-    -- catch all exceptions and avoid them from propagating, even-    -- async exceptions. See:-    -- https://github.com/yesodweb/wai/issues/850-    E.handle (settingsOnException set Nothing) $-        -- Run the connection maker to get a new connection, and ensure-        -- that the connection is closed. If the mkConn call throws an-        -- exception, we will leak the connection. If the mkConn call is-        -- vulnerable to attacks (e.g., Slowloris), we do nothing to-        -- protect the server. It is therefore vital that mkConn is well-        -- vetted.-        ---        -- We grab the connection before registering timeouts since the-        -- timeouts will be useless during connection creation, due to the-        -- fact that async exceptions are still masked.-        E.bracket mkConn cleanUp (serve unmask)+fork set mkConn addr app counter ii = do+    -- Count the connection here rather than in the thread below.  The+    -- accept loop does not wait for that thread to be scheduled, so+    -- counting there leaves a window in which the connection is accepted+    -- and not counted, and 'gracefulShutdown' waits on this counter.+    increase counter+    settingsFork set $ \unmask -> runConnection unmask `E.finally` decrease counter   where+    runConnection unmask = do+        tid <- myThreadId+        labelThread tid "Warp just forked"+        -- Call the user-supplied on exception code if any+        -- exceptions are thrown.+        --+        -- Intentionally using Control.Exception.handle, since we want to+        -- catch all exceptions and avoid them from propagating, even+        -- async exceptions. See:+        -- https://github.com/yesodweb/wai/issues/850+        E.handle (onConnectionException set addr) $+            -- Run the connection maker to get a new connection, and ensure+            -- that the connection is closed. If the mkConn call throws an+            -- exception, we will leak the connection. If the mkConn call is+            -- vulnerable to attacks (e.g., Slowloris), we do nothing to+            -- protect the server. It is therefore vital that mkConn is well+            -- vetted.+            --+            -- We grab the connection before registering timeouts since the+            -- timeouts will be useless during connection creation, due to the+            -- fact that async exceptions are still masked.+            E.bracket mkConn cleanUp (serve unmask)+     cleanUp (conn, _) =         connClose conn `E.finally` do             writeBuffer <- readIORef $ connWriteBuffer conn@@ -442,8 +460,8 @@                 -- above ensures the connection is closed.                 when goingon $ serveConnection conn ii th addr transport set app -    onOpen adr = increase counter >> settingsOnOpen set adr-    onClose adr _ = decrease counter >> settingsOnClose set adr+    onOpen adr = settingsOnOpen set adr+    onClose adr _ = settingsOnClose set adr  serveConnection     :: Connection@@ -531,8 +549,11 @@ initFdExhaustionRef :: IO (IORef FdExhaustion) initFdExhaustionRef = newIORef NoFdIssue +-- [FD_EXHAUSTION]+-- No need for "atomic" variants, since this is only used in a tight loop in+-- 'acceptConnection'. resetFdExhaustion :: IORef FdExhaustion -> IO () resetFdExhaustion = flip writeIORef NoFdIssue  setFdExhaustion :: IORef FdExhaustion -> IO ()-setFdExhaustion = flip writeIORef FdExhausted+setFdExhaustion = flip writeIORef FdExhausted -- [FD_EXHAUSTION]
Network/Wai/Handler/Warp/SendFile.hs view
@@ -38,7 +38,7 @@ --   This makes use of the file descriptor cache. --   For other OSes, this is identical to 'readSendFile'. ----- Since: 3.1.0+-- @since 3.1.0 sendFile :: Socket -> Buffer -> BufSize -> (ByteString -> IO ()) -> SendFile #ifdef SENDFILEFD sendFile s _ _ _ fid off len act hdr = case mfid of@@ -88,7 +88,7 @@ --   This makes use of the file descriptor cache. --   For Windows, this is emulated by 'Handle'. ----- Since: 3.1.0+-- @since 3.1.0 #ifdef WINDOWS readSendFile :: Buffer -> BufSize -> (ByteString -> IO ()) -> SendFile readSendFile buf siz send fid off0 len0 hook headers = do
Network/Wai/Handler/Warp/Settings.hs view
@@ -44,6 +44,16 @@ import qualified Paths_warp #endif +-- | Report a worker exception with the address captured for that connection.+-- An optional callback preserves the existing exception observer's meaning:+-- setting the ordinary observer later still changes the default fallback.+-- No shared peer state or synthetic Request is needed (#1113).+onConnectionException :: Settings -> SockAddr -> SomeException -> IO ()+onConnectionException settings address =+    case settingsOnConnectionException settings of+        Just report -> report address+        Nothing -> settingsOnException settings Nothing+ -- | Various Warp server settings. This is purposely kept as an abstract data -- type so that new settings can be added without breaking backwards -- compatibility. In order to create a 'Settings' value, use 'defaultSettings'@@ -57,12 +67,15 @@     -- ^ Default value: HostIPv4     , settingsOnException :: Maybe Request -> SomeException -> IO ()     -- ^ What to do with exceptions thrown by either the application or server. Default: ignore server-generated exceptions (see 'InvalidRequest') and print application-generated applications to stderr.+    , settingsOnConnectionException :: Maybe (SockAddr -> SomeException -> IO ())+    -- ^ Optional observer for exceptions escaping a connection worker. Nothing+    -- delegates to settingsOnException with no request, preserving its defaults.     , settingsOnExceptionResponse :: SomeException -> Response     -- ^ A function to create `Response` when an exception occurs.     --     -- Default: 500, text/plain, \"Something went wrong\"     ---    -- Since 2.0.3+    -- @since 2.0.3     , settingsOnOpen :: SockAddr -> IO Bool     -- ^ What to do when a connection is open. When 'False' is returned, the connection is closed immediately. Otherwise, the connection is going on. Default: always returns 'True'.     , settingsOnClose :: SockAddr -> IO ()@@ -82,7 +95,7 @@     --     -- Default: do nothing.     ---    -- Since 1.3.6+    -- @since 1.3.6     , settingsFork :: ((forall a. IO a -> IO a) -> IO ()) -> IO ()     -- ^ Code to fork a new thread to accept a connection.     --@@ -91,7 +104,7 @@     --     -- Default: 'defaultFork'     ---    -- Since 3.0.4+    -- @since 3.0.4     , settingsAccept :: Socket -> IO (Socket, SockAddr)     -- ^ Code to accept a new connection.     --@@ -100,7 +113,7 @@     --     -- Default: 'defaultAccept'     ---    -- Since 3.3.24+    -- @since 3.3.24     , settingsNoParsePath :: Bool     -- ^ Perform no parsing on the rawPathInfo.     --@@ -108,7 +121,7 @@     --     -- Default: False     ---    -- Since 2.0.3+    -- @since 2.0.3     , settingsInstallShutdownHandler :: IO () -> IO ()     -- ^ An action to install a handler (e.g. Unix signal handler)     -- to close a listen socket.@@ -116,27 +129,27 @@     --     -- Default: no action     ---    -- Since 3.0.1+    -- @since 3.0.1     , settingsServerName :: ByteString     -- ^ Default server name if application does not set one.     ---    -- Since 3.0.2+    -- @since 3.0.2     , settingsMaximumBodyFlush :: Maybe Int     -- ^ See @setMaximumBodyFlush@.     ---    -- Since 3.0.3+    -- @since 3.0.3     , settingsProxyProtocol :: ProxyProtocol     -- ^ Specify usage of the PROXY protocol.     ---    -- Since 3.0.5+    -- @since 3.0.5     , settingsSlowlorisSize :: Int     -- ^ Size of bytes read to prevent Slowloris protection. Default value: 2048     ---    -- Since 3.1.2+    -- @since 3.1.2     , settingsHTTP2Enabled :: Bool     -- ^ Whether to enable HTTP2 ALPN/upgrades. Default: True     ---    -- Since 3.1.7+    -- @since 3.1.7     , settingsLogger :: Request -> H.Status -> Maybe Integer -> IO ()     -- ^ A log function. Default: no action.     --@@ -146,38 +159,38 @@     -- /after all the headers have been sent. This is 'Nothing' when/     -- /'responseRaw' is used. (e.g. when using websockets)/     ---    -- Since 3.1.10+    -- @since 3.1.10     , settingsServerPushLogger :: Request -> ByteString -> Integer -> IO ()     -- ^ A HTTP/2 server push log function. Default: no action.     ---    -- Since 3.2.7+    -- @since 3.2.7     , settingsGracefulShutdownTimeout :: Maybe Int     -- ^ An optional timeout to limit the time (in seconds) waiting for     -- a graceful shutdown of the web server.     ---    -- Since 3.2.8+    -- @since 3.2.8     , settingsGracefulCloseTimeout1 :: Int     -- ^ A timeout to limit the time (in milliseconds) waiting for     -- FIN for HTTP/1.x. 0 means uses immediate close.     -- Default: 0.     ---    -- Since 3.3.5+    -- @since 3.3.5     , settingsGracefulCloseTimeout2 :: Int     -- ^ A timeout to limit the time (in milliseconds) waiting for     -- FIN for HTTP/2. 0 means uses immediate close.     -- Default: 2000.     ---    -- Since 3.3.5+    -- @since 3.3.5     , settingsMaxTotalHeaderLength :: Int     -- ^ Determines the maximum header size that Warp will tolerate when using HTTP/1.x.     ---    -- Since 3.3.8+    -- @since 3.3.8     , settingsAltSvc :: Maybe ByteString     -- ^ Specify the header value of Alternative Services (AltSvc:).     --     -- Default: Nothing     ---    -- Since 3.3.11+    -- @since 3.3.11     , settingsMaxBuilderResponseBufferSize :: Int     -- ^ Determines the maxium buffer size when sending `Builder` responses     -- (See `responseBuilder`).@@ -191,7 +204,7 @@     --     -- Default: 1049_000_000 = 1 MiB.     ---    -- Since 3.3.22+    -- @since 3.3.22     , settingsConnectionCounter :: Maybe Counter     -- ^ A counter for tracking open connections.     -- Use 'makeSettingsAndCounter' to create settings with a counter,@@ -201,7 +214,7 @@     --     -- /DEPRECATED in favor of 'settingsServerState'/     ---    -- Since 3.4.11+    -- @since 3.4.11     , settingsServerState :: Maybe ServerState     -- ^ Internal read-only server state.     -- Use 'makeSettingsAndServerState' to gain access to the state of the server.@@ -210,7 +223,7 @@     --     -- Default: 'Nothing' (warp creates its own internal state)     ---    -- Since 3.4.13+    -- @since 3.4.13     }  -- | Specify usage of the PROXY protocol.@@ -224,7 +237,7 @@  -- | Internal read-only state of the server ----- Since 3.4.13+-- @since 3.4.13 data ServerState = ServerState     { serverConnectionCounter :: Counter     , serverShuttingDown :: ShuttingDown@@ -238,7 +251,7 @@ -- This makes it idempotent if care is taken that the @oldSettings@ -- are not used after using this function. ----- Since 3.4.13+-- @since 3.4.13 makeServerState :: Settings -> IO (ServerState, Settings) makeServerState oldSettings =     case settingsServerState oldSettings of@@ -256,7 +269,7 @@  -- | Initialize a 'ServerState' ----- Since 3.4.13+-- @since 3.4.13 newServerState :: IO ServerState newServerState = do     counter <- newCounter@@ -269,13 +282,17 @@  -- | Get the currently open connections of the server. ----- Since 3.4.13+-- Connections are considered "open" the moment they are accepted by the socket.+--+-- @since 3.4.13 currentOpenConnections :: ServerState -> IO Int currentOpenConnections = getCount . serverConnectionCounter  -- | Get the currently open connections of the server in an 'STM' transaction. ----- Since 3.4.13+-- Connections are considered "open" the moment they are accepted by the socket.+--+-- @since 3.4.13 currentOpenConnectionsSTM :: ServerState -> STM Int currentOpenConnectionsSTM = getCountSTM . serverConnectionCounter @@ -284,7 +301,7 @@ -- > False: Server is not shutting down -- > True:  Server is shutting down or has shut down. ----- Since 3.4.13+-- @since 3.4.13 currentShuttingDownState :: ServerState -> IO Bool currentShuttingDownState = readShuttingDown . serverShuttingDown @@ -295,7 +312,7 @@ -- > False: Server is not shutting down -- > True:  Server is shutting down or has shut down. ----- Since 3.4.13+-- @since 3.4.13 currentShuttingDownStateSTM :: ServerState -> STM Bool currentShuttingDownStateSTM = readShuttingDownSTM . serverShuttingDown @@ -307,6 +324,7 @@         { settingsPort = 3000         , settingsHost = "*4"         , settingsOnException = defaultOnException+        , settingsOnConnectionException = Nothing         , settingsOnExceptionResponse = defaultOnExceptionResponse         , settingsOnOpen = const $ return True         , settingsOnClose = const $ return ()@@ -341,7 +359,7 @@ -- -- /DEPRECATED in favor of 'makeSettingsAndServerState'/ ----- Since 3.4.11+-- @since 3.4.11 makeSettingsAndCounter :: IO (Counter, Settings) makeSettingsAndCounter = do     (serverState, settings) <- makeSettingsAndServerState@@ -351,7 +369,7 @@ -- Use functions like 'currentOpenConnections' and 'currentShuttingDownState' -- to gain insight into the state of the server. ----- Since 3.4.13+-- @since 3.4.13 makeSettingsAndServerState :: IO (ServerState, Settings) makeSettingsAndServerState = makeServerState defaultSettings @@ -359,7 +377,7 @@ -- exception should be shown or not. The goal is to hide exceptions which occur -- under the normal course of the web server running. ----- Since 2.1.3+-- @since 2.1.3 defaultShouldDisplayException :: SomeException -> Bool defaultShouldDisplayException se     | Just (_ :: InvalidRequest) <- fromException se = False@@ -372,7 +390,7 @@ -- | Printing an exception to standard error --   if `defaultShouldDisplayException` returns `True`. ----- Since: 3.1.0+-- @since: 3.1.0 defaultOnException :: Maybe Request -> SomeException -> IO () defaultOnException _ e =     when (defaultShouldDisplayException e) $@@ -382,10 +400,10 @@  -- | Sending 400 for bad requests. --   Sending 500 for internal server errors.--- Since: 3.1.0+-- @since: 3.1.0 --   Sending 413 for too large payload. --   Sending 431 for too large headers.--- Since 3.2.27+-- @since 3.2.27 defaultOnExceptionResponse :: SomeException -> Response defaultOnExceptionResponse e     | isAsyncException e = throw e@@ -413,7 +431,7 @@ -- | Exception handler for the debugging purpose. --   500, text/plain, a showed exception. ----- Since: 2.0.3.2+-- @since: 2.0.3.2 exceptionResponseForDebug :: SomeException -> Response exceptionResponseForDebug e =     responseBuilder@@ -423,7 +441,7 @@  -- | Similar to @forkIOWithUnmask@, but does not set up the default exception handler. ----- Since Warp will always install its own exception handler in forked threads, this provides+-- @since Warp will always install its own exception handler in forked threads, this provides -- a minor optimization. -- -- For inspiration of this function, see @rawForkIO@ in the @async@ package.
Network/Wai/Handler/Warp/Types.hs view
@@ -6,7 +6,7 @@ import Control.Concurrent.STM (TVar) import qualified Control.Exception as E import qualified Data.ByteString as S-import Data.IORef (IORef, newIORef, readIORef, writeIORef)+import Data.IORef (IORef, writeIORef, newIORef, readIORef) #ifdef MIN_VERSION_crypton_x509 import Data.X509 #endif@@ -80,7 +80,7 @@ --   On Unix, a file descriptor would be specified to make use of --   the file descriptor cache. ----- Since: 3.1.0+-- @since 3.1.0 data FileId = FileId     { fileIdPath :: FilePath     , fileIdFd :: Maybe Fd@@ -88,7 +88,7 @@  -- |  fileid, offset, length, hook action, HTTP headers ----- Since: 3.1.0+-- @since 3.1.0 type SendFile = FileId -> Integer -> Integer -> IO () -> [ByteString] -> IO ()  -- | A write buffer of a specified size@@ -137,9 +137,12 @@     -- @since 3.4.13     } +-- This function isn't used nor exported... getConnHTTP2 :: Connection -> IO Bool getConnHTTP2 = readIORef . connHTTP2 +-- This doesn't need to be "atomic", since it is only really used for+-- determining how long to wait before closing the socket in 'socketConnection'. setConnHTTP2 :: Connection -> Bool -> IO () setConnHTTP2 = writeIORef . connHTTP2 @@ -155,6 +158,8 @@ ----------------------------------------------------------------  -- | Type for input streaming.+--+-- /Caveat: a 'Source' is meant to be used in one thread only./ data Source = Source !(IORef ByteString) !(IO ByteString)  mkSource :: IO ByteString -> IO Source@@ -162,6 +167,7 @@     ref <- newIORef S.empty     return $! Source ref func +-- | Caveats from 'Source' apply. readSource :: Source -> IO ByteString readSource (Source ref func) = do     bs <- readIORef ref@@ -172,9 +178,12 @@             return bs  -- | Read from a Source, ignoring any leftovers.+--+-- /Caveats from 'Source' apply./ readSource' :: Source -> IO ByteString readSource' (Source _ func) = func +-- | Caveats from 'Source' apply. leftoverSource :: Source -> ByteString -> IO () leftoverSource (Source ref _) = writeIORef ref 
+ bench/ResponseBench.hs view
@@ -0,0 +1,124 @@+{-# LANGUAGE OverloadedStrings #-}++-- | End-to-end benchmark of the response path: everything 'sendResponse'+-- does per response except the actual socket write (the Connection is a+-- sink). Covers header sanitization, indexing, Server/Date insertion,+-- header composition, chunking, buffer management and timeout handling.+module Main (main) where++import Control.Concurrent.STM (newTVarIO)+import Control.Monad (replicateM_)+import Criterion.Main+import Data.ByteString.Builder (byteString)+import Data.IORef (newIORef)+import qualified Network.HTTP.Types as H+import qualified Network.HTTP.Types.Header as H+import Network.Socket (SockAddr (..))+import Network.Wai (defaultRequest)+import Network.Wai.Internal (Request (..), Response (..))+import qualified System.TimeManager as T++import Network.Wai.Handler.Warp.Buffer (createWriteBuffer)+import Network.Wai.Handler.Warp.Header+import Network.Wai.Handler.Warp.Response (sendResponse)+import Network.Wai.Handler.Warp.ResponseHeader (composeHeader)+import Network.Wai.Handler.Warp.Settings (defaultSettings)+import Network.Wai.Handler.Warp.Types++main :: IO ()+main = do+    writeBuf <- createWriteBuffer 16384 >>= newIORef+    http2Ref <- newIORef False+    apps <- newTVarIO (0 :: Int)+    let conn =+            Connection+                { connSendMany = \_ -> return ()+                , connSendAll = \_ -> return ()+                , connSendFile = \_ _ _ _ _ -> return ()+                , connClose = return ()+                , connRecv = return ""+                , connRecvBuf = \_ _ -> return True+                , connWriteBuffer = writeBuf+                , connHTTP2 = http2Ref+                , connMySockAddr = SockAddrInet 0 0+                , connAppsInProgress = apps+                }+    mgr <- T.initialize 30000000+    th <- T.register mgr (return ())+    let ii =+            InternalInfo+                { timeoutManager = mgr+                , getDate = return "Fri, 18 Jul 2026 12:00:00 GMT"+                , getFd = \_ -> return (Nothing, return ())+                , getFileInfo = \_ -> ioError (userError "no file info in bench")+                }+        req = defaultRequest{httpVersion = H.http11, requestMethod = H.methodGet}+        reqidxhdr = indexRequestHeader reqHdrs+        send = sendResponse defaultSettings conn ii th req reqidxhdr (return "")+    defaultMain+        [ bgroup+            "sendResponse"+            [ bench "builder 4 headers content-length" $ whnfIO $ send (rspB hdrs4)+            , bench "builder 3 headers chunked" $ whnfIO $ send (rspB hdrs3NoCL)+            , bench "builder 20 headers content-length" $ whnfIO $ send (rspB hdrs20)+            , bench "no body 204" $ whnfIO $ send rsp204+            , bench "stream 64 fragments" $ whnfIO $ send (rspS 64)+            ]+        , bgroup+            "headers"+            [ bench "composeHeader 5 headers" $+                whnfIO $+                    composeHeader H.http11 H.status200 hdrs5+            , bench "indexRequestHeader" $ whnf indexRequestHeader reqHdrs+            , bench "indexResponseHeader" $ whnf indexResponseHeader hdrs5+            ]+        ]+  where+    body = byteString "Hello, World!"+    rspB hs = ResponseBuilder H.status200 hs body+    -- One fragment per write/flush pair, the shape an SSE-style body has.+    rspS n = ResponseStream H.status200 hdrs3NoCL $ \write flush ->+        replicateM_ n (write body >> flush)+    rsp204 = ResponseBuilder H.status204 [] mempty+    reqHdrs =+        [ (H.hHost, "127.0.0.1:3011")+        , (H.hUserAgent, "wrk/4.2.0")+        , (H.hAccept, "*/*")+        , ("Accept-Encoding", "gzip, deflate")+        , (H.hConnection, "keep-alive")+        ]+    hdrs4 =+        [ (H.hContentType, "text/plain; charset=utf-8")+        , (H.hContentLength, "13")+        , (H.hCacheControl, "no-cache")+        , ("X-Request-Id", "0123456789abcdef")+        ]+    hdrs3NoCL =+        [ (H.hContentType, "text/plain; charset=utf-8")+        , (H.hCacheControl, "no-cache")+        , ("X-Request-Id", "0123456789abcdef")+        ]+    -- what composeHeader sees after warp added Server and Date+    hdrs5 =+        (H.hServer, "Warp/3.4.15")+            : (H.hDate, "Fri, 18 Jul 2026 12:00:00 GMT")+            : hdrs3NoCL+    hdrs20 =+        hdrs4+            ++ [ (H.hCacheControl, "private, max-age=0")+               , ("ETag", "\"33a64df551425fcc55e4d42a148795d9f25f89d4\"")+               , (H.hLastModified, "Wed, 21 Oct 2015 07:28:00 GMT")+               , ("X-Frame-Options", "SAMEORIGIN")+               , ("X-Content-Type-Options", "nosniff")+               , ("X-XSS-Protection", "1; mode=block")+               , ("Strict-Transport-Security", "max-age=31536000; includeSubDomains")+               , ("Content-Security-Policy", "default-src 'self'")+               , ("Referrer-Policy", "strict-origin-when-cross-origin")+               , ("Access-Control-Allow-Origin", "*")+               , ("Vary", "Accept-Encoding")+               , ("Set-Cookie", "session=abc123; Path=/; HttpOnly; Secure")+               , ("X-Runtime", "0.012345")+               , ("X-Served-By", "cache-lhr-1234")+               , ("Age", "0")+               , ("Via", "1.1 varnish")+               ]
+ test/ConnectionExceptionSpec.hs view
@@ -0,0 +1,130 @@+{-# LANGUAGE OverloadedStrings #-}++module ConnectionExceptionSpec (spec) where++import Control.Concurrent (Chan, newChan, readChan, writeChan, newEmptyMVar, putMVar, takeMVar, tryPutMVar)+import Control.Concurrent.Async (link, withAsync)+import Control.Exception (Exception, SomeException, bracket, finally, fromException, throwIO, toException)+import Control.Monad (forM_, replicateM, void)+import Data.IORef (newIORef, readIORef, writeIORef, modifyIORef')+import Data.Maybe (isJust)+import qualified Data.Streaming.Network as N+import Network.HTTP.Types (internalServerError500)+import Network.Socket (SockAddr (SockAddrInet), close, tupleToHostAddress)+import Network.Wai (remoteHost)+import Network.Wai.Handler.Warp+import Network.Wai.Handler.Warp.Internal (runSettingsConnectionMakerSecure)+import System.Timeout (timeout)+import Test.Hspec++import HTTP (responseStatus, sendGET)++-- Regression for https://github.com/yesodweb/wai/issues/1113. A connection+-- maker can fail before any Request exists, but Warp already owns its peer.+data ConnectionFailure = ConnectionFailure Int deriving (Show)+instance Exception ConnectionFailure++type Observation = (Int, Maybe SockAddr)++spec :: Spec+spec = describe "connection exception peer" $ do+    it "preserves the legacy observer when no peer observer is installed" $ do+        events <- newChan+        makers <- newChan+        let settings = setOnException (\request -> record events (remoteHost <$> request)) defaultSettings+        withAsync (runSettingsConnectionMakerSecure settings (readChan makers) unusedApplication) $ \server -> do+            link server+            writeChan makers (throwIO (ConnectionFailure 1), peer 100)+            timeout 2000000 (readChan events) `shouldReturn` Just (1, Nothing)++    it "gets the current legacy observer as the default connection observer" $ do+        events <- newChan+        let settings = setOnException (\request -> record events (remoteHost <$> request)) defaultSettings+        getOnConnectionException settings (peer 110) (toException (ConnectionFailure 2))+        timeout 2000000 (readChan events) `shouldReturn` Just (2, Nothing)++    it "uses only the connection observer regardless of setter order" $+        forM_ [False, True] $ \legacyLast -> do+            calls <- newIORef ([] :: [String])+            let legacy _ _ = modifyIORef' calls (++ ["legacy"])+                connection _ _ = modifyIORef' calls (++ ["connection"])+                settings = if legacyLast+                    then setOnException legacy $ setOnConnectionException connection defaultSettings+                    else setOnConnectionException connection $ setOnException legacy defaultSettings+            getOnConnectionException settings (peer 120) (toException (ConnectionFailure 3))+            readIORef calls `shouldReturn` ["connection"]++    it "keeps accept failures on the legacy observer because no peer was obtained" $ do+        calls <- newIORef ([] :: [(String, Bool)])+        let legacy request _ = modifyIORef' calls (++ [("legacy", isJust request)])+            connection _ _ = modifyIORef' calls (++ [("connection", False)])+            settings = setOnException legacy $ setOnConnectionException connection defaultSettings+        runSettingsConnectionMakerSecure settings (ioError (userError "accept failed")) unusedApplication+        readIORef calls `shouldReturn` [("legacy", False)]++    it "keeps application exceptions on the legacy observer with their request" $+        bracket (N.bindRandomPortTCP "127.0.0.1") (close . snd) $ \(port, listener) -> do+            events <- newChan+            connectionCalled <- newIORef False+            ready <- newEmptyMVar+            let legacy request exception = writeChan events (isJust request, show exception)+                connection _ _ = writeIORef connectionCalled True+                settings = setBeforeMainLoop (putMVar ready ())+                    $ setOnException legacy+                    $ setOnConnectionException connection defaultSettings+                application _ _ = throwIO (ConnectionFailure 4)+            withAsync (runSettingsSocket settings listener application) $ \server -> do+                link server+                timeout 2000000 (takeMVar ready) `shouldReturn` Just ()+                response <- sendGET ("http://127.0.0.1:" ++ show port ++ "/")+                responseStatus response `shouldBe` internalServerError500+                timeout 2000000 (readChan events) `shouldReturn` Just (True, "ConnectionFailure 4")+                readIORef connectionCalled `shouldReturn` False++    it "reports each sequential connection maker's own peer" $ do+        events <- newChan+        makers <- newChan+        let settings = observePeer (record events) defaultSettings+        withAsync (runSettingsConnectionMakerSecure settings (readChan makers) unusedApplication) $ \server -> do+            link server+            forM_ [(5, 130), (6, 140)] $ \(failureId, peerId) -> do+                writeChan makers (throwIO (ConnectionFailure failureId), peer peerId)+                timeout 2000000 (readChan events) `shouldReturn` Just (failureId, Just (peer peerId))++    it "reports peers when overlapping connection makers fail in reverse order" $ do+        events <- newChan+        makers <- newChan+        started <- newChan+        first <- newEmptyMVar+        second <- newEmptyMVar+        let settings = observePeer (record events) defaultSettings+            maker i gate = writeChan started i >> takeMVar gate >> throwIO (ConnectionFailure i)+        withAsync (runSettingsConnectionMakerSecure settings (readChan makers) unusedApplication) $ \server -> do+            link server+            writeChan makers (maker 7 first, peer 150)+            writeChan makers (maker 8 second, peer 160)+            -- Release both workers even when a readiness assertion fails.+            let release = forM_ [first, second] $ \gate -> void (tryPutMVar gate ())+            flip finally release $ do+                ready <- timeout 2000000 (replicateM 2 (readChan started))+                fmap length ready `shouldBe` Just 2+                putMVar second ()+                secondEvent <- timeout 2000000 (readChan events)+                putMVar first ()+                firstEvent <- timeout 2000000 (readChan events)+                (secondEvent, firstEvent) `shouldBe` (Just (8, Just (peer 160)), Just (7, Just (peer 150)))+  where+    unusedApplication _ _ = fail "connection maker must fail before the application"++-- Install the peer observer used by the regression assertions. The original+-- failing commit had to infer peers from Maybe Request.+observePeer :: (Maybe SockAddr -> SomeException -> IO ()) -> Settings -> Settings+observePeer report = setOnConnectionException (report . Just)++record :: Chan Observation -> Maybe SockAddr -> SomeException -> IO ()+record events address exception = case fromException exception of+    Just (ConnectionFailure i) -> writeChan events (i, address)+    Nothing -> throwIO exception++peer :: Int -> SockAddr+peer i = SockAddrInet (fromIntegral (40000 + i)) (tupleToHostAddress (127, 0, 0, fromIntegral i))
test/EarlyHintsSpec.hs view
@@ -22,6 +22,10 @@ spec :: Spec spec = describe "HTTP/2 Early Hints" $     it "delivers a WAI app's 103 Early Hints to the client before the final response (h2c)" $+#ifdef WINDOWS+        -- This test is failing on Windows (it hangs at @C.run@)+        pendingWith "requires more testing on a Windows machine"+#else         testWithApplication (pure app) $ \port -> do             hintsRef <- newIORef []             earlyHintsClient port hintsRef `shouldReturn` Just ok200@@ -64,6 +68,7 @@         sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)         connect sock (addrAddress addr)         return sock+#endif #else spec :: Spec spec = describe "HTTP/2 Early Hints" $
test/GracefulShutdownSpec.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}  module GracefulShutdownSpec (spec) where @@ -8,16 +9,68 @@ import Control.Concurrent.Async import Control.Exception (bracket) import Control.Monad (void)+import Data.IORef import Network.HTTP.Client import Network.HTTP.Types (ok200, status200)-import Network.Socket (close)+import Network.Socket import Network.Wai (responseLBS) import Network.Wai.Handler.Warp import System.Timeout (timeout) import Test.Hspec  spec :: Spec-spec = describe "graceful shutdown" $+spec = describe "graceful shutdown" $ do+    it "waits for a connection accepted just before it stopped accepting" $ do+        -- The window is between accepting a connection and the thread+        -- serving it being scheduled. Delaying the thread makes it wide+        -- enough to test; in a running server it is however long the RTS+        -- takes to get to the new thread.+        accepted <- newIORef (0 :: Int)+        closed <- newIORef (0 :: Int)++        let slowFork :: ((forall a. IO a -> IO a) -> IO ()) -> IO ()+            slowFork act = void $ forkIOWithUnmask $ \unmask -> do+                threadDelay 200_000+                act unmask++            -- Take one connection, then stop accepting by closing the+            -- listening socket, which is what a graceful shutdown does. The+            -- close happens here rather than from another thread so that it+            -- cannot land while the accept loop is parked inside accept().+            acceptOnlyOne sock = do+                taken <- atomicModifyIORef' accepted $ \n -> (n + 1, n)+                if taken == 0+                    then accept sock+                    else close sock >> accept sock++            settings =+                setFork slowFork $+                    setAccept acceptOnlyOne $+                        setOnClose (\_ -> atomicModifyIORef' closed $ \n -> (n + 1, ())) $+                            setGracefulShutdownTimeout (Just 5) $+                                setOnException (\_ _ -> pure ()) defaultSettings++            app _ respond = respond $ responseLBS status200 [("Content-Length", "0")] ""++        bracket openFreePort (close . snd) $ \(testPort, sock) -> do+            -- Connect before the server exists. openFreePort has already put+            -- the socket in listen state, so this lands in its accept queue+            -- in the kernel and stays there: closing the client end sends a+            -- FIN but does not take it off the queue, and accept() still+            -- hands it over. Queueing it up front is what makes the accept+            -- loop's first accept() return immediately, rather than racing a+            -- client connecting alongside it, which on a loaded machine it+            -- can lose.+            bracket (openConnection testPort) close $ \_ -> pure ()++            withAsync (runSettingsSocket settings sock app) $ \server -> do+                timeout 30_000_000 (wait server)+                    >>= maybe (expectationFailure "Timeout waiting for server shutdown") pure+                -- Returning is what lets the process exit, so a connection+                -- still open here is one the client never hears back on.+                connectionsClosed <- readIORef closed+                connectionsClosed `shouldBe` 1+     it "serves the request in flight, then closes keep-alive connections and exits" $ do         shutdownSignal <- newEmptyMVar         allowResponse <- newEmptyMVar@@ -76,6 +129,11 @@                         -- wait for all clients and propagate any exceptions                         wait clients   where+    openConnection testPort = do+        client <- socket AF_INET Stream defaultProtocol+        connect client $+            SockAddrInet (fromIntegral testPort) (tupleToHostAddress (127, 0, 0, 1))+        pure client     -- set number of clients to the number of keep-alive connections     numClients = managerConnCount defaultManagerSettings     connectionRefused = \case
test/RunSpec.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -357,8 +358,16 @@                     check $ count == 2                 front <- I.readIORef ifront                 front [] `shouldBe` replicate 2 (S.concat $ replicate 50 "12345")+#ifndef WINDOWS         -- For some reason, the following test on Windows causes the socket         -- to be killed prematurely. Worth investigating in the future if possible.+        --+        -- @+        --   test\RunSpec.hs:362:9:+        --   1) Run, chunked bodies, in chunks+        --        uncaught exception: IOException of type InvalidArgument+        --        Network.Socket.sendBuf: invalid argument (Invalid argument)+        -- @         it "in chunks" $ do             ifront <- I.newIORef id             countVar <- newTVarIO (0 :: Int)@@ -384,6 +393,7 @@                     `shouldBe` [ "Hello World\nBye"                                , "Hello World"                                ]+#endif         it "timeout in request body" $ do             ifront <- I.newIORef id             let app req f = do
warp.cabal view
@@ -1,6 +1,6 @@ cabal-version:      >=1.10 name:               warp-version:            3.4.15+version:            3.4.16 license:            MIT license-file:       LICENSE maintainer:         michael@snoyman.com@@ -110,12 +110,12 @@         http-semantics >=0.4 && <0.5,         http2 >=5.4 && <5.5,         iproute >=1.3.1,-        recv >=0.1.0 && <0.2.0,+        recv >=0.1.2 && <0.2.0,         simple-sendfile >=0.2.7 && <0.3,         stm >=2.3,         streaming-commons >=0.1.10,         text,-        time-manager >=0.2 && <0.4,+        time-manager >=0.2 && <0.5,         vault >=0.3,         wai >=3.2.5 && <3.3,         word8@@ -182,6 +182,7 @@         BufferSpec         ConduitSpec         ConnectionSpec+        ConnectionExceptionSpec         EarlyHintsSpec         ExceptionSpec         FdCacheSpec@@ -256,7 +257,7 @@         iproute >=1.3.1,         network,         process,-        recv >=0.1.0 && <0.2.0,+        recv >=0.1.2 && <0.2.0,         simple-sendfile >=0.2.4 && <0.3,         stm >=2.3,         streaming-commons >=0.1.10,@@ -363,6 +364,78 @@         build-depends:             time,             unix-compat >=0.2++    if impl(ghc >=8)+        default-extensions: Strict StrictData++benchmark response+    type:             exitcode-stdio-1.0+    main-is:          ResponseBench.hs+    hs-source-dirs:   bench .+    other-modules:+        Network.Wai.Handler.Warp.Buffer+        Network.Wai.Handler.Warp.Conduit+        Network.Wai.Handler.Warp.Counter+        Network.Wai.Handler.Warp.Date+        Network.Wai.Handler.Warp.FdCache+        Network.Wai.Handler.Warp.File+        Network.Wai.Handler.Warp.FileInfoCache+        Network.Wai.Handler.Warp.HashMap+        Network.Wai.Handler.Warp.Header+        Network.Wai.Handler.Warp.IO+        Network.Wai.Handler.Warp.Imports+        Network.Wai.Handler.Warp.PackInt+        Network.Wai.Handler.Warp.ReadInt+        Network.Wai.Handler.Warp.Request+        Network.Wai.Handler.Warp.RequestHeader+        Network.Wai.Handler.Warp.Response+        Network.Wai.Handler.Warp.ResponseHeader+        Network.Wai.Handler.Warp.Settings+        Network.Wai.Handler.Warp.ShuttingDown+        Network.Wai.Handler.Warp.Types++    if flag(include-warp-version)+        other-modules: Paths_warp++    default-language: Haskell2010+    ghc-options:      -threaded+    build-depends:+        base >=4.8 && <5,+        array,+        auto-update,+        bsb-http-chunked,+        bytestring,+        case-insensitive,+        containers,+        criterion,+        hashable,+        http-date,+        http-types,+        network,+        recv,+        stm,+        streaming-commons,+        text,+        time-manager,+        vault,+        wai,+        word8++    if flag(x509)+        build-depends: crypton-x509++    if (((os(linux) || os(freebsd)) || os(osx)) && flag(allow-sendfilefd))+        cpp-options:   -DSENDFILEFD+        build-depends: unix++    if os(windows)+        cpp-options:   -DWINDOWS+        build-depends:+            time,+            unix-compat >=0.2+    else+        other-modules: Network.Wai.Handler.Warp.MultiMap+        build-depends: unix      if impl(ghc >=8)         default-extensions: Strict StrictData