Mojo · Serving

Serving

from ucall import Interface, Metrics, ServerOptions, … 1.0.0-rc.1 · 60202332

A handler is a compile-time parameter of its route, server.get["/users/{name}", handler]() and likewise post, put, delete and patch, or route[path, handler](verb) and route_with[path, handler](verb, delivery) for any other.

UCall writes nothing on its own; a server’s or a client’s log option hands every line to a handler, with state of your own that must outlive the server or client:

def count(level: LogLevel, line: StringSlice[ImmUntrackedOrigin], mut lines: Int):
    lines += 1

var options = ServerOptions()
options.log = Log.to[count](lines, level=LogLevel.INFO)

API

StructInterfaceOne address the server answers on, with the security and admission limits of what arrives there.1
struct Interface

Several live under one server, so it may answer plaintext on loopback and Noise on the wire at once.

Fields

addressStringListening address in "host:port" form; port zero picks a free one, which Server.address reports.
listen_descriptorOptional[Int]A listening stream socket another owner opened, accepted on instead of binding address.
device_nameStringA physical device to pin the interface to, like "eth0", on Linux only; empty binds by address.
securitySecurityEncryption for connections accepted here; NOISE_XX also needs peer_authorizer.
noise_private_keyOptional[SIMD[.uint8, 32]]The server identity for handshakes, required unless security is NONE.
noise_peer_keyOptional[SIMD[.uint8, 32]]The one initiator's static public key a NOISE_KK interface admits.
peer_authorizerPeerAuthorizerVets each client's authenticated static key before any of its requests is read.
event_loopsIntEvent loops accepting here; zero means one, or the receive-queue count of a named device.
connectionsIntConnections open at once across this interface's loops.
pipelined_requestsIntRequests one connection may have in flight before the server stops reading it; zero means one.
max_request_bytesIntThe largest request head and buffered body; zero takes the largest socket receive buffer.
reply_directoriesList[String]Directories the path parts of replies to this interface's requests resolve beneath, by position.

Conforms to

Copyable, Deinitable, Movable
__init__An interface listening on address with the default security and limits.
def __init__(out self, address: String, *, connections: Int = Int(1024), event_loops: Int = Int(0))

Parameters

addressStringWhere to listen, in "host:port" form.
connectionsIntConnections open at once.
event_loopsIntEvent loops accepting here; zero means one.
StructMetricsThe StatsD endpoint a server sends each event loop's counters and sampled latencies to.1
struct Metrics

Fields

addressStringThe endpoint in "host:port" form; empty sends nothing.
sample_rateFloat32The fraction of finished requests whose latency is reported, in (0, 1]; zero means 1.
prefixStringThe metric name prefix; empty means "ucall".
tagsList[String]Tags on every metric, each "key:value".
interval_microsecondsIntThe time between one event loop's datagrams; zero means one second.

Conforms to

Copyable, Defaultable, Deinitable, Movable
__init__No endpoint, so no datagram leaves.
def __init__(out self)
StructServerOptionsWhat a server runs on and reports through, beside its interfaces.1
struct ServerOptions

Fields

backendBackendThe requested I/O backend, failing rather than substituting when it is unavailable.
cpu_classCPUClassA ceiling on the CPU class; UNKNOWN takes what the constructing thread's CPU offers.
logLogWhere log lines go; the default keeps the server silent.
metricsMetricsWhere StatsD datagrams go; the default sends none.

Conforms to

Copyable, Defaultable, Deinitable, Movable
__init__The automatic backend and CPU class, silent and without metrics.
def __init__(out self)
StructEventLoopStatisticsCounters of one event loop since the server started, ucall_event_loop_statistics_t in C.1
struct EventLoopStatistics

Divided by requests, they price one call. Each counter is read on its own, so a snapshot of a running loop is not one instant.

Fields

