Sign in

    moonhttp

    moonhttp — the HTTP family for MoonBit: conditional requests and entity-tags, range requests, HTTP/1.1, HTTP/2 and HTTP/3 framing and connections, HPACK and QPACK header compression, WebSocket framing and its handshake, Server-Sent Events, cookies, and form bodies by content type, each a package of its own. Bytes in, events out; no sockets.

    http
    http2
    etag
    http3
    websocket
    hpack
    qpack
    protocol
    moonbit
    Download zip
    Version
    0.15.0
    License
    Apache-2.0
    Last updated
    3 hours ago
    Downloads
    807

    #moonhttp

    The HTTP family for MoonBit: the formats a request and a response are written in, with nothing about sockets in them.

    // An event stream, one frame at a time.
    @sse.Event::new(data="{\"seq\":1}", kind="tick", id="1").encode()
    @sse.decode(received[:]) // the frames as written
    @sse.events(received[:]) // what an EventSource dispatches from them

    // A cookie set on the way out and read on the way back.
    @cookie.Cookie::new("sid", token, http_only=true, secure=true).encode()
    @cookie.get(request_header[:], "sid"[:])

    // A posted form, whichever way the browser encoded it.
    let form = @form.parse(body[:], content_type[:])
    form.field("name")
    form.file("avatar")

    // A second request for something that has not changed.
    let etag = @conditional.hash(body[:], digest=@sha2.Hasher::new())
    @conditional.Ask::read(headers[:]).evaluate(verb="GET", etag~) // Go | Fresh | Failed

    Run moon run examples/tour for the whole surface in one go. The worked examples run a package each:

    moon run examples/04-ws-handshake moon run examples/09-http3-frames moon run examples/05-ws-frame moon run examples/10-http3-message moon run examples/06-qpack-primitives moon run examples/11-http3-conn moon run examples/07-qpack-field moon run examples/12-range moon run examples/08-qpack-dynamic moon run examples/13-conditional

    The API reference is generated from the sources' doc comments: moonbitstack.github.io/moonhttp.

    #Packages

    PackageWhatSpecification
    sseServer-Sent Events, both directionsWHATWG HTML, the event-stream format
    formA request body read as the form its Content-Type says it is, multipart/form-data or application/x-www-form-urlencodedWHATWG HTML form submission
    rangeAsking for part of a representation: the fields, the arithmetic that resolves a range against a length, and the multi-range bodyRFC 9110 §14
    conditionalEntity-tags, the five precondition fields, and the order they are weighed inRFC 9110 §8.8, §13
    cookieCookie and Set-Cookie, each read and writtenRFC 6265, and its revision for SameSite
    wsWebSocket framing: opcodes, masking, fragment reassembly, close statusesRFC 6455 §5, §7.4.1
    upgradeThe WebSocket opening handshake, both sidesRFC 6455 §4
    huffmanThe Huffman code both header compressions useRFC 7541 Appendix B
    headerA header field: a name and a value, as octetsRFC 9110 §5
    hpackHTTP/2 header compression: both tables, both ends, and a bound on the list a block decodes toRFC 7541
    qpackHTTP/3 header compression: both tables, the field lines, both instruction streamsRFC 9204
    http1HTTP/1.1 messages: the request and status lines, field lines, body framing and the chunked coding; Conn, a whole connection for either end on h11's state machine, octets in and events outRFC 9112
    http2HTTP/2 framing: the nine-octet header, all ten frame types, the connection preface; Conn, a whole connection for either end after python-hyper h2, with stream states, flow control, settings, push and field validationRFC 9113, RFC 8441
    http3HTTP/3 frames, their placement rules, settings, stream types and connectionRFC 9114

    #One vocabulary

    The same word means the same thing in every package here, and in every other package the organisation publishes. encode and decode are a pair, and parse is for a format with no symmetric writer. code is an enum's value on the wire, so a WebSocket close status is status and never code. A limit passed is Exceeded(limit~, got~), a predicate reads as an adjective or is named is_, and a refusal is Refused.

    #Configuration

    Every bound and every leniency is an argument, and every default is the one the mainstream uses.

    // A bound belongs to an endpoint. Either form names one.
    let small = @moonmime.Limits::new(parts=8, part_size=64 * 1024)
    let same = { ..@moonmime.limits, parts: 8, part_size: 64 * 1024 }

    @form.parse(body[:], content_type[:], limits=small)

    // By default a body that is not a form is an empty form, and a multipart body
    // that stops making sense gives up the parts it read. `strict` says so instead.
    @form.parse(body[:], content_type[:], strict=true) // raises NotAForm, or Malformed(at~)

    @sse.Event::of("hello").encode(space=false) // drop the optional space

    SettingDefaultWhy that one
    limits.parts1000Starlette's max_files and max_fields
    limits.part_size1 MiBpython-multipart's in-memory part size
    strictoffA truncated upload is ordinary on a dropped connection, and "this request carried no form" is an answer rather than a failure. On is for the caller who would rather be told
    spaceonThe space after a colon is optional in the format and universal on the wire; a reader strips it either way

    Limits has no per-call mirror, and therefore no precedence rule. A bound belongs to an endpoint rather than to a request — an avatar upload and a spreadsheet import are two endpoints, each with its own — so the record is the only place it comes from and there is nothing to arbitrate. Where a setting can arrive from two places, as in mooncred, the precedence is published with it.

    #Downloads

    A download needs three answers, and each comes from its own place: what the body is and under what name to save it is moonmime's guess_type and content_disposition; resuming it and seeking in it is range; not sending it again is conditional. Reading the file and sending it in pieces is the server's, not this library's — there are no sockets here.

    #Framing, not transport

    Nothing here opens a connection or writes to one. sse turns an event into the bytes of a frame and back; sending each frame as its own chunk is the server's job, and it matters — a stream delivered as one body is not a stream, because a client dispatches an event only when it reads that event's blank line.

    A client wants the second of sse's two readings. decode answers each frame as written; events answers what a browser's EventSource hands its listeners — no event for a frame without data, the type defaulting to message, and the id and reconnection delay carried from frame to frame as the stream's own state.

    form reads a body that has already arrived. Both of its encodings are bounded by a Limits the caller sets, because the body came from whoever sent it: a million empty parts and one enormous part are two ways of asking a server to allocate more than it has. A form over its bounds is refused whole, not truncated — a handler given the first thousand parts of a larger form would be answering a request nobody sent.

    #What is checked

    sse is measured against the two example streams the WHATWG specification prints, which are the cases an implementation that trims too much or too little gets wrong; then every frame it writes is read back as the event that wrote it, the exact bytes of each frame are pinned, all three line endings are accepted, and the things a reader must ignore rather than refuse are ignored. events is read against the same streams, and each rule by which it differs from decode is checked by a test that turns red when the rule is removed.

    form is measured on both encodings by content type, upper case and a charset parameter included, on what strict turns into an error, and on one bound holding for both. The decoders behind it are measured where they live: moonmime against a browser's multipart body, moonurl against the parser and serialiser cases of web-platform-tests.

    conditional follows §13, which states rules rather than printing vectors: the comparison table of §8.8.3.2 entry for entry, then §13.2.2's steps one at a time, then against each other — the order is the part that gets got wrong. The content tag is pinned to SHA-256's vector for hello.

    hpack is measured against all twelve worked examples of RFC 7541 Appendix C, in both directions: the encoder reproduces the published blocks octet for octet and the decoder reproduces the published header lists, with the three requests of a series sharing one dynamic table and the three responses evicting under a 256-octet ceiling. huffman is measured against the coded strings those same blocks contain.

    http3 is measured section by section: every frame type round-trips, every placement rule of §7.2 is checked on each of the three stream kinds, the settings and the unidirectional stream types round-trip, and a request stream decodes into its pseudo-headers, its fields and its body.

    qpack is measured against the instruction vectors of RFC 9204 Appendix B and, section by section, against every representation §4.5 defines — indexed and post-base, relative and absolute, with a name reference and with a literal name.

    ws is measured against all five frames RFC 6455 §5.7 prints: the unmasked and masked text messages, the fragmented one, the ping with its pong, and the two long length forms. upgrade is measured against the key and proof §1.3 prints, which is the one computation in it.

    #What lives elsewhere

    URI references and data URLs are moonurl; media types, multipart and Content-Disposition are moonmime.

    The variable-length integers are not here either. HTTP/3's frames are counted in QUIC's varint and both header compressions count in RFC 7541's prefixed integer; neither belongs to HTTP, and both are written in more than one protocol. They are moonvar, which this depends on and which depends on nothing.

    TLS is moontls and QUIC is moonquic, so that parsing an HTTP message does not mean carrying a handshake. Compression is moonzip.

    #Install

    moon add moonbitstack/moonhttp

    #Licence

    Apache-2.0. See LICENSE.