Files
boxmaker/docs/plans/M4a/06-gatewayd-http.md
T
kyleandClaude Opus 5.5 0339dc13b2 Plan M4a: gatewayd in 15 tasks, with skeletons and given tests
Each task's tests were run against a reference at its end state; the end states were replayed
from master in order with the gate at each step (650 to 762 tests); each skeleton compiles
against its tests and fails them. The reference is kept off this machine. Lessons T27 (every
wait in a test has a limit) and T28 (mutate the reference before hand-over) come from this work.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 19:05:44 -07:00

2.8 KiB

M4a task 06: HTTP/1.1 requests

Branch: m4a (run git switch m4a; git status --short must be empty, otherwise stop) Commit subject: gatewayd: http, requests over a stream with size caps and rate-limit waits

Goal

Mattermost's REST calls are HTTP/1.1, one connection per request (Connection: close), over a net::Stream. This module writes a request and reads the response. The server is not trusted: a response head over 16 KiB or a body over 4 MiB is an error, and every limit is checked before reading or allocating. The WebSocket handshake (task 07) reuses read_head, which therefore must not read a byte past the blank line that ends the head: the WebSocket's first frame may follow at once.

Files

  • Copy: crates/gatewayd/tests/http.rs, and the skeleton crates/gatewayd/src/http.rs
  • Modify: crates/gatewayd/src/lib.rs (pub mod http;), docs/implementer-log.md

The skeleton

Written: the constants MAX_HEAD (16 KiB), MAX_BODY (4 MiB) and MAX_RATE_WAIT (60 s); HttpError (Io, Protocol(String), TooLarge(&'static str)) with Display and From<std::io::Error>; Head { status, headers } and Response { head, body }.

To fill, each with its steps above it: Head::header, write_request, request, read_head, read_body, read_line, read_chunked, rate_limit_wait.

Rules that apply to all of them:

  • read_head reads one byte at a time. That is slow and it is right: it must stop exactly at the end of the head.
  • A retry on ErrorKind::Interrupted, and nowhere else.
  • No subtraction that can wrap and no indexing that can go out of bounds: saturating_sub, get, get_mut. raw.truncate(raw.len() - 2) is fine only right after checking raw.ends_with(b"\r\n").
  • rate_limit_wait is about Mattermost's X-Ratelimit-Reset, which is a Unix time in seconds; the test also gives it a small number of seconds, which some servers send.

Steps

  • 1. Copy. git switch m4a, then cp docs/plans/M4a/files/crates/gatewayd/tests/http.rs crates/gatewayd/tests/ && cp docs/plans/M4a/files/crates/gatewayd/src/http.rs crates/gatewayd/src/. Add pub mod http; to lib.rs.
  • 2. See it fail. cargo test -p gatewayd --test http. Expected: it compiles and 6 tests fail.
  • 3. Fill the functions in the order above, cargo check -p gatewayd after each.
  • 4. See it pass. cargo test -p gatewayd --test http. Expected: 6 passed.
  • 5. Run the gate. cargo fmt --all, then make gate. Expected last line: gate: ok.
  • 6. Log and commit. git add crates/gatewayd docs/implementer-log.md Cargo.lock && git commit

Done when

  • cargo test -p gatewayd --test http passes; make gate prints gate: ok.

Stop and report if

  • A test needs read_head to read ahead into a buffer.