pollsUInt64Engine polls the loop issued, each taking zero or more completions.
empty_pollsUInt64Engine polls that took no completion.
kernel_entersUInt64System calls: io_uring enters, or other backends' waits, registrations and socket calls.
submissionsUInt64Operations queued.
completionsUInt64Results taken.
cancellationsUInt64Pending operations, or io_uring multishot receives, the engine asked to cancel.
acceptsUInt64Connections accepted and handed to this loop.
requestsUInt64HTTP requests parsed and dispatched to an endpoint or an error reply.
receive_rearmsUInt64Multishot receives armed again on a socket that already streamed.
bytes_receivedUInt64Bytes the kernel reported received, ciphertext under Noise.
bytes_sentUInt64Bytes the kernel reported sent, ciphertext under Noise.
bytes_copiedUInt64Bytes UCall copied: receive appends, reply bodies, Noise records and parked receives.
zero_copy_sendsUInt64Sends that left without copying.
zero_copy_fallbacksUInt64Zero-copy sends the kernel copied anyway or refused to pin.
copying_sendsUInt64Sends that copied instead of pinning.
file_reads_asynchronousUInt64File reads the kernel finishes later.
file_reads_blockingUInt64File reads the loop issued itself and blocked on.
bytes_readUInt64Bytes the kernel reported read from files into the loop's file windows.
file_window_overlapsUInt64File reads submitted while an earlier window of the same stream waited to send or sent.
linger_resetsUInt64Lingering closes reset at their deadline because the kernel still held what they sent.
ready_passesUInt64Passes the loop made over a ready connection.
replied_dequeuesUInt64Replied exchanges the loop took from its queue to adopt their reply segments.
refused_requestsUInt64Requests the loop answered itself with 400, 404, 405, 413, 501, 503 or 505.
handshake_failuresUInt64Connections whose Noise handshake failed.
authorizer_refusalsUInt64Connections whose authenticated peer key the interface's authorizer refused.

Conforms to

Copyable, Defaultable, Deinitable, ImplicitlyCopyable, Movable
__init__Every counter at zero, as a failed read leaves them.
def __init__(out self)
StructBatchEndpointStatisticsCounters of one batch endpoint since registration, ucall_batch_endpoint_statistics_t in C.1
struct BatchEndpointStatistics

Fields

batchesUInt64Batches handed to the handler.
requestsUInt64Requests across those batches.
filled_batchesUInt64Batches dispatched on reaching Batching.max_size.
ready_batchesUInt64Batches dispatched on reaching Batching.min_size, below the maximum.
expired_batchesUInt64Batches dispatched below Batching.min_size once Batching.max_latency_microseconds passed.

Conforms to

Copyable, Defaultable, Deinitable, ImplicitlyCopyable, Movable
__init__Every counter at zero, as a failed read leaves them.
def __init__(out self)
StructServerAn HTTP server over one or more interfaces, polled by application threads.18
struct Server

Register every endpoint first, then poll each event loop from one thread at a time. Not Movable, as every route hands the library this server's address. Dropping it stops the server; the library frees it once the last batch endpoint and detached request are gone too.

Fields

sharedArcPointer[_ServerCore]The handle, shared with batch endpoints and detached requests.

Conforms to

Deinitable, Movable
__init__Starts a server answering on interfaces. Calls ucall_server_init.
def __init__(
    out self,
    library: Library,
    interfaces: Span[Interface],
    options: ServerOptions = ServerOptions()
)

Parameters

libraryLibraryThe loaded library, kept so the shared object stays mapped.
interfacesSpan[Interface]At least one interface; their addresses, keys and directories are copied.
optionsServerOptionsThe backend, CPU class, log sink and StatsD endpoint.

Raises

Error if an interface is unusable, no backend serves the configuration, or an address cannot be bound.
__deinit__Stops the server, which the library frees once nothing else holds it.
def __deinit__(deinit self)
event_loopsEvent loops this server runs, each to be polled by one thread at a time. Binding-only.
def event_loops(self) -> Int
backendThe I/O backend the server resolved to. Binding-only.
def backend(self) -> Backend
cpu_classThe CPU class the server resolved to. Binding-only.
def cpu_class(self) -> CPUClass
addressThe numeric address interface index listens on, with the port it bound. Binding-only.
def address(self, index: Int) -> Optional[String]

Parameters

indexIntThe interface's position among those the server was started with.

Returns

The bound address, or None past the last interface.
routeRoutes requests matching path and verb to handler, which runs on the polling thread. Calls ucall_server_add_http_endpoint.
def route[path: StringSpan[ImmStaticOrigin], handler: def(var Request) raises thin -> None](
    mut self,
    verb: Verb
)

Templates capture {name} segments and a greedy {*name} tail, read back through path_parameter. A handler that raises, or leaves its request unreplied, answers 500.

Parameters

pathStringSpan[ImmStaticOrigin]comptimeThe URI template.
handlerdef(var Request) raises thin -> NonecomptimeReceives each matching request, owned from then on.
verbVerbThe HTTP method, or Verb.ANY.

Raises

Error with ALREADY_EXISTS for a template and verb registered before, or INVALID_ARGUMENT for a malformed template.
route_withRoutes like route, running handler once the body is buffered, or with STREAMED as soon as the headers arrived. Calls ucall_server_add_http_endpoint.
def route_with[path: StringSpan[ImmStaticOrigin], handler: def(var Request) raises thin -> None](
    mut self,
    verb: Verb,
    delivery: BodyDelivery
)

