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>
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 skeletoncrates/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_headreads 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 checkingraw.ends_with(b"\r\n"). rate_limit_waitis about Mattermost'sX-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, thencp 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/. Addpub mod http;tolib.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 gatewaydafter each. - 4. See it pass.
cargo test -p gatewayd --test http. Expected: 6 passed. - 5. Run the gate.
cargo fmt --all, thenmake 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 httppasses;make gateprintsgate: ok.
Stop and report if
- A test needs
read_headto read ahead into a buffer.