Mojo · Requests

Requests

from ucall import Peer, RequestPart, Request, … 1.0.0-rc.1 · 60202332

Each handler runs on the thread polling the event loop and owns its Request:

def greet(var request: Request) raises:
    var verbose = request.query("verbose")          # Optional[Span[Byte, ...]], still percent-encoded
    var agent = request.header("user-agent")        # case-insensitive
    if not request.path_parameter("name"):          # case-sensitive
        request^.reply_error(HTTPStatus.BAD_REQUEST, "no name")
        return
    request^.reply(HTTPStatus.OK, [BytesView.of("content-type: text/plain")], "Hello!".as_bytes())

Every read borrows the received bytes with the request’s origin. fields_count and field walk every header line in arrival order, path_segments_count and path_segment walk the path, parts_count and part walk a multipart/form-data body, and peer reports the interface, the authenticated Noise key and the socket address. reply, reply_error and detach consume the request, and a request dropped without them answers 500, which is also what a handler raising past it answers.

API

StructPeerWhich interface a request arrived on, and who sent it.
struct Peer

Fields

network_interface_indexIntThe position of its interface among those the server was started with.
static_keyNoiseKeyThe client's authenticated Noise static key, zeroed on plaintext interfaces.
addressPeerAddressThe socket address the connection was accepted from.

Conforms to

Copyable, Deinitable, ImplicitlyCopyable, Movable
StructRequestPartOne part of a multipart/form-data request body, borrowing the received bytes.
struct RequestPart[origin: Origin[mut=origin.mut]]

Fields

nameSpan[UInt8, origin]The name its Content-Disposition gives.
content_typeSpan[UInt8, origin]Its Content-Type, empty when absent.
dataSpan[UInt8, origin]Its payload, undecoded.

Conforms to

Copyable, Deinitable, ImplicitlyCopyable, Movable
StructRequestOne received request, owned by its handler on the polling thread.5
struct Request

reply, reply_error and detach consume it; one dropped without them answers 500. Reads borrow the received bytes with the request's origin.

Conforms to

Deinitable, Movable, RequestReads
__init__
def __init__(out self, server: Pointer[Server, ImmUntrackedOrigin], request: Int)
__deinit__Answers 500 for a request no reply or detach consumed. Calls ucall_server_reply_error.
def __deinit__(deinit self)
replyReplies with status, full header lines like "content-type: text/plain", and body. Calls ucall_server_reply_content.
def reply[origin: ImmOrigin](
    deinit self,
    status: HTTPStatus,
    headers: Span[BytesView[origin]],
    body: Span[UInt8]
)
def reply(deinit self, status: HTTPStatus, body: Span[UInt8])

Replies with status and body and no header lines of its own. Calls ucall_server_reply_content.

A closed connection still consumes the request and raises CONNECTION_CLOSED; any other refusal answers 500 before raising.

Parameters

statusHTTPStatusThe response status; zero sends 200.
headersSpan[BytesView[origin]]Every header line; Content-Length, Date and Server are generated.
bodySpan[UInt8]The response body.

Raises

Error if the reply is refused.
reply_errorReplies with an error status, 400 or above, and a plain-text message. Calls ucall_server_reply_error.
def reply_error(deinit self, status: HTTPStatus, message: StringSpan)

Raises

Error if the reply is refused, as reply does.
detachHands the request to an owner that may move to any thread and reply after the handler returned, gaining the replies that may finish later. Binding-only.
def detach(deinit self) -> DetachedRequest

Returns

The request, owned from here on.
StructDetachedRequestA request taken off its polling thread, and the only owner of the verbs that may finish later: reply_parts, reply_stream and body_chunk.7
struct DetachedRequest

It reads like the Request it came from, moves to any thread, and answers 500 when dropped unreplied. Its replies hand back a Deferred while a file or path part still sends, and waiting on one belongs off the polling thread, as that thread is the one sending it.

Conforms to

Deinitable, Movable, RequestReads
__init__
def __init__(out self, shared: ArcPointer[_ServerCore], request: Int)
__deinit__Answers 500 for a request dropped unreplied. Calls ucall_server_reply_error.
def __deinit__(deinit self)
replyReplies with status, full header lines, and body, final on return. Calls ucall_server_reply_content.
def reply[origin: ImmOrigin](
    deinit self,
    status: HTTPStatus,
    headers: Span[BytesView[origin]],
    body: Span[UInt8]
)
def reply(deinit self, status: HTTPStatus, body: Span[UInt8])

Replies with status and body and no header lines of its own. Calls ucall_server_reply_content.

A refusal that keeps the request answers 500 before raising.

Raises

Error if the reply is refused.
reply_errorReplies with an error status and a plain-text message, final on return. Calls ucall_server_reply_error.
def reply_error(deinit self, status: HTTPStatus, message: StringSpan)

