mooncat — a native ASGI 3.0 server for MoonBit (← uvicorn), built on moonbitlang/async and the moonasgi SEAM.
Dependencies
flowchart LR
net["moonbitlang/async<br/>(sockets · TLS · HTTP/1.1)"] --> cat["**mooncat**<br/>accept loop"]
cat -->|"Scope / Receive / Send"| asgi(["moonasgi SEAM"])
asgi --> app["your app<br/>(moonapi, …)"]// An ASGI app: respond 200 with a text body.
let app : @moonasgi.AsgiApp = (_scope, _receive, send) => {
send(@moonasgi.Event::HttpResponseStart(
status=200, headers=[("content-type", "text/plain")]))
send(@moonasgi.Event::HttpResponseBody(
body=b"Hello from mooncat!", more_body=false))
}
// Serve it (inside an async context / task group):
@mooncat.serve(app, host="127.0.0.1", port=8000)@mooncat.serve_tls(app,
certificate_file="certs/cert.pem", // PEM cert (OpenSSL platforms)
private_key_file="certs/key.pem", // PEM key (OpenSSL platforms)
pfx_file="certs/dev.pfx", // PKCS#12 (Windows / SChannel)
port=8443)let handle = @mooncat.ShutdownHandle::new()
g.spawn(() => @mooncat.serve_graceful(app, @mooncat.Config::new(port=8000), handle~))
// … later, from anywhere:
handle.shutdown() // stop accepting → drain in-flight → lifespan shutdown → close@mooncat.serve_h2c(app, host="127.0.0.1", port=8000)pub suberror FlowError {
FlowError(String)
}pub suberror Http3ConnError {
Http3ConnError(String)
}pub suberror Http3FrameError {
Http3FrameError(String)
}pub suberror LifespanError {
LifespanError(String)
}pub suberror QpackError {
QpackError(String)
}pub suberror QuicPayloadError {
QuicPayloadError(String)
}pub suberror ReassemblyError {
ReassemblyError(String)
}pub suberror StreamStateError {
StreamStateError(String)
}pub suberror WsError {
WsError(String)
}pub struct Http3Conn {
role : Http3Role
local_settings : Http3Settings
peer_settings : Http3Settings?
control_seen : Bool
}pub(all) struct Http3Request {
meth : String
scheme : String
authority : String
path : String
headers : Array[(String, String)]
body : Bytes
}fn Http3Settings::new(qpack_max_table_capacity : UInt64, max_field_section_size : UInt64, qpack_blocked_streams : UInt64) -> Http3Settingspub(all) enum Http3UniEvent {
ControlEstablished(Http3Settings)
QpackEncoder
QpackDecoder
Push(UInt64)
Reserved(UInt64)
} derive(Eq, Debug)pub struct NewReno {
cwnd : Int64
ssthresh : Int64
bytes_in_flight : Int64
max_datagram_size : Int64
}pub struct PacketNumberSpace {
next_pn : Int64
received : AckRangeSet
largest_acked : Int64?
ack_eliciting_pending : Bool
}fn PacketNumberSpace::on_packet_received(self : PacketNumberSpace, pn : Int64, ack_eliciting : Bool) -> Unitpub(all) struct QpackDynamicTable {
entries : Array[(Bytes, Bytes)]
used : Int
capacity : Int
insert_count : Int
dropped : Int
}fn QpackDynamicTable::apply(self : QpackDynamicTable, inst : QpackEncoderInst) -> Bool raise QpackErrorpub struct QuicConnection {
is_server : Bool
initial_space : PacketNumberSpace
handshake_space : PacketNumberSpace
application_space : PacketNumberSpace
initial_crypto : StreamReassembler
handshake_crypto : StreamReassembler
application_crypto : StreamReassembler
}fn QuicConnection::build_ack(self : QuicConnection, level : QuicLevel, ack_delay : UInt64) -> QuicFrame?fn QuicConnection::on_ack_received(self : QuicConnection, level : QuicLevel, largest : Int64) -> Unitfn QuicConnection::on_crypto_frame(self : QuicConnection, level : QuicLevel, offset : UInt64, data : Bytes) -> Bytes raise ReassemblyErrorfn QuicConnection::on_packet_received(self : QuicConnection, level : QuicLevel, pn : Int64, ack_eliciting : Bool) -> Unitpub(all) enum QuicFrame {
Padding(Int)
Ping
Crypto(offset~ : UInt64, data~ : Bytes)
Stream(id~ : UInt64, offset~ : UInt64, fin~ : Bool, data~ : Bytes)
Ack(largest~ : UInt64, delay~ : UInt64, first_range~ : UInt64, ranges~ : Array[(UInt64, UInt64)])
ResetStream(id~ : UInt64, error_code~ : UInt64, final_size~ : UInt64)
StopSending(id~ : UInt64, error_code~ : UInt64)
NewToken(token~ : Bytes)
MaxData(UInt64)
MaxStreamData(id~ : UInt64, max~ : UInt64)
MaxStreams(bidi~ : Bool, max~ : UInt64)
DataBlocked(UInt64)
StreamDataBlocked(id~ : UInt64, max~ : UInt64)
StreamsBlocked(bidi~ : Bool, max~ : UInt64)
NewConnectionId(seq~ : UInt64, retire_prior_to~ : UInt64, conn_id~ : Bytes, reset_token~ : Bytes)
RetireConnectionId(UInt64)
PathChallenge(Bytes)
PathResponse(Bytes)
HandshakeDone
ConnectionClose(error_code~ : UInt64, frame_type~ : UInt64?, reason~ : Bytes)
} derive(Eq, Debug)pub(all) struct QuicLongHeader {
packet_type : QuicLongPacketType
type_specific : Int
version : UInt
dcid : Bytes
scid : Bytes
} derive(Eq, Debug)pub struct QuicRecovery {
rtt : RttEstimator
tracker : SentPacketTracker
cc : NewReno
max_ack_delay : Int64
granularity : Int64
packet_threshold : Int64
pto_count : Int
last_sent_time : Int64
}fn QuicRecovery::new(max_datagram_size : Int64, max_ack_delay : Int64, granularity : Int64) -> QuicRecoveryfn QuicRecovery::on_ack_received(self : QuicRecovery, frame : QuicFrame, now : Int64, ack_delay : Int64) -> Array[Int64] raisefn QuicSendFlow::record_stream_sent(self : QuicSendFlow, id : UInt64, n : UInt64) -> Unit raise FlowErrorpub struct QuicSender {
sched : QuicStreamScheduler
recovery : QuicRecovery
data : Map[UInt64, Bytes]
in_packet : Map[Int64, Array[StreamSend]]
retransmit : Array[StreamSend]
next_pn : Int64
}fn QuicSender::new(initial_max_data : UInt64, initial_max_stream_data : UInt64, max_datagram_size : Int64, max_ack_delay : Int64, granularity : Int64, max_frame : UInt64) -> QuicSenderpub struct QuicServerConn {
initial_keys : QuicPacketKeys
handshake : TlsServerHandshake
conn : QuicConnection
}fn QuicServerConn::receive_handshake(self : QuicServerConn, packet : Bytes, client_hs_keys : QuicPacketKeys, client_hs_secret : Bytes) -> Bool raisefn QuicServerConn::send_handshake_flight(self : QuicServerConn, server_hs_secret : Bytes, encrypted_extensions : Bytes, certificate : Bytes, certificate_verify : Bytes, out_dcid : Bytes, out_scid : Bytes, pn : Int64) -> Bytes raisefn QuicServerConn::send_handshake_flight_signed(self : QuicServerConn, server_hs_secret : Bytes, encrypted_extensions : Bytes, certificate : Bytes, signing_key : EcdsaPrivateKey, out_dcid : Bytes, out_scid : Bytes, pn : Int64) -> Bytes raisefn QuicServerConn::send_server_hello(self : QuicServerConn, server_hello : TlsServerHello, out_dcid : Bytes, out_scid : Bytes, pn : Int64) -> Bytespub struct QuicStreamScheduler {
flow : QuicSendFlow
streams : Map[UInt64, StreamOut]
order : Array[UInt64]
cursor : Int
max_frame : UInt64
}fn QuicStreamScheduler::on_max_stream_data(self : QuicStreamScheduler, id : UInt64, new_max : UInt64) -> Unitpub struct RecvFlow {
received : UInt64
consumed : UInt64
limit : UInt64
window : UInt64
}type RecvStreampub struct RttEstimator {
latest_rtt : Int64
min_rtt : Int64
smoothed_rtt : Int64
rttvar : Int64
has_sample : Bool
}fn RttEstimator::update(self : RttEstimator, latest_rtt : Int64, ack_delay : Int64, max_ack_delay : Int64) -> Unitpub struct SendFlow {
sent : UInt64
limit : UInt64
}pub(all) struct SentPacket {
pn : Int64
time_sent : Int64
size : Int64
}fn SentPacketTracker::detect_lost_packets(self : SentPacketTracker, now : Int64, packet_threshold : Int64, time_threshold : Int64) -> Array[SentPacket]fn SentPacketTracker::detect_lost_time(self : SentPacketTracker, now : Int64, time_threshold : Int64) -> Array[Int64]fn SentPacketTracker::on_ack_ranges(self : SentPacketTracker, ranges : Array[(Int64, Int64)]) -> Int64fn SentPacketTracker::on_sent(self : SentPacketTracker, pn : Int64, time_sent : Int64, size : Int64) -> Unitpub struct StreamManager {
streams : Map[UInt64, RecvStream]
conn_flow : RecvFlow
conn_bytes : UInt64
stream_window : UInt64
}fn StreamManager::on_stream_frame(self : StreamManager, id : UInt64, offset : UInt64, data : Bytes, fin : Bool) -> Bytes raisepub(all) struct StreamOut {
pending : UInt64
offset : UInt64
fin_queued : Bool
fin_sent : Bool
}pub struct StreamReassembler {
consumed : UInt64
intervals : Array[(UInt64, Bytes)]
final_size : UInt64?
}fn StreamReassembler::insert(self : StreamReassembler, offset : UInt64, data : Bytes, fin : Bool) -> Unit raise ReassemblyErrorpub(all) struct StreamSend {
stream : UInt64
offset : UInt64
length : UInt64
fin : Bool
}pub(all) struct TlsCert {
certificate_file : String
private_key_file : String
pfx_file : String
}pub(all) struct TlsClientHello {
random : Bytes
session_id : Bytes
cipher_suites : Array[Int]
extensions : Array[TlsExtension]
} derive(Eq, Debug)pub struct TlsServerHandshake {
state : TlsServerState
transcript : TranscriptHash
buffer : Bytes
client_hello : Bytes
}fn TlsServerHandshake::sent_flight(self : TlsServerHandshake, request_client_cert : Bool) -> Unit raisepub(all) struct TlsServerHello {
random : Bytes
session_id : Bytes
cipher_suite : Int
extensions : Array[TlsExtension]
} derive(Eq, Debug)fn ack_fields_to_ranges(largest : UInt64, first_range : UInt64, pairs : Array[(UInt64, UInt64)]) -> Array[(UInt64, UInt64)] raise QuicAckErrorfn aes128_gcm_open(key : Bytes, nonce : Bytes, packet : Bytes, aad : Bytes) -> Bytes?fn aes128_gcm_seal(key : Bytes, nonce : Bytes, plaintext : Bytes, aad : Bytes) -> Bytesfn base64_decode(data : Bytes) -> Bytesfn base64_encode(data : Bytes) -> Bytesfn der_bit_string(content : Bytes) -> Bytesfn der_explicit(tag_num : Int, content : Bytes) -> Bytesfn der_integer(magnitude : Bytes) -> Bytesfn der_length(n : Int) -> Bytesfn der_tlv(tag : Int, value : Bytes) -> Bytesfn der_utc_time(s : String) -> Byteslet h3_settings_max_field_section_size : UInt64let h3_settings_qpack_blocked_streams : UInt64let h3_settings_qpack_max_table_capacity : UInt64fn hkdf_expand(prk : Bytes, info : Bytes, length : Int) -> Bytesfn hkdf_expand_label(secret : Bytes, label : Bytes, context : Bytes, length : Int) -> Bytesfn hkdf_extract(salt : Bytes, ikm : Bytes) -> Bytesfn hmac_sha256(key : Bytes, msg : Bytes) -> Bytesfn http3_request(meth : String, scheme : String, authority : String, path : String, headers : Array[(String, String)], body : Bytes) -> Bytesfn http3_serve(request_bytes : Bytes, handler : (Http3Request) -> Http3Response) -> Bytes raise Http3FrameErrorfn qpack_decode_field_section_dyn(input : Bytes, table : QpackDynamicTable, max_entries : Int) -> Array[(String, String)] raise QpackErrorfn qpack_decode_required_insert_count(encoded : Int, max_entries : Int, total_inserts : Int) -> Int raise QpackErrorfn qpack_decode_section_prefix(input : BytesView, max_entries : Int, total_inserts : Int) -> (Int, Int, Int)? raise QpackErrorfn qpack_encode_dynamic(headers : Array[(String, String)], table : QpackDynamicTable, max_entries : Int, huffman? : Bool) -> (Bytes, Bytes)fn qpack_encode_section_prefix(req_insert_count : Int, base : Int, max_entries : Int) -> Bytesfn qpack_encode_string(data : Bytes, prefix_bits : Int, pattern : Int, huffman : Bool) -> Bytesfn qpack_entry_size(name : Bytes, value : Bytes) -> Intfn qpack_int_decode(input : BytesView, prefix_bits : Int) -> (Int, Int)?fn qpack_int_encode(value : Int, prefix_bits : Int, flags : Int) -> Bytesfn qpack_max_entries(max_capacity : Int) -> Intfn qpack_static_find(name : String, value : String) -> Int?fn qpack_static_find_name(name : String) -> Int?fn qpack_static_get(index : Int) -> (String, String)?fn qpack_static_len() -> Intfn quic_application_keys(master_secret : Bytes, transcript_hash : Bytes, is_client : Bool) -> QuicPacketKeysfn quic_client_initial_secret(dcid : Bytes) -> Bytesfn quic_handshake_keys(handshake_secret : Bytes, transcript_hash : Bytes, is_client : Bool) -> QuicPacketKeysfn quic_header_protect(packet : Bytes, pn_offset : Int, pn_length : Int, hp_key : Bytes) -> Bytesfn quic_header_unprotect(packet : Bytes, pn_offset : Int, hp_key : Bytes) -> (Bytes, Int)fn quic_hp_mask(hp_key : Bytes, sample : Bytes) -> Bytesfn quic_initial_secret(dcid : Bytes) -> Bytesfn quic_key_update(secret : Bytes) -> Bytesfn quic_pad_payload(payload : Bytes, min_size : Int) -> Bytesfn quic_pn_decode(largest_pn : Int64, truncated_pn : Int64, pn_nbits : Int) -> Int64fn quic_pn_encode(full_pn : Int64, largest_acked : Int64?) -> Bytesfn quic_pn_len(full_pn : Int64, largest_acked : Int64?) -> Intfn quic_protect_handshake(version : UInt, dcid : Bytes, scid : Bytes, packet_number : Int64, pn_length : Int, payload : Bytes, key : Bytes, iv : Bytes, hp : Bytes) -> Bytesfn quic_protect_initial(version : UInt, dcid : Bytes, scid : Bytes, token : Bytes, packet_number : Int64, pn_length : Int, payload : Bytes, key : Bytes, iv : Bytes, hp : Bytes) -> Bytesfn quic_protect_short(spin : Bool, key_phase : Bool, dcid : Bytes, packet_number : Int64, pn_length : Int, payload : Bytes, key : Bytes, iv : Bytes, hp : Bytes) -> Bytesfn quic_recv_handshake(packet : Bytes, keys : QuicPacketKeys) -> (Array[QuicFrame], Int64)? raise QuicPayloadErrorfn quic_recv_initial(packet : Bytes, keys : QuicPacketKeys) -> (Array[QuicFrame], Int64)? raise QuicPayloadErrorfn quic_recv_short(packet : Bytes, dcid_len : Int, keys : QuicPacketKeys) -> (Array[QuicFrame], Int64)? raise QuicPayloadErrorfn quic_retry_integrity_tag(retry_without_tag : Bytes, odcid : Bytes) -> Bytesfn quic_retry_verify(retry_packet : Bytes, odcid : Bytes) -> Boolfn quic_send_handshake(version : UInt, dcid : Bytes, scid : Bytes, packet_number : Int64, pn_length : Int, frames : Array[QuicFrame], keys : QuicPacketKeys) -> Bytesfn quic_send_initial(version : UInt, dcid : Bytes, scid : Bytes, token : Bytes, packet_number : Int64, pn_length : Int, frames : Array[QuicFrame], keys : QuicPacketKeys) -> Bytesfn quic_send_short(spin : Bool, key_phase : Bool, dcid : Bytes, packet_number : Int64, pn_length : Int, frames : Array[QuicFrame], keys : QuicPacketKeys) -> Bytesfn quic_server_initial_secret(dcid : Bytes) -> Bytesfn quic_unprotect_initial(packet : Bytes, pn_offset : Int, key : Bytes, iv : Bytes, hp : Bytes) -> (Bytes, Int64)?fn quic_unprotect_short(packet : Bytes, dcid_len : Int, key : Bytes, iv : Bytes, hp : Bytes) -> (Bytes, Int64)?fn quic_varint_decode(b : BytesView) -> (UInt64, Int)?fn quic_varint_encode(v : UInt64) -> Bytesfn recv_stream_next(state : RecvStreamState, event : RecvStreamEvent) -> RecvStreamState raise StreamStateErrorasync fn reload_watch(path : String, on_reload : async () -> Unit) -> Unitfn send_stream_next(state : SendStreamState, event : SendStreamEvent) -> SendStreamState raise StreamStateErrorasync fn serve_graceful(app : async (Scope, async () -> Event, async (Event) -> Unit) -> Unit, config : Config, handle? : ShutdownHandle) -> Unitfn stream_is_client_initiated(id : UInt64) -> Boolfn stream_is_locally_initiated(id : UInt64, is_server : Bool) -> Boolfn stream_is_writable(id : UInt64, is_server : Bool) -> Boolfn stream_sequence(id : UInt64) -> UInt64fn tls13_certificate_verify_check(key : EcdsaPublicKey, context : String, transcript_hash : Bytes, body : Bytes) -> Boolfn tls13_certificate_verify_content(context : String, transcript_hash : Bytes) -> Bytesfn tls13_certificate_verify_sign(key : EcdsaPrivateKey, context : String, transcript_hash : Bytes) -> Bytesfn tls13_client_ap_traffic_secret(master_secret : Bytes, transcript_hash : Bytes) -> Bytesfn tls13_client_hs_traffic_secret(handshake_secret : Bytes, transcript_hash : Bytes) -> Byteslet tls13_cv_context_client : Stringlet tls13_cv_context_server : Stringfn tls13_derive_secret(secret : Bytes, label : Bytes, transcript_hash : Bytes) -> Bytesfn tls13_early_secret(psk : Bytes) -> Bytesfn tls13_ecdhe_handshake_traffic_secret(private_key : Bytes, peer_public : Bytes, transcript_hash : Bytes, is_client : Bool) -> Bytesfn tls13_ecdhe_secrets_from_client_hello(client_hello : Bytes, server_private : Bytes, transcript_hash : Bytes) -> (Bytes, Bytes)?fn tls13_ecdhe_shared(private_key : Bytes, peer_public : Bytes) -> Bytesfn tls13_finished_key(base_key : Bytes) -> Bytesfn tls13_finished_verify(base_key : Bytes, transcript_hash : Bytes, verify_data : Bytes) -> Boolfn tls13_finished_verify_data(base_key : Bytes, transcript_hash : Bytes) -> Bytesfn tls13_handshake_secret(early_secret : Bytes, ecdhe : Bytes) -> Bytesfn tls13_handshake_secret_from_ecdhe(private_key : Bytes, peer_public : Bytes) -> Bytesfn tls13_master_secret(handshake_secret : Bytes) -> Bytesfn tls13_server_ap_traffic_secret(master_secret : Bytes, transcript_hash : Bytes) -> Bytesfn tls13_server_hs_secret_from_client_hello(client_hello : Bytes, server_private : Bytes, transcript_hash : Bytes) -> Bytes?fn tls13_server_hs_traffic_secret(handshake_secret : Bytes, transcript_hash : Bytes) -> Bytesfn tls13_transcript_hash(messages : Bytes) -> Bytesfn tls13_x25519_public(private_key : Bytes) -> Byteslet tls_client_hello : Intfn tls_decode_key_share_entry(view : BytesView) -> (Int, Bytes, Int)?fn tls_decode_key_share_server(view : BytesView) -> (Int, Bytes)?fn tls_encode_handshake(msg_type : Int, body : Bytes) -> Bytesfn tls_encode_key_share_entry(group : Int, key : Bytes) -> Bytesfn tls_encode_key_share_server(group : Int, key : Bytes) -> Byteslet tls_ext_alpn : Intlet tls_ext_key_share : Intlet tls_ext_quic_transport_params : Intfn tls_parse_handshake(b : BytesView) -> (Int, Bytes)?let tls_server_hello : Intfn tls_server_send_flight(state : TlsServerState, request_client_cert : Bool) -> TlsServerState raise StreamStateErrorlet tls_sig_ecdsa_secp256r1_sha256 : Intlet tp_ack_delay_exponent : UInt64let tp_active_connection_id_limit : UInt64let tp_initial_max_data : UInt64let tp_initial_max_stream_data_bidi_local : UInt64let tp_initial_max_stream_data_bidi_remote : UInt64let tp_initial_max_stream_data_uni : UInt64let tp_initial_max_streams_bidi : UInt64let tp_initial_max_streams_uni : UInt64let tp_initial_source_connection_id : UInt64let tp_max_ack_delay : UInt64let tp_max_idle_timeout : UInt64let tp_max_udp_payload_size : UInt64fn websocket_accept_key(sec_websocket_key : String) -> Stringlet websocket_guid : Stringfn websocket_handshake_response(sec_websocket_key : String, subprotocol : String?, extra_headers : Array[(String, String)]) -> Bytesfn ws_close_payload(code : Int, reason : String) -> Bytesfn x25519(k : Bytes, u : Bytes) -> Bytesfn x25519_base() -> Bytesfn x509_alg_ec_public_key() -> Bytesfn x509_alg_ecdsa_sha256() -> Bytesfn x509_common_name(cn : String) -> Bytesfn x509_self_signed(key : EcdsaPrivateKey, serial : Bytes, common_name : String, not_before : String, not_after : String) -> Bytesfn x509_tbs_certificate(serial : Bytes, common_name : String, not_before : String, not_after : String, pub_key : EcdsaPublicKey) -> Bytesfn x509_validity(not_before : String, not_after : String) -> BytesInstall
Download zipmooncat — a native ASGI 3.0 server for MoonBit (← uvicorn), built on moonbitlang/async and the moonasgi SEAM.
Dependencies