diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,14 @@
 # ChangeLog for warp
 
+## 3.3.23
+
+* Add `setAccept` for hooking the socket `accept` call.
+  [#912](https://github.com/yesodweb/wai/pull/912)
+* Removed some package dependencies from test suite
+  [#902](https://github.com/yesodweb/wai/pull/902)
+* Factored out `Network.Wai.Handler.Warp.Recv` to its own package `recv`.
+  [#899](https://github.com/yesodweb/wai/pull/899)
+
 ## 3.3.22
 
 * Creating a bigger buffer when the current one is too small to fit the Builder
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
@@ -64,6 +64,7 @@
   , setServerName
   , setMaximumBodyFlush
   , setFork
+  , setAccept
   , setProxyProtocolNone
   , setProxyProtocolRequired
   , setProxyProtocolOptional
@@ -135,7 +136,7 @@
 import Data.X509
 #endif
 import qualified Network.HTTP.Types as H
-import Network.Socket (SockAddr)
+import Network.Socket (Socket, SockAddr)
 import Network.Wai (Request, Response, vault)
 import System.TimeManager
 
@@ -369,6 +370,17 @@
 -- Since 3.0.4
 setFork :: (((forall a. IO a -> IO a) -> IO ()) -> IO ()) -> Settings -> Settings
 setFork fork' s = s { settingsFork = fork' }
+
+-- | Code to accept a new connection.
+--
+-- Useful if you need to provide connected sockets from something other
+-- than a standard accept call.
+--
+-- Default: 'defaultAccept'
+--
+-- Since 3.3.24
+setAccept :: (Socket -> IO (Socket, SockAddr)) -> Settings -> Settings
+setAccept accept' s = s { settingsAccept = accept' }
 
 -- | Do not use the PROXY protocol.
 --
diff --git a/Network/Wai/Handler/Warp/Buffer.hs b/Network/Wai/Handler/Warp/Buffer.hs
--- a/Network/Wai/Handler/Warp/Buffer.hs
+++ b/Network/Wai/Handler/Warp/Buffer.hs
@@ -2,25 +2,18 @@
 
 module Network.Wai.Handler.Warp.Buffer (
     createWriteBuffer
-  , bufferSize
   , allocateBuffer
   , freeBuffer
-  , mallocBS
-  , newBufferPool
-  , withBufferPool
   , toBuilderBuffer
-  , copy
   , bufferIO
   ) where
 
-import qualified Data.ByteString as BS
-import Data.ByteString.Internal (memcpy)
-import Data.ByteString.Unsafe (unsafeTake, unsafeDrop)
-import Data.IORef (IORef, newIORef, readIORef, writeIORef)
+import Data.IORef (IORef, readIORef)
 import qualified Data.Streaming.ByteString.Builder.Buffer as B (Buffer (..))
 import Foreign.ForeignPtr
-import Foreign.Marshal.Alloc (mallocBytes, free, finalizerFree)
-import Foreign.Ptr (castPtr, plusPtr)
+import Foreign.Marshal.Alloc (mallocBytes, free)
+import Foreign.Ptr (plusPtr)
+import Network.Socket.BufferPool
 
 import Network.Wai.Handler.Warp.Imports
 import Network.Wai.Handler.Warp.Types
@@ -41,13 +34,6 @@
 
 ----------------------------------------------------------------
 
--- | The default size of the write buffer: 16384 (2^14 = 1024 * 16).
---   This is the maximum size of TLS record.
---   This is also the maximum size of HTTP/2 frame payload
---   (excluding frame header).
-bufferSize :: BufSize
-bufferSize = 16384
-
 -- | Allocating a buffer with malloc().
 allocateBuffer :: Int -> IO Buffer
 allocateBuffer = mallocBytes
@@ -57,50 +43,6 @@
 freeBuffer = free
 
 ----------------------------------------------------------------
-
-largeBufferSize :: Int
-largeBufferSize = 16384
-
-minBufferSize :: Int
-minBufferSize = 2048
-
-newBufferPool :: IO BufferPool
-newBufferPool = newIORef BS.empty
-
-mallocBS :: Int -> IO ByteString
-mallocBS size = do
-    ptr <- allocateBuffer size
-    fptr <- newForeignPtr finalizerFree ptr
-    return $! PS fptr 0 size
-{-# INLINE mallocBS #-}
-
-usefulBuffer :: ByteString -> Bool
-usefulBuffer buffer = BS.length buffer >= minBufferSize
-{-# INLINE usefulBuffer #-}
-
-getBuffer :: BufferPool -> IO ByteString
-getBuffer pool = do
-    buffer <- readIORef pool
-    if usefulBuffer buffer then return buffer else mallocBS largeBufferSize
-{-# INLINE getBuffer #-}
-
-putBuffer :: BufferPool -> ByteString -> IO ()
-putBuffer pool buffer = writeIORef pool buffer
-{-# INLINE putBuffer #-}
-
-withForeignBuffer :: ByteString -> ((Buffer, BufSize) -> IO Int) -> IO Int
-withForeignBuffer (PS ps s l) f = withForeignPtr ps $ \p -> f (castPtr p `plusPtr` s, l)
-{-# INLINE withForeignBuffer #-}
-
-withBufferPool :: BufferPool -> ((Buffer, BufSize) -> IO Int) -> IO ByteString
-withBufferPool pool f = do
-    buffer <- getBuffer pool
-    consumed <- withForeignBuffer buffer f
-    putBuffer pool $! unsafeDrop consumed buffer
-    return $! unsafeTake consumed buffer
-{-# INLINE withBufferPool #-}
-
-----------------------------------------------------------------
 --
 -- Utilities
 --
@@ -112,14 +54,6 @@
         size = bufSize writeBuffer
     fptr <- newForeignPtr_ ptr
     return $ B.Buffer fptr ptr ptr (ptr `plusPtr` size)
-
--- | Copying the bytestring to the buffer.
---   This function returns the point where the next copy should start.
-copy :: Buffer -> ByteString -> IO Buffer
-copy !ptr (PS fp o l) = withForeignPtr fp $ \p -> do
-    memcpy ptr (p `plusPtr` o) (fromIntegral l)
-    return $! ptr `plusPtr` l
-{-# INLINE copy #-}
 
 bufferIO :: Buffer -> Int -> (ByteString -> IO ()) -> IO ()
 bufferIO ptr siz io = do
diff --git a/Network/Wai/Handler/Warp/HTTP2.hs b/Network/Wai/Handler/Warp/HTTP2.hs
--- a/Network/Wai/Handler/Warp/HTTP2.hs
+++ b/Network/Wai/Handler/Warp/HTTP2.hs
@@ -9,16 +9,17 @@
   , http2server
   ) where
 
-import qualified UnliftIO
 import qualified Data.ByteString as BS
 import Data.IORef (IORef, newIORef, writeIORef, readIORef)
 import qualified Data.IORef as I
 import qualified Network.HTTP2.Frame as H2
 import qualified Network.HTTP2.Server as H2
 import Network.Socket (SockAddr)
+import Network.Socket.BufferPool
 import Network.Wai
 import Network.Wai.Internal (ResponseReceived(..))
 import qualified System.TimeManager as T
+import qualified UnliftIO
 
 import Network.Wai.Handler.Warp.HTTP2.File
 import Network.Wai.Handler.Warp.HTTP2.PushPromise
@@ -27,7 +28,6 @@
 import Network.Wai.Handler.Warp.Imports
 import qualified Network.Wai.Handler.Warp.Settings as S
 import Network.Wai.Handler.Warp.Types
-import Network.Wai.Handler.Warp.Recv
 
 ----------------------------------------------------------------
 
@@ -115,7 +115,7 @@
 
 wrappedRecvN :: T.Handle -> IORef Bool -> Int -> (BufSize -> IO ByteString) -> (BufSize -> IO ByteString)
 wrappedRecvN th istatus slowlorisSize readN bufsize = do
-    bs <- readN bufsize
+    bs <-  UnliftIO.handleAny handler $ readN bufsize
     unless (BS.null bs) $ do
         writeIORef istatus True
     -- TODO: think about the slowloris protection in HTTP2: current code
@@ -125,6 +125,9 @@
     -- deployments with large NATs may be trickier).
         when (BS.length bs >= slowlorisSize || bufsize <= slowlorisSize) $ T.tickle th
     return bs
+ where
+   handler :: UnliftIO.SomeException -> IO ByteString
+   handler _ = return ""
 
 -- connClose must not be called here since Run:fork calls it
 goaway :: Connection -> H2.ErrorCodeId -> ByteString -> IO ()
diff --git a/Network/Wai/Handler/Warp/Imports.hs b/Network/Wai/Handler/Warp/Imports.hs
--- a/Network/Wai/Handler/Warp/Imports.hs
+++ b/Network/Wai/Handler/Warp/Imports.hs
@@ -4,7 +4,6 @@
   , module Control.Applicative
   , module Control.Monad
   , module Data.Bits
-  , module Data.List
   , module Data.Int
   , module Data.Monoid
   , module Data.Ord
@@ -18,7 +17,6 @@
 import Data.Bits
 import Data.ByteString.Internal (ByteString(..))
 import Data.Int
-import Data.List
 import Data.List.NonEmpty (NonEmpty(..))
 import Data.Maybe
 import Data.Monoid
diff --git a/Network/Wai/Handler/Warp/Internal.hs b/Network/Wai/Handler/Warp/Internal.hs
--- a/Network/Wai/Handler/Warp/Internal.hs
+++ b/Network/Wai/Handler/Warp/Internal.hs
@@ -21,7 +21,6 @@
   , BufSize
   , WriteBuffer(..)
   , createWriteBuffer
-  , bufferSize
   , allocateBuffer
   , freeBuffer
   , copy
@@ -75,6 +74,7 @@
   , pReadMaker
   ) where
 
+import Network.Socket.BufferPool
 import System.TimeManager
 
 import Network.Wai.Handler.Warp.Buffer
@@ -84,7 +84,6 @@
 import Network.Wai.Handler.Warp.HTTP2
 import Network.Wai.Handler.Warp.HTTP2.File
 import Network.Wai.Handler.Warp.Header
-import Network.Wai.Handler.Warp.Recv
 import Network.Wai.Handler.Warp.Request
 import Network.Wai.Handler.Warp.Response
 import Network.Wai.Handler.Warp.Run
diff --git a/Network/Wai/Handler/Warp/Recv.hs b/Network/Wai/Handler/Warp/Recv.hs
deleted file mode 100644
--- a/Network/Wai/Handler/Warp/Recv.hs
+++ /dev/null
@@ -1,160 +0,0 @@
-{-# LANGUAGE ForeignFunctionInterface, OverloadedStrings #-}
-{-# LANGUAGE CPP #-}
-
-module Network.Wai.Handler.Warp.Recv (
-    receive
-  , receiveBuf
-  , makeReceiveN
-  , makePlainReceiveN
-  , spell
-  ) where
-
-import qualified UnliftIO
-import qualified Data.ByteString as BS
-import Data.IORef
-import Foreign.C.Error (eAGAIN, getErrno, throwErrno)
-import Foreign.C.Types
-import Foreign.ForeignPtr (withForeignPtr)
-import Foreign.Ptr (Ptr, castPtr, plusPtr)
-import GHC.Conc (threadWaitRead)
-import qualified GHC.IO.Exception as E
-import Network.Socket (Socket)
-import qualified System.IO.Error as E
-#if MIN_VERSION_network(3,1,0)
-import Network.Socket (withFdSocket)
-#else
-import Network.Socket (fdSocket)
-#endif
-import System.Posix.Types (Fd(..))
-
-import Network.Wai.Handler.Warp.Buffer
-import Network.Wai.Handler.Warp.Imports
-import Network.Wai.Handler.Warp.Types
-
-#ifdef mingw32_HOST_OS
-import GHC.IO.FD (FD(..), readRawBufferPtr)
-import Network.Wai.Handler.Warp.Windows
-#endif
-
-----------------------------------------------------------------
-
-makeReceiveN :: ByteString -> Recv -> RecvBuf -> IO (BufSize -> IO ByteString)
-makeReceiveN bs0 recv recvBuf = do
-    ref <- newIORef bs0
-    return $ receiveN ref recv recvBuf
-
--- | This function returns a receiving function
---   based on two receiving functions.
---   The returned function efficiently manages received data
---   which is initialized by the first argument.
---   The returned function may allocate a byte string with malloc().
-makePlainReceiveN :: Socket -> ByteString -> IO (BufSize -> IO ByteString)
-makePlainReceiveN s bs0 = do
-    ref <- newIORef bs0
-    pool <- newBufferPool
-    return $ receiveN ref (receive s pool) (receiveBuf s)
-
-receiveN :: IORef ByteString -> Recv -> RecvBuf -> BufSize -> IO ByteString
-receiveN ref recv recvBuf size = UnliftIO.handleAny handler $ do
-    cached <- readIORef ref
-    (bs, leftover) <- spell cached size recv recvBuf
-    writeIORef ref leftover
-    return bs
- where
-   handler :: UnliftIO.SomeException -> IO ByteString
-   handler _ = return ""
-
-----------------------------------------------------------------
-
-spell :: ByteString -> BufSize -> IO ByteString -> RecvBuf -> IO (ByteString, ByteString)
-spell init0 siz0 recv recvBuf
-  | siz0 <= len0 = return $ BS.splitAt siz0 init0
-  -- fixme: hard coding 4096
-  | siz0 <= 4096 = loop [init0] (siz0 - len0)
-  | otherwise    = do
-      bs@(PS fptr _ _) <- mallocBS siz0
-      withForeignPtr fptr $ \ptr -> do
-          ptr' <- copy ptr init0
-          full <- recvBuf ptr' (siz0 - len0)
-          if full then
-              return (bs, "")
-            else
-              return ("", "") -- fixme
-  where
-    len0 = BS.length init0
-    loop bss siz = do
-        bs <- recv
-        let len = BS.length bs
-        if len == 0 then
-            return ("", "")
-          else if len >= siz then do
-            let (consume, leftover) = BS.splitAt siz bs
-                ret = BS.concat $ reverse (consume : bss)
-            return (ret, leftover)
-          else do
-            let bss' = bs : bss
-                siz' = siz - len
-            loop bss' siz'
-
--- The timeout manager may close the socket.
--- In that case, an error of "Bad file descriptor" occurs.
--- We ignores it because we expect TimeoutThread.
-receive :: Socket -> BufferPool -> Recv
-receive sock pool = UnliftIO.handleIO handler $ withBufferPool pool $ \ (ptr, size) -> do
-#if MIN_VERSION_network(3,1,0)
-  withFdSocket sock $ \fd -> do
-#elif MIN_VERSION_network(3,0,0)
-    fd <- fdSocket sock
-#else
-    let fd = fdSocket sock
-#endif
-    let size' = fromIntegral size
-    fromIntegral <$> receiveloop fd ptr size'
-  where
-    handler :: UnliftIO.IOException -> IO ByteString
-    handler e
-      | E.ioeGetErrorType e == E.InvalidArgument = return ""
-      | otherwise                                = UnliftIO.throwIO e
-
-receiveBuf :: Socket -> RecvBuf
-receiveBuf sock buf0 siz0 = do
-#if MIN_VERSION_network(3,1,0)
-  withFdSocket sock $ \fd -> do
-#elif MIN_VERSION_network(3,0,0)
-    fd <- fdSocket sock
-#else
-    let fd = fdSocket sock
-#endif
-    loop fd buf0 siz0
-  where
-    loop _  _   0   = return True
-    loop fd buf siz = do
-        n <- fromIntegral <$> receiveloop fd buf (fromIntegral siz)
-        -- fixme: what should we do in the case of n == 0
-        if n == 0 then
-            return False
-          else
-            loop fd (buf `plusPtr` n) (siz - n)
-
-receiveloop :: CInt -> Ptr Word8 -> CSize -> IO CInt
-receiveloop sock ptr size = do
-#ifdef mingw32_HOST_OS
-    bytes <- windowsThreadBlockHack $ fromIntegral <$> readRawBufferPtr "recv" (FD sock 1) (castPtr ptr) 0 size
-#else
-    bytes <- c_recv sock (castPtr ptr) size 0
-#endif
-    if bytes == -1 then do
-        errno <- getErrno
-        if errno == eAGAIN then do
-            threadWaitRead (Fd sock)
-            receiveloop sock ptr size
-          else
-            throwErrno "receiveloop"
-       else
-        return bytes
-
-#ifndef mingw32_HOST_OS
--- fixme: the type of the return value
-foreign import ccall unsafe "recv"
-    c_recv :: CInt -> Ptr CChar -> CSize -> CInt -> IO CInt
-#endif
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
@@ -39,7 +39,7 @@
 import Network.Wai.Handler.Warp.Conduit
 import Network.Wai.Handler.Warp.FileInfoCache
 import Network.Wai.Handler.Warp.Header
-import Network.Wai.Handler.Warp.Imports hiding (readInt, lines)
+import Network.Wai.Handler.Warp.Imports hiding (readInt)
 import Network.Wai.Handler.Warp.ReadInt
 import Network.Wai.Handler.Warp.RequestHeader
 import Network.Wai.Handler.Warp.Settings (Settings, settingsNoParsePath, settingsMaxTotalHeaderLength)
diff --git a/Network/Wai/Handler/Warp/Response.hs b/Network/Wai/Handler/Warp/Response.hs
--- a/Network/Wai/Handler/Warp/Response.hs
+++ b/Network/Wai/Handler/Warp/Response.hs
@@ -23,6 +23,7 @@
 import qualified Data.ByteString.Char8 as C8
 import qualified Data.CaseInsensitive as CI
 import Data.Function (on)
+import Data.List (deleteBy)
 import Data.Streaming.ByteString.Builder (newByteStringBuilderRecv, reuseBufferStrategy)
 import Data.Version (showVersion)
 import Data.Word8 (_cr, _lf)
diff --git a/Network/Wai/Handler/Warp/ResponseHeader.hs b/Network/Wai/Handler/Warp/ResponseHeader.hs
--- a/Network/Wai/Handler/Warp/ResponseHeader.hs
+++ b/Network/Wai/Handler/Warp/ResponseHeader.hs
@@ -6,11 +6,12 @@
 import qualified Data.ByteString as S
 import Data.ByteString.Internal (create)
 import qualified Data.CaseInsensitive as CI
+import Data.List (foldl')
 import Foreign.Ptr
 import GHC.Storable
 import qualified Network.HTTP.Types as H
+import Network.Socket.BufferPool (copy)
 
-import Network.Wai.Handler.Warp.Buffer (copy)
 import Network.Wai.Handler.Warp.Imports
 
 ----------------------------------------------------------------
diff --git a/Network/Wai/Handler/Warp/Run.hs b/Network/Wai/Handler/Warp/Run.hs
--- a/Network/Wai/Handler/Warp/Run.hs
+++ b/Network/Wai/Handler/Warp/Run.hs
@@ -8,25 +8,26 @@
 module Network.Wai.Handler.Warp.Run where
 
 import Control.Arrow (first)
-import Control.Exception (allowInterrupt)
 import qualified Control.Exception
-import qualified UnliftIO
-import UnliftIO (toException)
+import Control.Exception (allowInterrupt)
 import qualified Data.ByteString as S
 import Data.IORef (newIORef, readIORef)
 import Data.Streaming.Network (bindPortTCP)
 import Foreign.C.Error (Errno(..), eCONNABORTED, eMFILE)
 import GHC.IO.Exception (IOException(..), IOErrorType(..))
-import Network.Socket (Socket, close, accept, withSocketsDo, SockAddr, setSocketOption, SocketOption(..))
+import Network.Socket (Socket, close, withSocketsDo, SockAddr, setSocketOption, SocketOption(..))
 #if MIN_VERSION_network(3,1,1)
 import Network.Socket (gracefulClose)
 #endif
+import Network.Socket.BufferPool
 import qualified Network.Socket.ByteString as Sock
 import Network.Wai
 import System.Environment (lookupEnv)
 import System.IO.Error (ioeGetErrorType)
 import qualified System.TimeManager as T
 import System.Timeout (timeout)
+import qualified UnliftIO
+import UnliftIO (toException)
 
 import Network.Wai.Handler.Warp.Buffer
 import Network.Wai.Handler.Warp.Counter
@@ -37,7 +38,6 @@
 import Network.Wai.Handler.Warp.HTTP2 (http2)
 import Network.Wai.Handler.Warp.HTTP2.Types (isHTTP2)
 import Network.Wai.Handler.Warp.Imports hiding (readInt)
-import Network.Wai.Handler.Warp.Recv
 import Network.Wai.Handler.Warp.SendFile
 import Network.Wai.Handler.Warp.Settings
 import Network.Wai.Handler.Warp.Types
@@ -56,8 +56,8 @@
 #else
 socketConnection _ s = do
 #endif
-    bufferPool <- newBufferPool
-    writeBuffer <- createWriteBuffer bufferSize
+    bufferPool <- newBufferPool 2048 16384
+    writeBuffer <- createWriteBuffer 16384
     writeBufferRef <- newIORef writeBuffer
     isH2 <- newIORef False -- HTTP/1.x
     return Connection {
@@ -76,12 +76,19 @@
 #else
       , connClose = close s
 #endif
-      , connRecv = receive s bufferPool
+      , connRecv = receive' s bufferPool
       , connRecvBuf = receiveBuf s
       , connWriteBuffer = writeBufferRef
       , connHTTP2 = isH2
       }
   where
+    receive' sock pool = UnliftIO.handleIO handler $ receive sock pool
+      where
+        handler :: UnliftIO.IOException -> IO ByteString
+        handler e
+          | ioeGetErrorType e == InvalidArgument = return ""
+          | otherwise                            = UnliftIO.throwIO e
+
     sendfile writeBufferRef fid offset len hook headers = do
       writeBuffer <- readIORef writeBufferRef
       sendFile s (bufBuffer writeBuffer) (bufSize writeBuffer) sendall
@@ -142,16 +149,12 @@
 -- Note that the 'settingsPort' will still be passed to 'Application's via the
 -- 'serverPort' record.
 runSettingsSocket :: Settings -> Socket -> Application -> IO ()
-runSettingsSocket set socket app = do
+runSettingsSocket set@Settings{settingsAccept = accept'} socket app = do
     settingsInstallShutdownHandler set closeListenSocket
     runSettingsConnection set getConn app
   where
     getConn = do
-#if WINDOWS
-        (s, sa) <- windowsThreadBlockHack $ accept socket
-#else
-        (s, sa) <- accept socket
-#endif
+        (s, sa) <- accept' socket
         setSocketCloseOnExec s
         -- NoDelay causes an error for AF_UNIX.
         setSocketOption s NoDelay 1 `UnliftIO.catchAny` \(UnliftIO.SomeException _) -> return ()
diff --git a/Network/Wai/Handler/Warp/SendFile.hs b/Network/Wai/Handler/Warp/SendFile.hs
--- a/Network/Wai/Handler/Warp/SendFile.hs
+++ b/Network/Wai/Handler/Warp/SendFile.hs
@@ -11,6 +11,7 @@
 
 import qualified Data.ByteString as BS
 import Network.Socket (Socket)
+import Network.Socket.BufferPool
 
 #ifdef WINDOWS
 import Foreign.ForeignPtr (newForeignPtr_)
diff --git a/Network/Wai/Handler/Warp/Settings.hs b/Network/Wai/Handler/Warp/Settings.hs
--- a/Network/Wai/Handler/Warp/Settings.hs
+++ b/Network/Wai/Handler/Warp/Settings.hs
@@ -16,7 +16,7 @@
 import Data.Version (showVersion)
 import GHC.IO.Exception (IOErrorType(..), AsyncException (ThreadKilled))
 import qualified Network.HTTP.Types as H
-import Network.Socket (SockAddr)
+import Network.Socket (Socket, SockAddr, accept)
 import Network.Wai
 import qualified Paths_warp
 import System.IO (stderr)
@@ -25,6 +25,7 @@
 
 import Network.Wai.Handler.Warp.Imports
 import Network.Wai.Handler.Warp.Types
+import Network.Wai.Handler.Warp.Windows (windowsThreadBlockHack)
 
 -- | Various Warp server settings. This is purposely kept as an abstract data
 -- type so that new settings can be added without breaking backwards
@@ -67,6 +68,16 @@
       --
       -- Since 3.0.4
 
+    , settingsAccept :: Socket -> IO (Socket, SockAddr)
+      -- ^ Code to accept a new connection.
+      --
+      -- Useful if you need to provide connected sockets from something other
+      -- than a standard accept call.
+      --
+      -- Default: 'defaultAccept'
+      --
+      -- Since 3.3.24
+
     , settingsNoParsePath :: Bool
       -- ^ Perform no parsing on the rawPathInfo.
       --
@@ -178,6 +189,7 @@
     , settingsFileInfoCacheDuration = 0
     , settingsBeforeMainLoop = return ()
     , settingsFork = defaultFork
+    , settingsAccept = defaultAccept
     , settingsNoParsePath = False
     , settingsInstallShutdownHandler = const $ return ()
     , settingsServerName = C8.pack $ "Warp/" ++ showVersion Paths_warp.version
@@ -274,4 +286,15 @@
     case (fork# (io unsafeUnmask) s0) of
       (# s1, _tid #) ->
         (# s1, () #)
+#endif
+
+-- | Standard "accept" call for a listening socket.
+--
+-- @since 3.3.24
+defaultAccept :: Socket -> IO (Socket, SockAddr)
+defaultAccept =
+#if WINDOWS
+    windowsThreadBlockHack . accept
+#else
+    accept
 #endif
diff --git a/Network/Wai/Handler/Warp/Types.hs b/Network/Wai/Handler/Warp/Types.hs
--- a/Network/Wai/Handler/Warp/Types.hs
+++ b/Network/Wai/Handler/Warp/Types.hs
@@ -11,7 +11,7 @@
 #ifdef MIN_VERSION_x509
 import Data.X509
 #endif
-import Foreign.Ptr (Ptr)
+import Network.Socket.BufferPool
 import System.Posix.Types (Fd)
 import qualified System.TimeManager as T
 
@@ -88,12 +88,6 @@
 -- Since: 3.1.0
 type SendFile = FileId -> Integer -> Integer -> IO () -> [ByteString] -> IO ()
 
--- | Type for read buffer pool
-type BufferPool = IORef ByteString
-
--- | Type for buffer
-type Buffer = Ptr Word8
-
 -- | A write buffer of a specified size
 -- containing bytes and a way to free the buffer.
 data WriteBuffer = WriteBuffer {
@@ -105,16 +99,6 @@
     , bufFree :: IO ()
     }
 
--- | Type for buffer size
-type BufSize = Int
-
--- | Type for the action to receive input data
-type Recv = IO ByteString
-
--- | Type for the action to receive input data with a buffer.
---   The result boolean indicates whether or not the buffer is fully filled.
-type RecvBuf = Buffer -> BufSize -> IO Bool
-
 -- | Data type to manipulate IO actions for connections.
 --   This is used to abstract IO actions for plain HTTP and HTTP over TLS.
 data Connection = Connection {
@@ -128,7 +112,7 @@
     -- called once. Other functions (like 'connRecv') may be called after
     -- 'connClose' is called.
     , connClose       :: IO ()
-    -- | The connection receiving function. This returns "" for EOF.
+    -- | The connection receiving function. This returns "" for EOF or exceptions.
     , connRecv        :: Recv
     -- | The connection receiving function. This tries to fill the buffer.
     --   This returns when the buffer is filled or reaches EOF.
diff --git a/test/BufferPoolSpec.hs b/test/BufferPoolSpec.hs
deleted file mode 100644
--- a/test/BufferPoolSpec.hs
+++ /dev/null
@@ -1,47 +0,0 @@
-module BufferPoolSpec where
-
-import qualified Data.ByteString as B
-import qualified Data.ByteString.Internal as B (ByteString(PS))
-import Foreign.ForeignPtr (withForeignPtr)
-import Foreign.Marshal.Utils (copyBytes)
-import Foreign.Ptr (plusPtr)
-
-import Test.Hspec (Spec, hspec, shouldBe, describe, it)
-
-import Network.Wai.Handler.Warp.Buffer
-    ( bufferSize
-    , newBufferPool
-    , withBufferPool
-    )
-import Network.Wai.Handler.Warp.Types (Buffer, BufSize)
-
-main :: IO ()
-main = hspec spec
-
--- Two ByteStrings each big enough to fill a 'bufferSize' buffer (16K).
-wantData, otherData :: B.ByteString
-wantData = B.replicate bufferSize 0xac
-otherData = B.replicate bufferSize 0x77
-
-spec :: Spec
-spec = describe "withBufferPool" $ do
-    it "does not clobber buffers" $ do
-        pool <- newBufferPool
-        -- 'pool' contains B.empty; prime it to contain a real buffer.
-        _ <- withBufferPool pool $ const $ return 0
-        -- 'pool' contains a 16K buffer; fill it with \xac and keep the result.
-        got <- withBufferPool pool $ blitBuffer wantData
-        got `shouldBe` wantData
-        -- 'pool' should now be empty and reallocate, rather than clobber the
-        -- previous buffer.
-        _ <- withBufferPool pool $ blitBuffer otherData
-        got `shouldBe` wantData
-
--- Fill the Buffer with the contents of the ByteString and return the number of
--- bytes written.  To be used with 'withBufferPool'.
-blitBuffer :: B.ByteString -> (Buffer, BufSize) -> IO Int
-blitBuffer (B.PS fp off len) (dst, len') = withForeignPtr fp $ \ptr -> do
-    let src = ptr `plusPtr` off
-        n = min len len'
-    copyBytes dst src n
-    return n
diff --git a/warp.cabal b/warp.cabal
--- a/warp.cabal
+++ b/warp.cabal
@@ -1,5 +1,5 @@
 Name:                warp
-Version:             3.3.22
+Version:             3.3.23
 Synopsis:            A fast, light-weight web server for WAI applications.
 License:             MIT
 License-file:        LICENSE
@@ -50,6 +50,7 @@
                    , http-types                >= 0.12
                    , http2                     >= 3.0      && < 5
                    , iproute                   >= 1.3.1
+                   , recv
                    , simple-sendfile           >= 0.2.7    && < 0.3
                    , stm                       >= 2.3
                    , streaming-commons         >= 0.1.10
@@ -91,7 +92,6 @@
                      Network.Wai.Handler.Warp.Imports
                      Network.Wai.Handler.Warp.PackInt
                      Network.Wai.Handler.Warp.ReadInt
-                     Network.Wai.Handler.Warp.Recv
                      Network.Wai.Handler.Warp.Request
                      Network.Wai.Handler.Warp.RequestHeader
                      Network.Wai.Handler.Warp.Response
@@ -135,8 +135,7 @@
 
 Test-Suite spec
     Main-Is:         Spec.hs
-    Other-modules:   BufferPoolSpec
-                     ConduitSpec
+    Other-modules:   ConduitSpec
                      ExceptionSpec
                      FdCacheSpec
                      FileSpec
@@ -170,7 +169,6 @@
                      Network.Wai.Handler.Warp.MultiMap
                      Network.Wai.Handler.Warp.PackInt
                      Network.Wai.Handler.Warp.ReadInt
-                     Network.Wai.Handler.Warp.Recv
                      Network.Wai.Handler.Warp.Request
                      Network.Wai.Handler.Warp.RequestHeader
                      Network.Wai.Handler.Warp.Response
@@ -189,10 +187,8 @@
     Ghc-Options:     -Wall -threaded
     Build-Tool-Depends: hspec-discover:hspec-discover
     Build-Depends:   base >= 4.8 && < 5
-                   , HUnit
                    , QuickCheck
                    , array
-                   , async
                    , auto-update
                    , bsb-http-chunked                         < 0.1
                    , bytestring                >= 0.9.1.4
@@ -209,11 +205,11 @@
                    , iproute                   >= 1.3.1
                    , network
                    , process
+                   , recv
                    , simple-sendfile           >= 0.2.4    && < 0.3
                    , stm                       >= 2.3
                    , streaming-commons         >= 0.1.10
                    , text
-                   , time
                    , time-manager
                    , unix-compat               >= 0.2
                    , vault
@@ -227,11 +223,13 @@
                    , transformers
 
   if (os(linux) || os(freebsd) || os(darwin)) && flag(allow-sendfilefd)
-    Cpp-Options:   -DSENDFILEFD
-    Build-Depends: unix
+      Cpp-Options:   -DSENDFILEFD
   if os(windows)
-    Cpp-Options:   -DWINDOWS
-    Build-Depends: time
+      Cpp-Options:   -DWINDOWS
+      Build-Depends: time
+  else
+      Build-Depends: unix
+      Other-modules: Network.Wai.Handler.Warp.MultiMap
   if impl(ghc >= 8)
       Default-Extensions:  Strict StrictData
   Default-Language:     Haskell2010
@@ -257,6 +255,7 @@
                   , http-types
                   , network
                   , network
+                  , recv
                   , time-manager
                   , unix-compat
                   , unliftio
