diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,3 +1,13 @@
+## 3.0.10
+
+* Fix [missing `IORef` tweak](https://github.com/yesodweb/wai/issues/351)
+* Disable timeouts as soon as request body is fully consumed. This addresses
+  the common case of a non-chunked request body. Previously, we would wait
+  until a zero-length `ByteString` is returned, but that is suboptimal for some
+  cases. For more information, see [issue
+  351](https://github.com/yesodweb/wai/issues/351).
+* Add `pauseTimeout` function
+
 ## 3.0.9.3
 
 * Don't serve a 416 status code for 0-length files [keter issue #75](https://github.com/snoyberg/keter/issues/75)
diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs
--- a/Network/Wai/Handler/Warp.hs
+++ b/Network/Wai/Handler/Warp.hs
@@ -17,6 +17,21 @@
 ---------------------------------------------------------
 
 -- | A fast, light-weight HTTP server handler for WAI.
+--
+-- Note on slowloris timeouts: to prevent slowloris attacks, timeouts are used
+-- at various points in request receiving and response sending. One interesting
+-- corner case is partial request body consumption; in that case, Warp's
+-- timeout handling is still in effect, and the timeout will not be triggered
+-- again. Therefore, it is recommended that once you start consuming the
+-- request body, you either:
+--
+-- * consume the entire body promptly
+--
+-- * call the 'pauseTimeout' function
+--
+-- For more information, see <https://github.com/yesodweb/wai/issues/351>.
+--
+--
 module Network.Wai.Handler.Warp (
     -- * Run a Warp server
     run
@@ -73,6 +88,8 @@
   , Port
   , InvalidRequest (..)
   , ConnSendFileOverride (..)
+    -- * Per-request utilities
+  , pauseTimeout
     -- * Connection
   , Connection (..)
   , socketConnection
@@ -105,10 +122,12 @@
 import Network.Wai.Handler.Warp.Timeout
 import Network.Wai.Handler.Warp.Types
 import Control.Exception (SomeException)
-import Network.Wai (Request, Response)
+import Network.Wai (Request, Response, vault)
 import Network.Socket (SockAddr)
 import Data.Streaming.Network (HostPreference)
 import Data.ByteString (ByteString)
+import qualified Data.Vault.Lazy as Vault
+import Data.Maybe (fromMaybe)
 
 -- | Port to listen on. Default value: 3000
 --
@@ -284,3 +303,12 @@
 -- Since 3.0.5
 setProxyProtocolOptional :: Settings -> Settings
 setProxyProtocolOptional y = y { settingsProxyProtocol = ProxyProtocolOptional }
+
+-- | Explicitly pause the slowloris timeout.
+--
+-- 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
+pauseTimeout :: Request -> IO ()
+pauseTimeout = fromMaybe (return ()) . Vault.lookup pauseTimeoutKey . vault
diff --git a/Network/Wai/Handler/Warp/Request.hs b/Network/Wai/Handler/Warp/Request.hs
--- a/Network/Wai/Handler/Warp/Request.hs
+++ b/Network/Wai/Handler/Warp/Request.hs
@@ -6,6 +6,7 @@
 module Network.Wai.Handler.Warp.Request (
     recvRequest
   , headerLines
+  , pauseTimeoutKey
   ) where
 
 import qualified Control.Concurrent as Conc (yield)
@@ -16,7 +17,6 @@
 import qualified Data.ByteString.Unsafe as SU
 import qualified Data.CaseInsensitive as CI
 import qualified Data.IORef as I
-import Data.Monoid (mempty)
 import qualified Network.HTTP.Types as H
 import Network.Socket (SockAddr)
 import Network.Wai
@@ -30,6 +30,8 @@
 import Network.Wai.Internal
 import Prelude hiding (lines)
 import Control.Monad (when)
+import qualified Data.Vault.Lazy as Vault
+import System.IO.Unsafe (unsafePerformIO)
 
 ----------------------------------------------------------------
 
@@ -62,7 +64,7 @@
         te = idxhdr ! idxTransferEncoding
     handleExpect conn httpversion expect
     (rbody, remainingRef, bodyLength) <- bodyAndSource src cl te
-    rbody' <- timeoutBody th rbody
+    rbody' <- timeoutBody remainingRef th rbody
     let req = Request {
             requestMethod     = method
           , httpVersion       = httpversion
@@ -74,7 +76,9 @@
           , isSecure          = False
           , remoteHost        = addr
           , requestBody       = rbody'
-          , vault             = mempty
+          , vault             = Vault.insert pauseTimeoutKey
+                                (Timeout.pause th)
+                                Vault.empty
           , requestBodyLength = bodyLength
           , requestHeaderHost = idxhdr ! idxHost
           , requestHeaderRange = idxhdr ! idxRange
@@ -138,18 +142,31 @@
 
 ----------------------------------------------------------------
 
-timeoutBody :: Timeout.Handle -> IO ByteString -> IO (IO ByteString)
-timeoutBody timeoutHandle rbody = do
+timeoutBody :: Maybe (I.IORef Int) -- ^ remaining
+            -> Timeout.Handle
+            -> IO ByteString
+            -> IO (IO ByteString)
+timeoutBody remainingRef timeoutHandle rbody = do
     isFirstRef <- I.newIORef True
 
+    let checkEmpty =
+            case remainingRef of
+                Nothing -> return . S.null
+                Just ref -> \bs -> if S.null bs
+                    then return True
+                    else do
+                        x <- I.readIORef ref
+                        return $! x <= 0
+
     return $ do
         isFirst <- I.readIORef isFirstRef
 
-        when isFirst $
+        when isFirst $ do
             -- Timeout handling was paused after receiving the full request
             -- headers. Now we need to resume it to avoid a slowloris
             -- attack during request body sending.
             Timeout.resume timeoutHandle
+            I.writeIORef isFirstRef False
 
         bs <- rbody
 
@@ -157,7 +174,8 @@
         -- because the application is not interested in more bytes, or
         -- because there is no more data available, pause the timeout
         -- handler again.
-        when (S.null bs) (Timeout.pause timeoutHandle)
+        isEmpty <- checkEmpty bs
+        when isEmpty (Timeout.pause timeoutHandle)
 
         return bs
 
@@ -247,3 +265,7 @@
 checkCR bs pos = if pos > 0 && 13 == S.index bs p then p else pos -- 13 is CR
   where
     !p = pos - 1
+
+pauseTimeoutKey :: Vault.Key (IO ())
+pauseTimeoutKey = unsafePerformIO Vault.newKey
+{-# NOINLINE pauseTimeoutKey #-}
diff --git a/warp.cabal b/warp.cabal
--- a/warp.cabal
+++ b/warp.cabal
@@ -1,5 +1,5 @@
 Name:                warp
-Version:             3.0.9.3
+Version:             3.0.10
 Synopsis:            A fast, light-weight web server for WAI applications.
 License:             MIT
 License-file:        LICENSE
@@ -46,6 +46,7 @@
                    , wai                       >= 3.0      && < 3.1
                    , text
                    , streaming-commons         >= 0.1.10
+                   , vault                     >= 0.3
   if flag(network-bytestring)
       Build-Depends: network                   >= 2.2.1.5  && < 2.2.3
                    , network-bytestring        >= 0.1.3    && < 0.1.4
@@ -143,6 +144,7 @@
                    , text
                    , streaming-commons         >= 0.1.10
                    , async
+                   , vault
   if flag(use-bytestring-builder)
       Build-Depends: bytestring                < 0.10.2.0
                    , bytestring-builder
