{-# LANGUAGE TupleSections #-}
module Ecluse.Server (
ServerConfig (..),
mkServerConfig,
defaultPort,
MountBinding (..),
application,
tracedApplication,
runWarp,
probeApplication,
DrainSignal,
newDrainSignal,
neverDraining,
beginDrain,
isDraining,
ShutdownDrainTimeout (..),
defaultShutdownDrainTimeout,
InteractiveHalt (..),
defaultInteractiveHalt,
withInteractiveHalt,
serverMiddleware,
RequestSizeLimit (..),
defaultRequestSizeLimit,
) where
import Network.HTTP.Types (Method, Status, hConnection, hContentType, methodHead, status200, status404, status501, status503)
import Network.Wai (Application, Middleware, Request, Response, ResponseReceived, mapResponseHeaders, modifyResponse, pathInfo, requestMethod, responseLBS)
import Network.Wai.Handler.Warp qualified as Warp
import Network.Wai.Middleware.RealIp (realIp)
import Network.Wai.Middleware.RequestSizeLimit (defaultRequestSizeLimitSettings, requestSizeLimitMiddleware, setMaxLengthForRequest)
import Network.Wai.Middleware.Timeout (timeout)
import System.Exit (ExitCode (ExitFailure))
import System.IO (hIsTerminalDevice, isEOF)
import System.Posix.Process (exitImmediately)
import System.Posix.Signals (Handler (CatchOnce), installHandler, sigINT, sigTERM)
import UnliftIO.Async (withAsync)
import Ecluse.Core.Server.Context (
MountBinding (..),
RequestCtx (RequestCtx),
runHandler,
)
import Ecluse.Core.Server.Pipeline (headPackument, headTarball, servePackument, servePublish, serveTarball)
import Ecluse.Core.Server.Response (MountRenderer, RenderedBody (RenderedBody), renderError)
import Ecluse.Core.Server.Route (Route (..))
import Ecluse.Core.Worker (heartbeatHealthyNow)
import Ecluse.Env (Env, envDdContext, envLogEnv, envTelemetry, envWorkerHeartbeat, serveRuntimeOf)
import Ecluse.Telemetry.Correlation (ddPayloadNow)
import Ecluse.Telemetry.Tracing (telemetryWaiMiddleware)
data ServerConfig = ServerConfig
{ ServerConfig -> Int
scPort :: Int
, ServerConfig -> [MountBinding]
scMounts :: [MountBinding]
, ServerConfig -> RequestSizeLimit
scSizeLimit :: RequestSizeLimit
, ServerConfig -> DrainSignal
scDrain :: DrainSignal
, ServerConfig -> ShutdownDrainTimeout
scDrainTimeout :: ShutdownDrainTimeout
, ServerConfig -> IO Bool
scCheckReady :: IO Bool
}
mkServerConfig :: [MountBinding] -> ServerConfig
mkServerConfig :: [MountBinding] -> ServerConfig
mkServerConfig [MountBinding]
mounts =
ServerConfig
{ scPort :: Int
scPort = Int
defaultPort
, scMounts :: [MountBinding]
scMounts = [MountBinding]
mounts
, scSizeLimit :: RequestSizeLimit
scSizeLimit = RequestSizeLimit
defaultRequestSizeLimit
, scDrain :: DrainSignal
scDrain = DrainSignal
neverDraining
, scDrainTimeout :: ShutdownDrainTimeout
scDrainTimeout = ShutdownDrainTimeout
defaultShutdownDrainTimeout
, scCheckReady :: IO Bool
scCheckReady = Bool -> IO Bool
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
True
}
defaultPort :: Int
defaultPort :: Int
defaultPort = Int
4873
newtype RequestSizeLimit = RequestSizeLimit Word64
deriving stock (RequestSizeLimit -> RequestSizeLimit -> Bool
(RequestSizeLimit -> RequestSizeLimit -> Bool)
-> (RequestSizeLimit -> RequestSizeLimit -> Bool)
-> Eq RequestSizeLimit
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: RequestSizeLimit -> RequestSizeLimit -> Bool
== :: RequestSizeLimit -> RequestSizeLimit -> Bool
$c/= :: RequestSizeLimit -> RequestSizeLimit -> Bool
/= :: RequestSizeLimit -> RequestSizeLimit -> Bool
Eq, Int -> RequestSizeLimit -> ShowS
[RequestSizeLimit] -> ShowS
RequestSizeLimit -> String
(Int -> RequestSizeLimit -> ShowS)
-> (RequestSizeLimit -> String)
-> ([RequestSizeLimit] -> ShowS)
-> Show RequestSizeLimit
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> RequestSizeLimit -> ShowS
showsPrec :: Int -> RequestSizeLimit -> ShowS
$cshow :: RequestSizeLimit -> String
show :: RequestSizeLimit -> String
$cshowList :: [RequestSizeLimit] -> ShowS
showList :: [RequestSizeLimit] -> ShowS
Show)
defaultRequestSizeLimit :: RequestSizeLimit
defaultRequestSizeLimit :: RequestSizeLimit
defaultRequestSizeLimit = Word64 -> RequestSizeLimit
RequestSizeLimit (Word64
25 Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
* Word64
1024 Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
* Word64
1024)
data DrainSignal = DrainSignal
{ DrainSignal -> STM Bool
drainState :: STM Bool
, DrainSignal -> STM ()
drainRaise :: STM ()
}
newDrainSignal :: IO DrainSignal
newDrainSignal :: IO DrainSignal
newDrainSignal = do
tvar <- Bool -> IO (TVar Bool)
forall (m :: * -> *) a. MonadIO m => a -> m (TVar a)
newTVarIO Bool
False
pure
DrainSignal
{ drainState = readTVar tvar
, drainRaise = writeTVar tvar True
}
neverDraining :: DrainSignal
neverDraining :: DrainSignal
neverDraining =
DrainSignal
{ drainState :: STM Bool
drainState = Bool -> STM Bool
forall a. a -> STM a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
False
, drainRaise :: STM ()
drainRaise = () -> STM ()
forall a. a -> STM a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
}
beginDrain :: DrainSignal -> IO ()
beginDrain :: DrainSignal -> IO ()
beginDrain = STM () -> IO ()
forall (m :: * -> *) a. MonadIO m => STM a -> m a
atomically (STM () -> IO ())
-> (DrainSignal -> STM ()) -> DrainSignal -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. DrainSignal -> STM ()
drainRaise
isDraining :: DrainSignal -> IO Bool
isDraining :: DrainSignal -> IO Bool
isDraining = STM Bool -> IO Bool
forall (m :: * -> *) a. MonadIO m => STM a -> m a
atomically (STM Bool -> IO Bool)
-> (DrainSignal -> STM Bool) -> DrainSignal -> IO Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. DrainSignal -> STM Bool
drainState
newtype ShutdownDrainTimeout = ShutdownDrainTimeout Int
deriving stock (ShutdownDrainTimeout -> ShutdownDrainTimeout -> Bool
(ShutdownDrainTimeout -> ShutdownDrainTimeout -> Bool)
-> (ShutdownDrainTimeout -> ShutdownDrainTimeout -> Bool)
-> Eq ShutdownDrainTimeout
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ShutdownDrainTimeout -> ShutdownDrainTimeout -> Bool
== :: ShutdownDrainTimeout -> ShutdownDrainTimeout -> Bool
$c/= :: ShutdownDrainTimeout -> ShutdownDrainTimeout -> Bool
/= :: ShutdownDrainTimeout -> ShutdownDrainTimeout -> Bool
Eq, Int -> ShutdownDrainTimeout -> ShowS
[ShutdownDrainTimeout] -> ShowS
ShutdownDrainTimeout -> String
(Int -> ShutdownDrainTimeout -> ShowS)
-> (ShutdownDrainTimeout -> String)
-> ([ShutdownDrainTimeout] -> ShowS)
-> Show ShutdownDrainTimeout
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ShutdownDrainTimeout -> ShowS
showsPrec :: Int -> ShutdownDrainTimeout -> ShowS
$cshow :: ShutdownDrainTimeout -> String
show :: ShutdownDrainTimeout -> String
$cshowList :: [ShutdownDrainTimeout] -> ShowS
showList :: [ShutdownDrainTimeout] -> ShowS
Show)
defaultShutdownDrainTimeout :: ShutdownDrainTimeout
defaultShutdownDrainTimeout :: ShutdownDrainTimeout
defaultShutdownDrainTimeout = Int -> ShutdownDrainTimeout
ShutdownDrainTimeout Int
30
data InteractiveHalt = InteractiveHalt
{ InteractiveHalt -> IO Bool
haltOnInteractive :: IO Bool
, InteractiveHalt -> IO ()
awaitHaltSignal :: IO ()
, InteractiveHalt -> IO ()
halt :: IO ()
}
defaultInteractiveHalt :: InteractiveHalt
defaultInteractiveHalt :: InteractiveHalt
defaultInteractiveHalt =
InteractiveHalt
{ haltOnInteractive :: IO Bool
haltOnInteractive = Handle -> IO Bool
hIsTerminalDevice Handle
stdin
, awaitHaltSignal :: IO ()
awaitHaltSignal = IO ()
awaitStdinEof
, halt :: IO ()
halt = ExitCode -> IO ()
forall a. ExitCode -> IO a
exitImmediately (Int -> ExitCode
ExitFailure Int
130)
}
where
awaitStdinEof :: IO ()
awaitStdinEof :: IO ()
awaitStdinEof = IO ()
go
where
go :: IO ()
go =
IO Bool
isEOF IO Bool -> (Bool -> IO ()) -> IO ()
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Bool
True -> IO ()
forall (f :: * -> *). Applicative f => f ()
pass
Bool
False -> IO Text -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void IO Text
forall (m :: * -> *). MonadIO m => m Text
getLine IO () -> IO () -> IO ()
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> IO ()
go
withInteractiveHalt :: InteractiveHalt -> IO a -> IO a
withInteractiveHalt :: forall a. InteractiveHalt -> IO a -> IO a
withInteractiveHalt InteractiveHalt
ih IO a
action =
InteractiveHalt -> IO Bool
haltOnInteractive InteractiveHalt
ih IO Bool -> (Bool -> IO a) -> IO a
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Bool
False -> IO a
action
Bool
True -> IO () -> (Async () -> IO a) -> IO a
forall (m :: * -> *) a b.
MonadUnliftIO m =>
m a -> (Async a -> m b) -> m b
withAsync (InteractiveHalt -> IO ()
awaitHaltSignal InteractiveHalt
ih IO () -> IO () -> IO ()
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> InteractiveHalt -> IO ()
halt InteractiveHalt
ih) (IO a -> Async () -> IO a
forall a b. a -> b -> a
const IO a
action)
application :: ServerConfig -> Env -> Application
application :: ServerConfig -> Env -> Application
application ServerConfig
cfg Env
env = ServerConfig -> Middleware
serverMiddleware ServerConfig
cfg (ServerConfig -> Env -> Application
dispatch ServerConfig
cfg Env
env)
tracedApplication :: ServerConfig -> Env -> IO Application
tracedApplication :: ServerConfig -> Env -> IO Application
tracedApplication ServerConfig
cfg Env
env = do
traceMiddleware <- Telemetry -> IO Middleware
telemetryWaiMiddleware (Env -> Telemetry
envTelemetry Env
env)
pure (traceMiddleware (application cfg env))
dispatch :: ServerConfig -> Env -> Application
dispatch :: ServerConfig -> Env -> Application
dispatch ServerConfig
cfg Env
env Request
request Response -> IO ResponseReceived
respond =
case ByteString
-> [MountBinding] -> [Text] -> Maybe (MountBinding, Route)
matchMount (Request -> ByteString
requestMethod Request
request) (ServerConfig -> [MountBinding]
scMounts ServerConfig
cfg) (Request -> [Text]
pathInfo Request
request) of
Just (MountBinding
binding, Route
classified) -> Env -> MountBinding -> Route -> Application
serve Env
env MountBinding
binding Route
classified Request
request Response -> IO ResponseReceived
respond
Maybe (MountBinding, Route)
Nothing -> DrainSignal -> IO Bool -> IO Bool -> Application
probeApplication (ServerConfig -> DrainSignal
scDrain ServerConfig
cfg) (ServerConfig -> IO Bool
scCheckReady ServerConfig
cfg) (WorkerHeartbeat -> IO Bool
heartbeatHealthyNow (Env -> WorkerHeartbeat
envWorkerHeartbeat Env
env)) Request
request Response -> IO ResponseReceived
respond
serve :: Env -> MountBinding -> Route -> Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived
serve :: Env -> MountBinding -> Route -> Application
serve Env
env MountBinding
binding Route
classified Request
request Response -> IO ResponseReceived
respond =
case Route
classified of
Packument PackageName
name
| Bool
isHead -> Handler ResponseReceived -> IO ResponseReceived
forall {b}. Handler b -> IO b
run (PackageName
-> Request
-> (Response -> IO ResponseReceived)
-> Handler ResponseReceived
headPackument PackageName
name Request
request Response -> IO ResponseReceived
respond)
| Bool
otherwise -> Handler ResponseReceived -> IO ResponseReceived
forall {b}. Handler b -> IO b
run (PackageName
-> Request
-> (Response -> IO ResponseReceived)
-> Handler ResponseReceived
servePackument PackageName
name Request
request Response -> IO ResponseReceived
respond)
Tarball PackageName
name Version
version Filename
filename
| Bool
isHead -> Handler ResponseReceived -> IO ResponseReceived
forall {b}. Handler b -> IO b
run (PackageName
-> Version
-> Filename
-> Request
-> (Response -> IO ResponseReceived)
-> Handler ResponseReceived
headTarball PackageName
name Version
version Filename
filename Request
request Response -> IO ResponseReceived
respond)
| Bool
otherwise -> Handler ResponseReceived -> IO ResponseReceived
forall {b}. Handler b -> IO b
run (PackageName
-> Version
-> Filename
-> Request
-> (Response -> IO ResponseReceived)
-> Handler ResponseReceived
serveTarball PackageName
name Version
version Filename
filename Request
request Response -> IO ResponseReceived
respond)
Publish PackageName
name -> Handler ResponseReceived -> IO ResponseReceived
forall {b}. Handler b -> IO b
run (PackageName
-> Request
-> (Response -> IO ResponseReceived)
-> Handler ResponseReceived
servePublish PackageName
name Request
request Response -> IO ResponseReceived
respond)
Route
_ -> Response -> IO ResponseReceived
respond (MountRenderer -> Route -> Response
renderRoute (MountBinding -> MountRenderer
bindingRenderer MountBinding
binding) Route
classified)
where
run :: Handler b -> IO b
run Handler b
action = do
dd <- DdContext -> IO SimpleLogPayload
forall (m :: * -> *). MonadIO m => DdContext -> m SimpleLogPayload
ddPayloadNow (Env -> DdContext
envDdContext Env
env)
runHandler (envLogEnv env) dd ctx action
ctx :: RequestCtx
ctx :: RequestCtx
ctx = ServeRuntime -> MountBinding -> RequestCtx
RequestCtx (Env -> ServeRuntime
serveRuntimeOf Env
env) MountBinding
binding
isHead :: Bool
isHead :: Bool
isHead = Request -> ByteString
requestMethod Request
request ByteString -> ByteString -> Bool
forall a. Eq a => a -> a -> Bool
== ByteString
methodHead
matchMount :: Method -> [MountBinding] -> [Text] -> Maybe (MountBinding, Route)
matchMount :: ByteString
-> [MountBinding] -> [Text] -> Maybe (MountBinding, Route)
matchMount ByteString
method [MountBinding]
mounts [Text]
segments = [Maybe (MountBinding, Route)] -> Maybe (MountBinding, Route)
forall (t :: * -> *) (f :: * -> *) a.
(Foldable t, Alternative f) =>
t (f a) -> f a
asum ((MountBinding -> Maybe (MountBinding, Route))
-> [MountBinding] -> [Maybe (MountBinding, Route)]
forall a b. (a -> b) -> [a] -> [b]
map MountBinding -> Maybe (MountBinding, Route)
match [MountBinding]
mounts)
where
match :: MountBinding -> Maybe (MountBinding, Route)
match :: MountBinding -> Maybe (MountBinding, Route)
match MountBinding
binding =
(MountBinding
binding,) (Route -> (MountBinding, Route))
-> ([Text] -> Route) -> [Text] -> (MountBinding, Route)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. MountBinding -> Classifier
bindingClassifier MountBinding
binding ByteString
method
([Text] -> (MountBinding, Route))
-> Maybe [Text] -> Maybe (MountBinding, Route)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [Text] -> [Text] -> Maybe [Text]
stripPrefixSegments (NonEmpty Text -> [Text]
forall a. NonEmpty a -> [a]
forall (t :: * -> *) a. Foldable t => t a -> [a]
toList (MountBinding -> NonEmpty Text
bindingPrefix MountBinding
binding)) [Text]
segments
stripPrefixSegments :: [Text] -> [Text] -> Maybe [Text]
stripPrefixSegments :: [Text] -> [Text] -> Maybe [Text]
stripPrefixSegments [] [Text]
segs = [Text] -> Maybe [Text]
forall a. a -> Maybe a
Just ([Text] -> [Text]
dropTrailingSlash [Text]
segs)
stripPrefixSegments (Text
p : [Text]
ps) (Text
s : [Text]
ss)
| Text
p Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
s = [Text] -> [Text] -> Maybe [Text]
stripPrefixSegments [Text]
ps [Text]
ss
stripPrefixSegments [Text]
_ [Text]
_ = Maybe [Text]
forall a. Maybe a
Nothing
dropTrailingSlash :: [Text] -> [Text]
dropTrailingSlash :: [Text] -> [Text]
dropTrailingSlash [Text
""] = []
dropTrailingSlash (Text
x : [Text]
xs) = Text
x Text -> [Text] -> [Text]
forall a. a -> [a] -> [a]
: [Text] -> [Text]
dropTrailingSlash [Text]
xs
dropTrailingSlash [] = []
renderRoute :: MountRenderer -> Route -> Response
renderRoute :: MountRenderer -> Route -> Response
renderRoute MountRenderer
renderer = \case
Route
Ping -> Response
pong
Route
Search -> MountRenderer -> Status -> Text -> Response
renderedError MountRenderer
renderer Status
status501 Text
"search is not supported by this proxy; use the public registry's website to discover packages"
Packument PackageName
_ -> MountRenderer -> Status -> Text -> Response
renderedError MountRenderer
renderer Status
status501 Text
notYetServedMessage
Tarball{} -> MountRenderer -> Status -> Text -> Response
renderedError MountRenderer
renderer Status
status501 Text
notYetServedMessage
Publish PackageName
_ -> MountRenderer -> Status -> Text -> Response
renderedError MountRenderer
renderer Status
status501 Text
notYetServedMessage
Route
Unsupported -> MountRenderer -> Status -> Text -> Response
renderedError MountRenderer
renderer Status
status404 Text
"not found"
where
notYetServedMessage :: Text
notYetServedMessage :: Text
notYetServedMessage = Text
"this route is recognised but not yet served by this proxy"
renderedError :: MountRenderer -> Status -> Text -> Response
renderedError :: MountRenderer -> Status -> Text -> Response
renderedError MountRenderer
renderer Status
status Text
message =
let RenderedBody ByteString
contentType ByteString
body = MountRenderer -> Maybe HelpMessage -> Text -> RenderedBody
renderError MountRenderer
renderer Maybe HelpMessage
forall a. Maybe a
Nothing Text
message
in Status -> ResponseHeaders -> ByteString -> Response
responseLBS Status
status [(HeaderName
hContentType, ByteString
contentType)] ByteString
body
pong :: Response
pong :: Response
pong = Status -> ByteString -> Response
jsonResponse Status
status200 ByteString
"{}"
notFound :: Response
notFound :: Response
notFound =
Status -> ResponseHeaders -> ByteString -> Response
responseLBS Status
status404 [(HeaderName
hContentType, ByteString
"text/plain; charset=utf-8")] ByteString
"Not Found\n"
probeApplication :: DrainSignal -> IO Bool -> IO Bool -> Application
probeApplication :: DrainSignal -> IO Bool -> IO Bool -> Application
probeApplication DrainSignal
drain IO Bool
checkReady IO Bool
checkLiveness Request
request Response -> IO ResponseReceived
respond =
case Request -> [Text]
pathInfo Request
request of
[Text
"livez"] -> do
alive <- IO Bool
checkLiveness
if alive
then respond (jsonResponse status200 "{\"status\":\"live\"}")
else respond (jsonResponse status503 "{\"status\":\"liveness check failed\"}")
[Text
"readyz"] -> DrainSignal -> IO Bool -> IO Response
readiness DrainSignal
drain IO Bool
checkReady IO Response
-> (Response -> IO ResponseReceived) -> IO ResponseReceived
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Response -> IO ResponseReceived
respond
[Text]
_ -> Response -> IO ResponseReceived
respond Response
notFound
readiness :: DrainSignal -> IO Bool -> IO Response
readiness :: DrainSignal -> IO Bool -> IO Response
readiness DrainSignal
drain IO Bool
checkReady =
DrainSignal -> IO Bool
isDraining DrainSignal
drain IO Bool -> (Bool -> IO Response) -> IO Response
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Bool
True -> Response -> IO Response
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Status -> ByteString -> Response
jsonResponse Status
status503 ByteString
"{\"status\":\"draining\"}")
Bool
False ->
IO Bool
checkReady IO Bool -> (Bool -> Response) -> IO Response
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> \case
Bool
False -> Status -> ByteString -> Response
jsonResponse Status
status503 ByteString
"{\"status\":\"awaiting startup readiness\"}"
Bool
True -> Status -> ByteString -> Response
jsonResponse Status
status200 ByteString
"{\"status\":\"ready\"}"
jsonResponse :: Status -> LByteString -> Response
jsonResponse :: Status -> ByteString -> Response
jsonResponse Status
status =
Status -> ResponseHeaders -> ByteString -> Response
responseLBS Status
status [(HeaderName
hContentType, ByteString
"application/json")]
serverMiddleware :: ServerConfig -> Middleware
serverMiddleware :: ServerConfig -> Middleware
serverMiddleware ServerConfig
cfg =
RequestSizeLimit -> Middleware
sizeLimitMiddleware (ServerConfig -> RequestSizeLimit
scSizeLimit ServerConfig
cfg)
Middleware -> Middleware -> Middleware
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Middleware
realIp
Middleware -> Middleware -> Middleware
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Middleware
timeout Int
timeoutSeconds
Middleware -> Middleware -> Middleware
forall b c a. (b -> c) -> (a -> b) -> a -> c
. DrainSignal -> Middleware
goingAwayMiddleware (ServerConfig -> DrainSignal
scDrain ServerConfig
cfg)
goingAwayMiddleware :: DrainSignal -> Middleware
goingAwayMiddleware :: DrainSignal -> Middleware
goingAwayMiddleware DrainSignal
drain Application
app Request
request Response -> IO ResponseReceived
respond = do
draining <- DrainSignal -> IO Bool
isDraining DrainSignal
drain
if draining
then modifyResponse closeConnection app request respond
else app request respond
where
closeConnection :: Response -> Response
closeConnection :: Response -> Response
closeConnection = (ResponseHeaders -> ResponseHeaders) -> Response -> Response
mapResponseHeaders ((HeaderName
hConnection, ByteString
"close") Header -> ResponseHeaders -> ResponseHeaders
forall a. a -> [a] -> [a]
:)
timeoutSeconds :: Int
timeoutSeconds :: Int
timeoutSeconds = Int
60
sizeLimitMiddleware :: RequestSizeLimit -> Middleware
sizeLimitMiddleware :: RequestSizeLimit -> Middleware
sizeLimitMiddleware (RequestSizeLimit Word64
maxBytes) =
RequestSizeLimitSettings -> Middleware
requestSizeLimitMiddleware
((Request -> IO (Maybe Word64))
-> RequestSizeLimitSettings -> RequestSizeLimitSettings
setMaxLengthForRequest (\Request
_req -> Maybe Word64 -> IO (Maybe Word64)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Word64 -> Maybe Word64
forall a. a -> Maybe a
Just Word64
maxBytes)) RequestSizeLimitSettings
defaultRequestSizeLimitSettings)
runWarp :: ServerConfig -> IO Application -> IO ()
runWarp :: ServerConfig -> IO Application -> IO ()
runWarp ServerConfig
cfg0 IO Application
getApp = do
drain <- IO DrainSignal
newDrainSignal
let cfg = ServerConfig
cfg0{scDrain = drain}
ShutdownDrainTimeout timeoutSecs = scDrainTimeout cfg
settings =
Int -> Settings -> Settings
Warp.setPort (ServerConfig -> Int
scPort ServerConfig
cfg)
(Settings -> Settings)
-> (Settings -> Settings) -> Settings -> Settings
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (IO () -> IO ()) -> Settings -> Settings
Warp.setInstallShutdownHandler (DrainSignal -> IO () -> IO ()
installShutdownHandler DrainSignal
drain)
(Settings -> Settings)
-> (Settings -> Settings) -> Settings -> Settings
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Maybe Int -> Settings -> Settings
Warp.setGracefulShutdownTimeout (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
timeoutSecs)
(Settings -> Settings) -> Settings -> Settings
forall a b. (a -> b) -> a -> b
$ Settings
Warp.defaultSettings
app <- getApp
withInteractiveHalt defaultInteractiveHalt (Warp.runSettings settings app)
installShutdownHandler :: DrainSignal -> IO () -> IO ()
installShutdownHandler :: DrainSignal -> IO () -> IO ()
installShutdownHandler DrainSignal
drain IO ()
closeSocket =
(Signal -> IO Handler) -> [Signal] -> IO ()
forall (t :: * -> *) (f :: * -> *) a b.
(Foldable t, Applicative f) =>
(a -> f b) -> t a -> f ()
traverse_ Signal -> IO Handler
install [Signal
sigTERM, Signal
sigINT]
where
install :: Signal -> IO Handler
install Signal
sig = Signal -> Handler -> Maybe SignalSet -> IO Handler
installHandler Signal
sig (IO () -> Handler
CatchOnce (DrainSignal -> IO ()
beginDrain DrainSignal
drain IO () -> IO () -> IO ()
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> IO ()
closeSocket)) Maybe SignalSet
forall a. Maybe a
Nothing