packages feed

warpZ-3.4.10: Network/Wai/Handler/Warp/Settings.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE ImpredicativeTypes #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternGuards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE UnboxedTuples #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE LiberalTypeSynonyms #-}
{-# LANGUAGE LambdaCase #-}

module Network.Wai.Handler.Warp.Settings where

import Control.Exception (SomeException(..), fromException, throw)
import Control.Lens
import qualified Data.ByteString.Builder as Builder
import qualified Data.ByteString.Char8 as C8
import Data.Streaming.Network (HostPreference)
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import GHC.IO (IO (IO), unsafeUnmask)
import GHC.IO.Exception (IOErrorType (..))
import GHC.Prim (fork#)
import qualified Network.HTTP.Types as H
import Network.Socket (SockAddr, Socket, accept)
import Network.Wai
import System.IO (stderr)
import System.IO.Error (ioeGetErrorType)
import System.TimeManager

import Network.Wai.Handler.Warp.Imports
import Network.Wai.Handler.Warp.Types
#if WINDOWS
import Network.Wai.Handler.Warp.Windows (windowsThreadBlockHack)
#endif

#ifdef INCLUDE_WARP_VERSION
import Data.Version (showVersion)
import qualified Paths_warpZ
#endif

-- | 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'
-- and the various \'set\' functions to modify individual fields. For example:
--
-- > setTimeout 20 defaultSettings
data Settings = Settings
    { settingsPort :: Port
    -- ^ Port to listen on. Default value: 3000
    , settingsHost :: HostPreference
    -- ^ 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.
    , settingsOnExceptionResponse :: SomeException -> Response
    -- ^ A function to create `Response` when an exception occurs.
    --
    -- Default: 500, text/plain, \"Something went wrong\"
    --
    -- 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 ()
    -- ^ What to do when a connection is close. Default: do nothing.
    , settingsTimeout :: Int
    -- ^ Timeout value in seconds. Default value: 30
    , settingsManager :: Maybe Manager
    -- ^ Use an existing timeout manager instead of spawning a new one. If used, 'settingsTimeout' is ignored. Default is 'Nothing'
    , settingsFdCacheDuration :: Int
    -- ^ Cache duration time of file descriptors in seconds. 0 means that the cache mechanism is not used. Default value: 0
    , settingsFileInfoCacheDuration :: Int
    -- ^ Cache duration time of file information in seconds. 0 means that the cache mechanism is not used. Default value: 0
    , settingsBeforeMainLoop :: IO ()
    -- ^ Code to run after the listening socket is ready but before entering
    -- the main event loop. Useful for signaling to tests that they can start
    -- running, or to drop permissions after binding to a restricted port.
    --
    -- Default: do nothing.
    --
    -- Since 1.3.6
    , settingsFork :: ((forall a. IO a -> IO a) -> IO ()) -> IO ()
    -- ^ Code to fork a new thread to accept a connection.
    --
    -- This may be useful if you need OS bound threads, or if
    -- you wish to develop an alternative threading model.
    --
    -- Default: 'defaultFork'
    --
    -- 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.
    --
    -- This is useful for writing HTTP proxies.
    --
    -- Default: False
    --
    -- Since 2.0.3
    , settingsInstallShutdownHandler :: IO () -> IO ()
    -- ^ An action to install a handler (e.g. Unix signal handler)
    -- to close a listen socket.
    -- The first argument is an action to close the listen socket.
    --
    -- Default: no action
    --
    -- Since 3.0.1
    , settingsServerName :: ByteString
    -- ^ Default server name if application does not set one.
    --
    -- Since 3.0.2
    , settingsMaximumBodyFlush :: Maybe Int
    -- ^ See @setMaximumBodyFlush@.
    --
    -- Since 3.0.3
    , settingsProxyProtocol :: ProxyProtocol
    -- ^ Specify usage of the PROXY protocol.
    --
    -- Since 3.0.5
    , settingsSlowlorisSize :: Int
    -- ^ Size of bytes read to prevent Slowloris protection. Default value: 2048
    --
    -- Since 3.1.2
    , settingsHTTP2Enabled :: Bool
    -- ^ Whether to enable HTTP2 ALPN/upgrades. Default: True
    --
    -- Since 3.1.7
    , settingsLogger :: Request -> H.Status -> Maybe Integer -> IO ()
    -- ^ A log function. Default: no action.
    --
    -- Since 3.1.10
    , settingsServerPushLogger :: Request -> ByteString -> Integer -> IO ()
    -- ^ A HTTP/2 server push log function. Default: no action.
    --
    -- 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
    , 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
    , 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
    , settingsMaxTotalHeaderLength :: Int
    -- ^ Determines the maximum header size that Warp will tolerate when using HTTP/1.x.
    --
    -- Since 3.3.8
    , settingsAltSvc :: Maybe ByteString
    -- ^ Specify the header value of Alternative Services (AltSvc:).
    --
    -- Default: Nothing
    --
    -- Since 3.3.11
    , settingsMaxBuilderResponseBufferSize :: Int
    -- ^ Determines the maxium buffer size when sending `Builder` responses
    -- (See `responseBuilder`).
    --
    -- When sending a builder response warp uses a 16 KiB buffer to write the
    -- builder to. When that buffer is too small to fit the builder warp will
    -- free it and create a new one that will fit the builder.
    --
    -- To protect against allocating too large a buffer warp will error if the
    -- builder requires more than this maximum.
    --
    -- Default: 1049_000_000 = 1 MiB.
    --
    -- Since 3.3.22
    }

class HasSettings a where
    settings :: Lens' a Settings
    port :: Lens' a Port
    port  = settings . port
    host :: Lens' a HostPreference
    host = settings . host
    onException :: Lens' a (Maybe Request -> SomeException -> IO ())
    onException = settings . onException
    onExceptionResponse :: Lens' a (SomeException -> Response)
    onExceptionResponse = settings . onExceptionResponse
    onOpen :: Lens' a (SockAddr -> IO Bool)
    onOpen = settings . onOpen
    onClose :: Lens' a (SockAddr -> IO ())
    onClose = settings . onClose
    timeoutL :: Lens' a Int
    timeoutL = settings . timeoutL
    manager :: Lens' a (Maybe Manager)
    manager = settings . manager
    fdCacheDuration :: Lens' a Int
    fdCacheDuration = settings . fdCacheDuration
    fileInfoCacheDuration :: Lens' a Int
    fileInfoCacheDuration = settings . fileInfoCacheDuration
    beforeMainLoop :: Lens' a (IO ())
    beforeMainLoop = settings . beforeMainLoop
    forkL :: Lens' a (((forall x. IO x -> IO x) -> IO ()) -> IO ())
    forkL = settings . forkL
    acceptL :: Lens' a (Socket -> IO (Socket, SockAddr))
    acceptL = settings . acceptL
    noParsePath :: Lens' a Bool
    noParsePath = settings . noParsePath
    installShutdownHandler :: Lens' a (IO () -> IO ())
    installShutdownHandler = settings . installShutdownHandler
    serverName :: Lens' a ByteString
    serverName = settings . serverName
    maximumBodyFlush :: Lens' a (Maybe Int)
    maximumBodyFlush = settings . maximumBodyFlush
    slowlorisSize :: Lens' a Int
    slowlorisSize = settings . slowlorisSize
    http2Enabled :: Lens' a Bool
    http2Enabled = settings . http2Enabled
    logger :: Lens' a (Request -> H.Status -> Maybe Integer -> IO ())
    logger = settings . logger
    serverPushLogger :: Lens' a (Request -> ByteString -> Integer -> IO ())
    serverPushLogger = settings . serverPushLogger
    gracefulShutdownTimeout :: Lens' a (Maybe Int)
    gracefulShutdownTimeout = settings . gracefulShutdownTimeout
    gracefulCloseTimeout1 :: Lens' a Int
    gracefulCloseTimeout1 = settings . gracefulCloseTimeout1
    gracefulCloseTimeout2 :: Lens' a Int
    gracefulCloseTimeout2 = settings . gracefulCloseTimeout2
    maxTotalHeaderLength :: Lens' a Int
    maxTotalHeaderLength = settings . maxTotalHeaderLength
    altSvc :: Lens' a (Maybe ByteString)
    altSvc = settings . altSvc
    maxBuilderResponseBufferSize :: Lens' a Int
    maxBuilderResponseBufferSize = settings . maxBuilderResponseBufferSize

instance HasSettings Settings where
    settings = id
    port f s = fmap (\x -> s { settingsPort = x }) (f (settingsPort s))
    host f s = fmap (\x -> s { settingsHost = x }) (f (settingsHost s))
    onException f s = fmap (\x -> s { settingsOnException = x }) (f (settingsOnException s))
    onExceptionResponse f s = fmap (\x -> s { settingsOnExceptionResponse = x }) (f (settingsOnExceptionResponse s))
    onOpen f s = fmap (\x -> s { settingsOnOpen = x }) (f (settingsOnOpen s))
    onClose f s = fmap (\x -> s { settingsOnClose = x }) (f (settingsOnClose s))
    timeoutL f s = fmap (\x -> s { settingsTimeout = x }) (f (settingsTimeout s))
    manager f s = fmap (\x -> s { settingsManager = x }) (f (settingsManager s))
    fdCacheDuration f s = fmap (\x -> s { settingsFdCacheDuration = x }) (f (settingsFdCacheDuration s))
    fileInfoCacheDuration f s = fmap (\x -> s { settingsFileInfoCacheDuration = x }) (f (settingsFileInfoCacheDuration s))
    beforeMainLoop f s = fmap (\x -> s { settingsBeforeMainLoop = x }) (f (settingsBeforeMainLoop s))
    forkL f s = fmap (\x -> s { settingsFork = x }) (f (settingsFork s))
    acceptL f s = fmap (\x -> s { settingsAccept = x }) (f (settingsAccept s))
    noParsePath f s = fmap (\x -> s { settingsNoParsePath = x }) (f (settingsNoParsePath s))
    installShutdownHandler f s = fmap (\x -> s { settingsInstallShutdownHandler = x }) (f (settingsInstallShutdownHandler s))
    serverName f s = fmap (\x -> s { settingsServerName = x }) (f (settingsServerName s))
    maximumBodyFlush f s = fmap (\x -> s { settingsMaximumBodyFlush = x }) (f (settingsMaximumBodyFlush s))
    slowlorisSize f s = fmap (\x -> s { settingsSlowlorisSize = x }) (f (settingsSlowlorisSize s))
    http2Enabled f s = fmap (\x -> s { settingsHTTP2Enabled = x }) (f (settingsHTTP2Enabled s))
    logger f s = fmap (\x -> s { settingsLogger = x }) (f (settingsLogger s))
    serverPushLogger f s = fmap (\x -> s { settingsServerPushLogger = x }) (f (settingsServerPushLogger s))
    gracefulShutdownTimeout f s = fmap (\x -> s { settingsGracefulShutdownTimeout = x }) (f (settingsGracefulShutdownTimeout s))
    gracefulCloseTimeout1 f s = fmap (\x -> s { settingsGracefulCloseTimeout1 = x }) (f (settingsGracefulCloseTimeout1 s))
    gracefulCloseTimeout2 f s = fmap (\x -> s { settingsGracefulCloseTimeout2 = x }) (f (settingsGracefulCloseTimeout2 s))
    maxTotalHeaderLength f s = fmap (\x -> s { settingsMaxTotalHeaderLength = x }) (f (settingsMaxTotalHeaderLength s))
    altSvc f s = fmap (\x -> s { settingsAltSvc = x }) (f (settingsAltSvc s))
    maxBuilderResponseBufferSize f s = fmap (\x -> s { settingsMaxBuilderResponseBufferSize = x }) (f (settingsMaxBuilderResponseBufferSize s))

class AsSettings a where
    _Settings :: Prism' a Settings

instance AsSettings Settings where
    _Settings = id

-- | Specify usage of the PROXY protocol.
data ProxyProtocol
    = -- | See @setProxyProtocolNone@.
      ProxyProtocolNone
    | -- | See @setProxyProtocolRequired@.
      ProxyProtocolRequired
    | -- | See @setProxyProtocolOptional@.
      ProxyProtocolOptional

class HasProxyProtocol a where
    proxyProtocol :: Lens' a ProxyProtocol

instance HasProxyProtocol ProxyProtocol where
    proxyProtocol = id

instance HasProxyProtocol Settings where
    proxyProtocol f s = fmap (\x -> s { settingsProxyProtocol = x }) (f (settingsProxyProtocol s))

class AsProxyProtocol a where
    _ProxyProtocol :: Prism' a ProxyProtocol
    _ProxyProtocolNone :: Prism' a ()
    _ProxyProtocolNone = _ProxyProtocol . _ProxyProtocolNone
    _ProxyProtocolRequired :: Prism' a ()
    _ProxyProtocolRequired = _ProxyProtocol . _ProxyProtocolRequired
    _ProxyProtocolOptional :: Prism' a ()
    _ProxyProtocolOptional = _ProxyProtocol . _ProxyProtocolOptional

instance AsProxyProtocol ProxyProtocol where
    _ProxyProtocol = id
    _ProxyProtocolNone =
        prism'
            (\() -> ProxyProtocolNone)
            (\case
                ProxyProtocolNone -> Just ()
                _ -> Nothing)
    _ProxyProtocolRequired =
        prism'
            (\() -> ProxyProtocolRequired)
            (\case
                ProxyProtocolRequired -> Just ()
                _ -> Nothing)
    _ProxyProtocolOptional =
        prism'
            (\() -> ProxyProtocolOptional)
            (\case
                ProxyProtocolOptional -> Just ()
                _ -> Nothing)

-- | The default settings for the Warp server. See the individual settings for
-- the default value.
defaultSettings :: Settings
defaultSettings =
    Settings
        { settingsPort = 3000
        , settingsHost = "*4"
        , settingsOnException = defaultOnException
        , settingsOnExceptionResponse = defaultOnExceptionResponse
        , settingsOnOpen = const $ return True
        , settingsOnClose = const $ return ()
        , settingsTimeout = 30
        , settingsManager = Nothing
        , settingsFdCacheDuration = 0
        , settingsFileInfoCacheDuration = 0
        , settingsBeforeMainLoop = return ()
        , settingsFork = defaultFork
        , settingsAccept = defaultAccept
        , settingsNoParsePath = False
        , settingsInstallShutdownHandler = const $ return ()
        , settingsServerName = C8.pack $ "Warp/" ++ warpVersion
        , settingsMaximumBodyFlush = Just 8192
        , settingsProxyProtocol = ProxyProtocolNone
        , settingsSlowlorisSize = 2048
        , settingsHTTP2Enabled = True
        , settingsLogger = \_ _ _ -> return ()
        , settingsServerPushLogger = \_ _ _ -> return ()
        , settingsGracefulShutdownTimeout = Nothing
        , settingsGracefulCloseTimeout1 = 0
        , settingsGracefulCloseTimeout2 = 2000
        , settingsMaxTotalHeaderLength = 50 * 1024
        , settingsAltSvc = Nothing
        , settingsMaxBuilderResponseBufferSize = 1049000000
        }

-- | Apply the logic provided by 'defaultOnException' to determine if an
-- 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
defaultShouldDisplayException :: SomeException -> Bool
defaultShouldDisplayException se
    | Just (_ :: InvalidRequest) <- fromException se = False
    | Just (ioeGetErrorType -> et) <- fromException se
    , et == ResourceVanished || et == InvalidArgument =
        False
    | isAsyncException se = False
    | otherwise = True

-- | Printing an exception to standard error
--   if `defaultShouldDisplayException` returns `True`.
--
-- Since: 3.1.0
defaultOnException :: Maybe Request -> SomeException -> IO ()
defaultOnException _ e =
    when (defaultShouldDisplayException e) $
        TIO.hPutStrLn stderr $
            T.pack $
                show e

-- | Sending 400 for bad requests.
--   Sending 500 for internal server errors.
-- Since: 3.1.0
--   Sending 413 for too large payload.
--   Sending 431 for too large headers.
-- Since 3.2.27
defaultOnExceptionResponse :: SomeException -> Response
defaultOnExceptionResponse e
    | isAsyncException e = throw e
    | Just PayloadTooLarge <- fromException e =
        responseLBS
            H.status413
            [(H.hContentType, "text/plain; charset=utf-8")]
            "Payload too large"
    | Just RequestHeaderFieldsTooLarge <- fromException e =
        responseLBS
            H.status431
            [(H.hContentType, "text/plain; charset=utf-8")]
            "Request header fields too large"
    | Just (_ :: InvalidRequest) <- fromException e =
        responseLBS
            H.badRequest400
            [(H.hContentType, "text/plain; charset=utf-8")]
            "Bad Request"
    | otherwise =
        responseLBS
            H.internalServerError500
            [(H.hContentType, "text/plain; charset=utf-8")]
            "Something went wrong"

-- | Exception handler for the debugging purpose.
--   500, text/plain, a showed exception.
--
-- Since: 2.0.3.2
exceptionResponseForDebug :: SomeException -> Response
exceptionResponseForDebug e =
    responseBuilder
        H.internalServerError500
        [(H.hContentType, "text/plain; charset=utf-8")]
        $ "Exception: " <> Builder.stringUtf8 (show e)

-- | 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
-- a minor optimization.
--
-- For inspiration of this function, see @rawForkIO@ in the @async@ package.
--
-- @since 3.3.17
defaultFork :: ((forall a. IO a -> IO a) -> IO ()) -> IO ()
defaultFork io =
    IO $ \s0 ->
#if __GLASGOW_HASKELL__ >= 904
        case io unsafeUnmask of
            IO io' ->
                case fork# io' s0 of
                    (# s1, _tid #) -> (# s1, () #)
#else
        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

-- | The version of Warp.
warpVersion :: String
warpVersion =
#ifdef INCLUDE_WARP_VERSION
  showVersion Paths_warpZ.version
#else
  "unknown"
#endif