Parameters

pathStringSpan[ImmStaticOrigin]comptimeThe URI template.
handlerdef(var Request) raises thin -> NonecomptimeReceives each matching request, owned from then on.
verbVerbThe HTTP method, or Verb.ANY.
deliveryBodyDeliveryWhen the handler runs.

Raises

Error as route does.
getRoutes GET requests on path to handler. Calls ucall_server_add_http_endpoint.
def get[path: StringSpan[ImmStaticOrigin], handler: def(var Request) raises thin -> None](mut self)
postRoutes POST requests on path to handler. Calls ucall_server_add_http_endpoint.
def post[path: StringSpan[ImmStaticOrigin], handler: def(var Request) raises thin -> None](mut self)
putRoutes PUT requests on path to handler. Calls ucall_server_add_http_endpoint.
def put[path: StringSpan[ImmStaticOrigin], handler: def(var Request) raises thin -> None](mut self)
deleteRoutes DELETE requests on path to handler. Calls ucall_server_add_http_endpoint.
def delete[path: StringSpan[ImmStaticOrigin], handler: def(var Request) raises thin -> None](
    mut self
)
patchRoutes PATCH requests on path to handler. Calls ucall_server_add_http_endpoint.
def patch[path: StringSpan[ImmStaticOrigin], handler: def(var Request) raises thin -> None](
    mut self
)
batchCollects requests matching path and verb from every event loop into one queue, which the returned endpoint drains into handler. Calls ucall_server_add_http_batch_endpoint.
def batch[path: StringSpan[ImmStaticOrigin], handler: def(Batch) raises thin -> None](
    mut self,
    verb: Verb,
    batching: Batching = Batching()
) -> BatchEndpoint

Requests the handler does not take answer 500 once it returns.

Parameters

pathStringSpan[ImmStaticOrigin]comptimeThe URI template.
handlerdef(Batch) raises thin -> NonecomptimeReceives each due batch, lent for the call.
verbVerbThe HTTP method, or Verb.ANY.
batchingBatchingWhen a batch is due.

Returns

The endpoint, polled by one thread at a time.

Raises

Error as route does, or with INVALID_ARGUMENT for a minimum above the maximum.
take_callAdvances one event loop, running the handlers of the requests it completes on this thread, and waits up to timeout_microseconds for progress. Calls ucall_server_take_call.
def take_call(self, event_loop: Int, timeout_microseconds: Int = Int(0)) -> Polling

Parameters

event_loopIntThe loop's index; each has one polling thread at a time.
timeout_microsecondsIntThe longest wait; zero performs one nonblocking round.

Returns

RETIRED once the loop has nothing left to take after stop, SERVING otherwise.

Raises

Error with INVALID_ARGUMENT past the last loop, or for an I/O failure.
take_callsDrives one event loop on this thread until stop or an I/O failure. Calls ucall_server_take_calls.
def take_calls(self, event_loop: Int)

Parameters

event_loopIntThe loop's index; each has one polling thread at a time.

Raises

Error with INVALID_ARGUMENT past the last loop, or for an I/O failure.
stopRequests shutdown and wakes every polling thread, from any thread or handler. Calls ucall_server_stop.
def stop(self)

The first call drains the requests already held; a second one cuts the replies in flight.

event_loop_statisticsA snapshot of one event loop's counters, readable from any thread while it is polled. Calls ucall_server_event_loop_statistics.
def event_loop_statistics(self, event_loop: Int) -> EventLoopStatistics

Parameters

event_loopIntThe loop's index.

Returns

The counters since the server started.

Raises

Error with INVALID_ARGUMENT past the last loop.
StructBatchEndpointOne batch endpoint, polled by one thread at a time and observable from any.4
struct BatchEndpoint

Requests from every event loop share its queue, so one thread drains what all of them received.

Conforms to

Deinitable, Movable
__init__
def __init__(out self, var state: OwnedPointer[_BatchState], handle: Int)
take_batchRuns the handler on this thread if a batch is due, waiting up to timeout_microseconds for one. Calls ucall_server_take_batch.
def take_batch(self, timeout_microseconds: Int = Int(0))

Parameters

timeout_microsecondsIntThe longest wait; zero performs one nonblocking round.

Raises

Error if the library refuses the round.
take_batchesRuns the handler on this thread for every due batch until Server.stop. Calls ucall_server_take_batches.
def take_batches(self)

Raises

Error if the library refuses the polling.
statisticsA snapshot of the endpoint's counters, readable from any thread while it is polled. Calls ucall_server_batch_endpoint_statistics.
def statistics(self) -> BatchEndpointStatistics

