HTTP formats for MoonBit with no I/O: HTTP/1 to HTTP/3, WebSocket, SSE and multipart.
// 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
// A WebSocket message compressed the way RFC 7692 asks, and the bit that says so.
let terms = @pmd.Extension::agree(@pmd.offers(offer[:])[:]).unwrap()
let (frame, compressed) = @pmd.Writer::new(extension=terms).frame(@ws.Opcode::Text, body[:])
frame.encode_extension(rsv1=compressed) // one deflate segment, tail stripped
// A stored response, and what may be done with it.
let stored = @cache.Entry::read(status=200, headers, request, sent~, received~)
stored.verdict(request=@cache.control, presented=headers, now~) // Serve | Validate | Miss
stored.conditional() // the If-None-Match a cache sends instead
// And where a 302 sends the request next.
@client.follow(status=302, location="/next", from~, verb=@method.Method::Post, visited=[from][:])moon run src/examples/04-ws-handshake moon run src/examples/09-http3-frames
moon run src/examples/05-ws-frame moon run src/examples/10-http3-message
moon run src/examples/06-qpack-primitives moon run src/examples/11-http3-conn
moon run src/examples/07-qpack-field moon run src/examples/12-range
moon run src/examples/08-qpack-dynamic moon run src/examples/13-conditional
moon run src/examples/14-ws-compressed moon run src/examples/15-cache| Package | What | Specification |
|---|---|---|
| sse | Server-Sent Events, both directions | WHATWG HTML, the event-stream format |
| form | A request body read as the form its Content-Type says it is, multipart/form-data or application/x-www-form-urlencoded | WHATWG HTML form submission |
| 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 |
| cache | What may be stored, how long it stays fresh, how old it is, and whether it may answer: the decisions, with no store | RFC 9111 |
| client | Where a redirect sends a request, with which method and which credentials; and Alt-Svc read into the alternatives it names | RFC 9110 §15.4, RFC 7838 |
| 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 |
| pmd | The permessage-deflate extension: its four parameters, the offer and the answer, and a reader and writer over ws frames | RFC 7692 |
| deflate | Raw DEFLATE shaped for a stream that outlives a message: a window carried across messages, and a ceiling on what one may expand to | RFC 1951 |
| upgrade | The WebSocket opening handshake, both sides | RFC 6455 §4 |
| huffman | The Huffman code both header compressions use | RFC 7541 Appendix B |
| method | The request method: the nine standard ones and any other token, with whether each is safe and idempotent | RFC 9110 §9, RFC 5789 |
| header | A header field: a name and a value, as octets | RFC 9110 §5 |
| hpack | HTTP/2 header compression: both tables, both ends, and a bound on the list a block decodes to | 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; Conn, a whole connection for either end on h11's state machine, octets in and events out | RFC 9112 |
| http2 | HTTP/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 validation | RFC 9113, RFC 8441 |
| 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 = @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
// A compressed message is the one place a peer can ask for memory it did not
// pay for, so the reader bounds what one expands to, in octets.
@pmd.Reader::new(limit=1 * 1024 * 1024)
// And a writer can leave a message under this many octets alone, which is worth
// doing: a compressed "OK" is longer than "OK".
@pmd.Writer::new(threshold=64)| 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 |
| limit (pmd) | 16 MiB | The ceiling ws puts on a message anyway. RFC 7692 sets none, and a compressed message is where that absence would cost the most |
| threshold (pmd) | 0 | Every message compressed, which is what agreeing to the extension means. A caller compressing a bulletin board and not a chat box raises it |
moon add moonbitstack/moonhttpInstall
Download zipHTTP formats for MoonBit with no I/O: HTTP/1 to HTTP/3, WebSocket, SSE and multipart.