moonhttp — the HTTP family for MoonBit: URI references and percent coding, conditional requests and entity-tags, range requests, HTTP/1.1, HTTP/2 and HTTP/3 framing, HPACK and QPACK header compression, WebSocket framing and its handshake, Server-Sent Events, cookies, data URLs, multipart bodies and media types, each a package of its own. Bytes in, events out; no sockets.
Dependencies
// 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 = @mime.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 | Failedmoon run examples/04-ws-handshake moon run examples/10-http3-message
moon run examples/05-ws-frame moon run examples/11-http3-conn
moon run examples/06-qpack-primitives moon run examples/12-range
moon run examples/07-qpack-field moon run examples/13-url
moon run examples/08-qpack-dynamic moon run examples/14-dataurl
moon run examples/09-http3-frames moon run examples/15-conditional| Package | What | Specification |
|---|---|---|
| sse | Server-Sent Events, both directions | WHATWG HTML, the event-stream format |
| mime | multipart/form-data and application/x-www-form-urlencoded | RFC 7578, WHATWG URL §5.1 |
| url | A URI reference: taking one apart, putting it back, resolving a relative one, and percent coding by component | RFC 3986 |
| dataurl | A representation written inside the reference to it | RFC 2397 |
| media | What a response says its body is, and under what name to save it | RFC 9110 §8.3, RFC 6266 |
| range | Asking for part of a representation: the fields, the arithmetic that resolves a range against a length, and the multi-range body | RFC 9110 §14 |
| conditional | Entity-tags, the five precondition fields, and the order they are weighed in | RFC 9110 §8.8, §13 |
| cookie | Cookie and Set-Cookie, each read and written | RFC 6265, and its revision for SameSite |
| ws | WebSocket framing: opcodes, masking, fragment reassembly, close statuses | RFC 6455 §5, §7.4.1 |
| upgrade | The WebSocket opening handshake, both sides | RFC 6455 §4 |
| huffman | The Huffman code both header compressions use | RFC 7541 Appendix B |
| header | A header field: a name and a value, as octets | RFC 9110 §5 |
| hpack | HTTP/2 header compression: both tables, both ends | RFC 7541 |
| qpack | HTTP/3 header compression: both tables, the field lines, both instruction streams | RFC 9204 |
| http1 | HTTP/1.1 messages: the request and status lines, field lines, body framing and the chunked coding | RFC 9112 |
| http2 | HTTP/2 framing: the nine-octet header, all ten frame types, the connection preface | RFC 9113 |
| http3 | HTTP/3 frames, their placement rules, settings, stream types and connection | RFC 9114 |
// A bound belongs to an endpoint. Either form names one.
let small = @mime.Limits::new(parts=8, part_size=64 * 1024)
let same = { ..@mime.limits, parts: 8, part_size: 64 * 1024 }
@mime.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.
@mime.parse(body[:], content_type[:], strict=true) // raises NotAForm
@mime.multipart(body[:], edge[:], strict=true) // raises Malformed(at~)
@sse.Event::of("hello").encode(space=false) // drop the optional space| Setting | Default | Why that one |
|---|---|---|
| limits.parts | 1000 | Starlette's max_files and max_fields |
| limits.part_size | 1 MiB | python-multipart's in-memory part size |
| strict | off | A 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 |
| space | on | The space after a colon is optional in the format and universal on the wire; a reader strips it either way |
@media.type_of("report.pdf") // "application/pdf"
@media.disposition("my report.pdf") // filename*=utf-8''my%20report.pdf
@media.disposition("cover.png", inline=true) // display it, do not save itmoon add moonbitstack/moonhttpInstall
Download zipmoonhttp — the HTTP family for MoonBit: URI references and percent coding, conditional requests and entity-tags, range requests, HTTP/1.1, HTTP/2 and HTTP/3 framing, HPACK and QPACK header compression, WebSocket framing and its handshake, Server-Sent Events, cookies, data URLs, multipart bodies and media types, each a package of its own. Bytes in, events out; no sockets.
Dependencies