Raises

Error if the reply is refused.
reply_partsReplies with status, full header lines, and parts concatenated into the body, its Content-Length measured. Calls ucall_server_reply_vectored.
def reply_parts[headers_origin: ImmOrigin, parts_origin: ImmOrigin](
    deinit self,
    status: HTTPStatus,
    headers: Span[BytesView[headers_origin]],
    parts: Span[ReplyPart[parts_origin]]
) -> Optional[Deferred]

A part failing before the head left replaces the reply with 404, 416 or 500, and the Deferred reports the cause. A refusal on return answers 500 before raising.

Returns

None once final, and a Deferred while a file or path part still sends.

Raises

Error if the reply is refused or fails.
reply_streamSends the status line and header lines, and opens a body appended part by part: chunked, or of the length a Content-Length header declares. Calls ucall_server_reply_begin.
def reply_stream[origin: ImmOrigin](
    deinit self,
    status: HTTPStatus,
    headers: Span[BytesView[origin]]
) -> ReplyStream

A closed connection still ends the stream and raises CONNECTION_CLOSED; any other failure answers 500 before raising.

Returns

The open stream, which ends once dropped or end is called.

Raises

Error if the head is refused.
body_chunkPulls the body bytes decoded since the last pull, waiting for them to arrive; empty once the body ended. Calls ucall_server_request_body_chunk and ucall_outcome_wait.
def body_chunk(ref self) -> Span[UInt8, origin_of(self)]

On a buffered endpoint the first pull is the whole body. On the polling thread, where nothing can arrive while it runs, a pull finding nothing raises WOULD_BLOCK instead.

Returns

The bytes, valid until the next pull.

Raises

Error with CONNECTION_CLOSED for a body cut short, or MALFORMED for a malformed one.
StructReplyPartOne part of a reply body, ucall_reply_part_t in C: bytes in memory, or a byte range of a file.3
struct ReplyPart[origin: ImmOrigin = ImmUntrackedOrigin]

Mojo has no unions, so the union's five words are spelled out, as the ABI lays them for each kind on the little-endian targets this binding runs on.

Parameters

originImmOrigincomptimeThe bytes or path a part borrows, named so they outlive the reply call.

Fields

kindc_intucall_reply_part_kind_t: 0 for bytes, 1 for a file, 2 for a path.
firstUInt64
secondUInt64
thirdUInt64
fourthUInt64
fifthUInt64

Conforms to