Returns

The counters since registration.

Raises

Error if the library refuses the read.
StructVerbAn HTTP method, for routing and for client calls, ucall_verb_t in C.
struct Verb

Fields

codec_intThe raw ucall_verb_t value the ABI exchanges.

Values

UNKNOWNVerb(Int32(0))Unrecognized or not yet parsed.
GETVerb(Int32(1))Retrieves a resource.
POSTVerb(Int32(2))Creates a resource.
PUTVerb(Int32(3))Updates a resource.
DELETEVerb(Int32(4))Removes a resource.
PATCHVerb(Int32(5))Updates part of a resource.
OPTIONSVerb(Int32(7))Retrieves the supported methods.
ANYVerb(Int32(8))Matches every method; valid only when registering an endpoint.

Conforms to

Copyable, Deinitable, Equatable, ImplicitlyCopyable, Movable, RegisterPassable, TrivialRegisterPassable
StructBackendA concrete I/O implementation, ucall_io_backend_t in C. Library.io_backend_name spells each one.
struct Backend

Fields

codec_intThe raw ucall_io_backend_t value the ABI exchanges.

Values

AUTOMATICBackend(Int32(0))The fastest backend this machine and its limits allow.
POSIXBackend(Int32(1))Blocking sockets and files, polled with poll, on any POSIX system.
LINUX_EPOLLAIOBackend(Int32(2))Linux epoll for sockets and Linux AIO for files, where io_uring is blocked.
LINUX_URING612Backend(Int32(3))Linux io_uring as of 6.12, the older LTS line.
LINUX_URING618Backend(Int32(4))Linux io_uring as of 6.18, the newer LTS line, with multishot receives.
BSD_KQUEUEBackend(Int32(5))BSD kqueue on FreeBSD, with FreeBSD AIO for files, and on macOS.
WINDOWS_IOCPBackend(Int32(6))I/O completion ports on Windows.

Conforms to

Copyable, Deinitable, Equatable, ImplicitlyCopyable, Movable, RegisterPassable, TrivialRegisterPassable
StructLogLevelSeverity of a log line, from the most verbose up, ucall_log_level_t in C.
struct LogLevel

Fields

codec_intThe raw ucall_log_level_t value the ABI exchanges.

Values

DEBUGLogLevel(Int32(0))Detail for debugging.
INFOLogLevel(Int32(1))Normal lifecycle events.
WARNINGLogLevel(Int32(2))Something unexpected that the library recovered from.
ERRORLogLevel(Int32(3))An operation failed.
FATALLogLevel(Int32(4))The server or client cannot continue.

Conforms to

Copyable, Deinitable, Equatable, ImplicitlyCopyable, Movable, RegisterPassable, TrivialRegisterPassable
StructLogFormatShape of each log line, ucall_log_format_t in C.
struct LogFormat

Fields

codec_intThe raw ucall_log_format_t value the ABI exchanges.

Values

HUMANLogFormat(Int32(0))Human-readable unit-normalized lines.
JSONLogFormat(Int32(1))Newline-delimited JSON documents.

Conforms to

Copyable, Deinitable, Equatable, ImplicitlyCopyable, Movable, RegisterPassable, TrivialRegisterPassable
StructLogThe single log sink of a client or a server, ucall_log_t in C; the default logs nothing.2
struct Log

Fields

callbackAddressThe ucall_log_callback_t every line at or above level goes to; NULL_ADDRESS logs nothing.
contextAddressThe state callback writes into, passed verbatim.
formatLogFormatLine format.
levelLogLevelLines below this severity are never formatted.

Conforms to

Copyable, Defaultable, Deinitable, ImplicitlyCopyable, Movable, RegisterPassable, TrivialRegisterPassable
__init__No sink, so the library stays silent.
def __init__() -> Self
toA sink calling handler with every line at or above level, on whichever thread made it.
def to[State: AnyType, //, handler: def(LogLevel, StringSpan[ImmUntrackedOrigin], mut State) thin -> None](
    mut state: State,
    level: LogLevel = LogLevel.DEBUG,
    format: LogFormat = LogFormat.HUMAN
) -> Self

Parameters

StateAnyTypecomptimeThe state's type, inferred from the argument.
handlerdef(LogLevel, StringSpan[ImmUntrackedOrigin], mut State) thin -> NonecomptimeReceives the severity, the line borrowed for the call without a newline, and the state.
stateStatemutWhat handler writes into; it must outlive the client or server, which keep its address.
levelLogLevelLines below this severity are never formatted.
formatLogFormatLine format.

Returns

The sink, for a client's or a server's options.