Task 08's `header` was one todo!() with a dozen branches; Ornith planned it in its head until the turn ran out (tip T25). `next_message` and `header` are now written as glue over seven small helpers, and task 09's `poll` over four, each checked to pass when filled from its comments. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
79 lines
3.9 KiB
Markdown
79 lines
3.9 KiB
Markdown
# M4a task 09: the WebSocket connection
|
|
|
|
**Branch:** `m4a` (run `git switch m4a`; `git status --short` must be empty, otherwise stop)
|
|
**Commit subject:** `gatewayd: ws conn, messages, pings, closing and a dead peer`
|
|
|
|
## Goal
|
|
|
|
The connection that uses tasks 07 and 08: open it, send text, and wait for the next message while
|
|
keeping the connection alive (spec section 6):
|
|
|
|
- A ping from the server is answered with a pong carrying the same payload.
|
|
- We send a ping every `ping_every`. No bytes at all for `dead_after` means the peer is dead.
|
|
- A close frame is answered with a close frame, and the connection ends (`Closed`); so does the end
|
|
of the stream.
|
|
- Any protocol error ends the connection. `gatewayd` then reconnects (task 14); nothing panics.
|
|
|
|
## Files
|
|
|
|
- Copy: `crates/gatewayd/tests/ws_conn.rs`, `crates/gatewayd/tests/support/ws_server.rs`, and the
|
|
skeleton `crates/gatewayd/src/ws/conn.rs`
|
|
- Modify: `crates/gatewayd/src/ws/mod.rs` (`pub mod conn;`), `docs/implementer-log.md`
|
|
|
|
## The skeleton
|
|
|
|
```rust
|
|
pub const PATH: &str = "/api/v4/websocket";
|
|
pub struct Timing { pub ping_every: Duration, pub dead_after: Duration } // Clone, Copy
|
|
pub struct Ws { stream, decoder, random, timing, last_heard, last_ping }
|
|
|
|
impl Ws {
|
|
pub fn open(connector: &Connector, token: &str, timing: Timing,
|
|
mut random: Box<dyn Read + Send>) -> Result<Ws, WsError>;
|
|
fn send(&mut self, opcode: u8, payload: &[u8]) -> Result<(), WsError>;
|
|
pub fn send_text(&mut self, text: &str) -> Result<(), WsError>;
|
|
pub fn poll(&mut self, wait: Duration) -> Result<Option<String>, WsError>; // written
|
|
fn take_messages(&mut self) -> Result<Option<String>, WsError>;
|
|
fn keep_alive(&mut self, now: Instant) -> Result<(), WsError>;
|
|
fn read_timeout(&self, until: Instant, now: Instant) -> Duration;
|
|
fn read_some(&mut self, timeout: Duration) -> Result<(), WsError>;
|
|
pub fn close(mut self);
|
|
}
|
|
pub fn host_header(server: &ServerUrl) -> String;
|
|
```
|
|
|
|
**`poll` is written for you**: it is the glue, and it calls the four helpers after it. It returns
|
|
the next text message, or `None` after about `wait` with none, and does the pinging, the pong
|
|
answers and the dead-peer check on the way. Its read timeout is always the time to the **next
|
|
thing it must do** (the end of `wait`, the next ping, or the dead-after limit), so a quiet
|
|
connection neither spins nor oversleeps. Read it first.
|
|
|
|
Everything else is `todo!()`, each a few lines, with the steps and the expressions to use above it.
|
|
Use them as written; do not weigh other ways to write the same thing. Write the function, run
|
|
`cargo check -p gatewayd`, go on to the next.
|
|
|
|
`random` gives the handshake key and a fresh 4-byte mask for every frame we send (in `gatewayd`,
|
|
`/dev/urandom`).
|
|
|
|
## Steps
|
|
|
|
- [ ] **1. Copy.** `git switch m4a`, then
|
|
`cp docs/plans/M4a/files/crates/gatewayd/tests/ws_conn.rs crates/gatewayd/tests/ && cp docs/plans/M4a/files/crates/gatewayd/tests/support/ws_server.rs crates/gatewayd/tests/support/ && cp docs/plans/M4a/files/crates/gatewayd/src/ws/conn.rs crates/gatewayd/src/ws/`.
|
|
Add `pub mod conn;` to `crates/gatewayd/src/ws/mod.rs`.
|
|
- [ ] **2. See it fail.** `cargo test -p gatewayd --test ws_conn`. Expected: it compiles and 10
|
|
tests fail.
|
|
- [ ] **3. Fill `host_header`, `send`, `send_text`, `open`, `close`, then `take_messages`,
|
|
`keep_alive`, `read_timeout`, `read_some`**, `cargo check -p gatewayd` after each.
|
|
- [ ] **4. See it pass.** `cargo test -p gatewayd --test ws_conn`, five times. Expected: 10 passed
|
|
each time, in under a second.
|
|
- [ ] **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 ws_conn` passes five times running; `make gate` prints `gate: ok`.
|
|
|
|
## Stop and report if
|
|
|
|
- A test passes only sometimes, or takes seconds.
|