shomei-server-0.3.0.0: src/Shomei/Server/Runtime.hs
-- | Shared Warp lifecycle for standalone and embedded hosts. Rejected connections never
-- wait for a permit. Warp brackets onOpen/onClose even when onOpen returns False; track
-- admitted worker identities so rejected closes cannot release someone else's capacity.
module Shomei.Server.Runtime (hostWarpSettings) where
import Control.Concurrent (myThreadId)
import Control.Concurrent.STM (atomically, newTVarIO, readTVar, writeTVar)
import Control.Monad (when)
import Data.Set qualified as Set
import Data.String (fromString)
import Data.Text qualified as Text
import Network.Wai.Handler.Warp qualified as Warp
import Shomei.Config (ObservabilityConfig (..), ShomeiConfig (..))
import Shomei.Server.Config (ProxyProtocolMode (..), ServerSettings (..))
import Shomei.Server.ExceptionResponse (problemExceptionResponse)
import Shomei.Server.Observability.Logging (logServerError)
hostWarpSettings :: ShomeiConfig -> ServerSettings -> Warp.Settings -> IO Warp.Settings
hostWarpSettings cfg settings base = do
when
(settings.serverMaxConnections <= 0 || settings.serverSlowClientTimeoutSeconds <= 0 || settings.serverSlowClientTimeoutSeconds > maxBound `div` 1000000 || Text.null settings.serverBindHost)
(ioError (userError "host bind, connection limit and slow-client timeout must be finite and positive"))
admitted <- newTVarIO Set.empty
let onOpen _ = do
worker <- myThreadId
atomically do
active <- readTVar admitted
if Set.size active >= settings.serverMaxConnections
then pure False
else do
writeTVar admitted (Set.insert worker active)
pure True
onClose _ = do
worker <- myThreadId
atomically do
active <- readTVar admitted
writeTVar admitted (Set.delete worker active)
proxy = case settings.serverProxyProtocol of
ProxyProtocolOff -> Warp.setProxyProtocolNone
ProxyProtocolRequired -> Warp.setProxyProtocolRequired
onException request err = when (Warp.defaultShouldDisplayException err) (logServerError request err)
pure
$ Warp.setPort settings.serverPort
. Warp.setHost (fromString (Text.unpack settings.serverBindHost))
. Warp.setTimeout settings.serverSlowClientTimeoutSeconds
. Warp.setOnOpen onOpen
. Warp.setOnClose onClose
. Warp.setGracefulShutdownTimeout (Just cfg.observabilityConfig.gracefulShutdownTimeoutSeconds)
. Warp.setServerName "shomei"
. Warp.setOnException onException
. Warp.setOnExceptionResponse problemExceptionResponse
. proxy
$ base