// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
// What a listener beside the TCP one needs from the server it runs in. HTTP/3 is the case: UDP on
// the same port, in a package of its own so that a server without it does not carry QUIC, but the
// same lifespan, the same scopes, the same limits and the same shutdown as the rest.
///|
/// The serving process, as a listener beside the TCP one sees it.
pub struct Site {
priv config : @config.Config
priv bound : @socket.Addr
priv load : Load
priv state : Map[String, Json]
}
///|
pub fn Site::config(self : Site) -> @config.Config {
self.config
}
///|
/// The address the server listens on.
pub fn Site::bound(self : Site) -> @socket.Addr {
self.bound
}
///|
/// Whether the server is shutting down, which is when a listener stops taking new work and lets
/// what is running finish.
pub fn Site::stopping(self : Site) -> Bool {
self.load.stopping
}
///|
/// Take a new request, or refuse it past `limit_concurrency`. Every request taken is given back
/// with `finished`, which is what shutdown waits for.
pub fn Site::admit(self : Site) -> Bool {
guard !self.load.busy(self.config.limit_concurrency) else {
self.config.logger.log(
@moonlog.warn, "Exceeded concurrency limit, refusing with 503",
)
return false
}
self.load.requests += 1
true
}
///|
/// A request taken with `admit` is over.
pub async fn Site::finished(self : Site) -> Unit {
self.load.requests -= 1
self.load.count(self.config) |> ignore
}
///|
/// The ASGI scope for a request head (RFC 9110 ยง7) that arrived from `peer` under TLS on HTTP
/// version `version`, with `suite` naming its cipher.
pub fn Site::scope(
self : Site,
fields : Array[@header.Header],
version~ : String,
peer~ : @socket.Addr,
suite~ : Int32,
) -> @moonasgi.Scope {
let link : Link = {
scheme: "https",
bound: Some(self.bound),
peer: Some(peer),
tls: Some({
server_cert: None,
client_cert_chain: [],
client_cert_name: None,
client_cert_error: None,
tls_version: Some(0x0304),
cipher_suite: Some(suite),
}),
state: self.state,
}
field_scope(fields, link, self.config, version)
}
///|
/// A response head as a field section: `:status`, the application's fields that may cross, and the
/// configured server fields.
pub fn Site::head(
self : Site,
status : Int32,
headers : Array[(String, String)],
) -> Array[@header.Header] {
h2_response_headers(status, headers, self.config)
}
///|
/// The method and target of a request head, for the access log.
pub fn request_of(fields : Array[@header.Header]) -> (String, String) {
request_line(fields)
}