Copyable, Deinitable, ImplicitlyCopyable, Movable, RegisterPassable, TrivialRegisterPassable
bytesBytes in memory, copied before the reply call returns.
def bytes[bytes_origin: ImmOrigin, //](bytes: Span[UInt8, bytes_origin]) -> ReplyPart[bytes_origin]

Parameters

bytesSpan[UInt8, bytes_origin]The content.

Returns

The part.
fileA range of an O_DIRECT file, which the event loop reads after the call returns.
def file[part_origin: ImmOrigin = ImmUntrackedOrigin](range: FileRange) -> ReplyPart[part_origin]

Parameters

part_originImmOrigincomptimeThe origin of the parts it shares an array with.
rangeFileRangeThe file and the range.

Returns

The part.
pathA range of a file beneath one of the interface's reply directories, opened by the event loop.
def path[path_origin: ImmOrigin, //](
    directory: Int,
    relative_path: StringSpan[path_origin],
    offset: UInt64 = UInt64(0),
    length: UInt64 = UInt64(0)
) -> ReplyPart[path_origin]

Parameters

directoryIntThe position of the directory among the interface's reply directories.
relative_pathStringSpan[path_origin]The file beneath it, copied before the call returns.
offsetUInt64The range's first byte.
lengthUInt64The range's byte count; zero sends through the end of the file.

Returns

The part.
TraitRequestReadsThe reads every received request offers, detached or not, each written once.14
trait RequestReads
verbThe HTTP method, which tells apart the requests of a Verb.ANY endpoint. Calls ucall_server_request_verb.
def verb(self) -> Verb
pathThe request path as sent, still percent-encoded, without the query. Calls ucall_server_request_path.
def path(ref self) -> Span[UInt8, origin_of(self)]
query_stringThe query after the first '?', still percent-encoded, or empty without one. Calls ucall_server_request_query.
def query_string(ref self) -> Span[UInt8, origin_of(self)]
bodyThe buffered body, decoded from chunked framing if it arrived that way. Calls ucall_server_request_body.
def body(ref self) -> Span[UInt8, origin_of(self)]

Raises

Error with INVALID_ARGUMENT on a streamed endpoint, whose body body_chunk pulls.
path_parameterThe value the endpoint's template captured under name, case-sensitive. Calls ucall_server_request_path_parameter.
def path_parameter(ref self, name: StringSpan) -> Optional[Span[UInt8, origin_of(self)]]
queryThe value of query parameter name, case-sensitive and still percent-encoded. Calls ucall_server_request_query_parameter.
def query(ref self, name: StringSpan) -> Optional[Span[UInt8, origin_of(self)]]
fields_countHeader field lines in arrival order, repeated names included. Calls ucall_server_request_fields_count.
def fields_count(self) -> Int
fieldHeader field line index: its name, lowercased as the parser keeps it, and its trimmed value. Calls ucall_server_request_field.
def field(ref self, index: Int) -> Tuple[Span[UInt8, origin_of(self)], Span[UInt8, origin_of(self)]]

Raises

Error with NOT_FOUND past the last line.
peerThe interface the request arrived on, the sender's authenticated key and its socket address, readable after its connection closed. Calls ucall_server_request_peer.
def peer(self) -> Peer
path_segments_countPath components in order, as sent. Calls ucall_server_request_path_segments_count.
def path_segments_count(self) -> Int
path_segmentPath component index, still percent-encoded, or None past the last one. Calls ucall_server_request_path_segment.
def path_segment(ref self, index: Int) -> Optional[Span[UInt8, origin_of(self)]]
parts_countParts in a multipart/form-data body, zero for any other body. Calls ucall_server_request_parts_count.
def parts_count(self) -> Int
partPart index of a multipart/form-data body, its payload undecoded. Calls ucall_server_request_part.
def part(ref self, index: Int) -> RequestPart[origin_of(self)]

Raises

Error with INVALID_ARGUMENT past the last part, or MALFORMED for a malformed body.
StructBytesViewA borrowed run of bytes and its length, ucall_bytes_view_t in C, never null-terminated.4
struct BytesView[origin: ImmOrigin = ImmUntrackedOrigin]

Crosses by value, which the two-word argument class handles on both x86-64 and AArch64.

Parameters

originImmOrigincomptimeThe borrowed storage, named so it outlives every call this view crosses.

Fields

dataPointer[UInt8, origin]The first byte, borrowed for the duration of the call.
lengthc_size_tHow many bytes the run covers, since nothing terminates it.

Conforms to

Copyable, Deinitable, ImplicitlyCopyable, Movable, RegisterPassable, TrivialRegisterPassable
ofBorrows a Mojo string's bytes, which the caller keeps alive across the call.
def of[text_origin: ImmOrigin, //](text: StringSpan[text_origin]) -> BytesView[text_origin]
def of[bytes_origin: ImmOrigin, //](bytes: Span[UInt8, bytes_origin]) -> BytesView[bytes_origin]

Borrows a run of raw bytes, such as a request body.

Parameters

text_originImmOrigincomptimeThe string's origin, inferred from the argument.
textStringSpan[text_origin]The characters to hand over.
bytes_originImmOrigincomptimeThe run's origin, inferred from the argument.
bytesSpan[UInt8, bytes_origin]The bytes to hand over.

Returns

A view of exactly those bytes, with no terminator manufactured.
emptyA run of no bytes, which output slots hold until the library writes them.
def empty() -> BytesView

It points at a static empty string, since a Pointer is never null.

Returns

A view of no bytes.
argumentsThe same run with its borrow erased, as the one instantiation the ABI signatures name.
def arguments(self) -> BytesView

Returns

A view of the same bytes, whose type no longer keeps their storage alive.
spanReads a run the library wrote back as a span living as long as target.
def span[target: Origin[mut=target.mut]](self) -> Span[UInt8, target]

The library writes {NULL, 0} for an empty run, and a span of no bytes never reads its pointer, so the null needs no branch.

Parameters

targetOrigin[mut=target.mut]comptimeThe owner whose lifetime bounds the bytes, such as a request or a response.

Returns

The same bytes, borrowed from target.
StructHTTPStatusAn HTTP status code a server sends and a client receives, ucall_http_status_t in C.
struct HTTPStatus

Codes absent from the named constants are as valid as the named ones.

Fields

codeUInt32The numeric status code.

Values

OKHTTPStatus(UInt32(200))Request succeeded.
CREATEDHTTPStatus(UInt32(201))Resource created.
NO_CONTENTHTTPStatus(UInt32(204))Success with an empty body.
PARTIAL_CONTENTHTTPStatus(UInt32(206))Success with the requested range of the body.
MOVED_PERMANENTLYHTTPStatus(UInt32(301))Resource has a new permanent URL.
FOUNDHTTPStatus(UInt32(302))Resource is temporarily at another URL.
NOT_MODIFIEDHTTPStatus(UInt32(304))Cached copy is still fresh.
BAD_REQUESTHTTPStatus(UInt32(400))Malformed request syntax.
UNAUTHORIZEDHTTPStatus(UInt32(401))Authentication required or failed.
FORBIDDENHTTPStatus(UInt32(403))Authenticated, but not permitted.
NOT_FOUNDHTTPStatus(UInt32(404))No endpoint matches the request path.
METHOD_NOT_ALLOWEDHTTPStatus(UInt32(405))Endpoint exists, but not for this verb.
TIMEOUTHTTPStatus(UInt32(408))Client took too long to send the request.
CONFLICTHTTPStatus(UInt32(409))Request conflicts with the current resource state.
PRECONDITION_FAILEDHTTPStatus(UInt32(412))A request condition does not match the current resource.
PAYLOAD_TOO_LARGEHTTPStatus(UInt32(413))Request body is too large.
RANGE_NOT_SATISFIABLEHTTPStatus(UInt32(416))A requested byte range starts or ends past the end of the resource.
TOO_MANY_REQUESTSHTTPStatus(UInt32(429))Rate limit exceeded.
INTERNAL_ERRORHTTPStatus(UInt32(500))Unhandled failure inside an endpoint.
NOT_IMPLEMENTEDHTTPStatus(UInt32(501))Verb or feature not supported.
BAD_GATEWAYHTTPStatus(UInt32(502))An upstream answered with something no HTTP peer would send.
SERVICE_UNAVAILABLEHTTPStatus(UInt32(503))Server is overloaded or shutting down.
GATEWAY_TIMEOUTHTTPStatus(UInt32(504))Upstream dependency timed out.
VERSION_NOT_SUPPORTEDHTTPStatus(UInt32(505))Request names an HTTP version the server does not speak.

Conforms to

Copyable, Deinitable, Equatable, ImplicitlyCopyable, Movable, RegisterPassable, TrivialRegisterPassable
StructPercentModeWhich part of a URL Library.decode_percent reads, as only a query spells a space '+'.
struct PercentMode

Fields

codec_intThe raw ucall_percent_mode_t value the ABI exchanges.

Values

PATHPercentMode(Int32(0))A path, where '+' stays itself.
QUERYPercentMode(Int32(1))A query, where '+' is a space.

Conforms to

Copyable, Deinitable, Equatable, ImplicitlyCopyable, Movable, RegisterPassable, TrivialRegisterPassable
StructAddressFamilyThe family of a peer's socket address, ucall_address_family_t in C, one byte wide.
struct AddressFamily

Fields

codeUInt8The raw ucall_address_family_t value the ABI exchanges.

Values

UNKNOWNAddressFamily(UInt8(0))No address was read for this peer.
IPV4AddressFamily(UInt8(1))IPv4, in the first 4 bytes.
IPV6AddressFamily(UInt8(2))IPv6, in all 16 bytes, with its scope.
UNIXAddressFamily(UInt8(3))A local AF_UNIX peer, with no address bytes.

Conforms to

Copyable, Deinitable, Equatable, ImplicitlyCopyable, Movable, RegisterPassable, TrivialRegisterPassable
StructPeerAddressA peer's socket address as its connection was accepted, ucall_peer_address_t in C.1
struct PeerAddress

Fields

bytesSIMD[.uint8, 16]The address in network order: IPv4 in the first 4 bytes, IPv6 in all 16.
scopeUInt32The IPv6 scope, 0 otherwise.
portUInt16The port in host order.
familyAddressFamilyWhich of the families the address belongs to.
reservedUInt8Zero.

Conforms to

Copyable, Defaultable, Deinitable, ImplicitlyCopyable, Movable
__init__No address, as a failed read leaves it.
def __init__(out self)
StructDeferredA reply still sending a file or path part, which owns the outcome the event loop makes final.4
struct Deferred

status reads DEFERRED until then without blocking, and wait blocks until the reply is final and raises its failure. Dropping one that is not final waits for it, so it belongs off the polling thread, which is the one finishing it. The descriptors its parts borrow stay open until it is final.

Fields

libraryLibraryThe loaded library, held so the wait can still reach ucall_outcome_wait.
outcomeOwnedPointer[Outcome]The outcome the event loop writes, on the heap since it outlives the verb.
symbolStaticStringThe entry point the reply went through, carried into its failure.

Conforms to

Deinitable, Movable
__init__A pending outcome at a stable heap address, for a verb that may finish after it returns.
def __init__(out self, library: Library, symbol: StringSpan[ImmStaticOrigin])
__deinit__Waits until the reply is final, then frees its outcome. Calls ucall_outcome_wait.
def __deinit__(deinit self)
statusDEFERRED until the reply is final, then its final status; never blocks. Calls ucall_outcome_status.
def status(self) -> Status

Returns

The status the outcome holds.
waitBlocks until the reply is final, and raises its failure. Calls ucall_outcome_wait.
def wait(deinit self)

Raises

Error carrying the reply's failure.