fix(http2): never write RST_STREAM ahead of the stream's still-queued HEADERS (#2008)
> [!NOTE]
> This PR was generated by an AI coding agent (Jetski) on behalf of @mosuem.
### Summary
`StreamHandler._terminateStream` (what `TransportStream.terminate()` ends up in) wrote the `RST_STREAM(CANCEL)` straight to the `FrameWriter`, bypassing `ConnectionMessageQueueOut`. If the stream's opening `HEADERS` were still sitting in that queue — because the socket was applying backpressure, or because the queue was head-of-line blocked behind flow-controlled `DATA` of another stream — the peer received `RST_STREAM` for a stream it had never seen, which is a connection error (RFC 9113 Section 6.4: *"RST_STREAM frames MUST NOT be sent for a stream in the "idle" state. If a RST_STREAM frame identifying an idle stream is received, the recipient MUST treat this as a connection error of type PROTOCOL_ERROR"*). A client cancelling a request shortly after starting it could thus take down the whole connection.
### Changes
- **`lib/src/flowcontrol/connection_queues.dart`**: Add `ConnectionMessageQueueOut.cancelStreamMessages(streamId)`, which drops the stream's queued `DataMessage`s (they would be discarded by the peer anyway) and reports whether a `HeadersMessage` for the stream is still queued.
- **`lib/src/streams/stream_handler.dart`**: In `_terminateStream`, enqueue a `ResetStreamMessage` behind the queued `HEADERS` when there are any; otherwise write the `RST_STREAM` directly as before.
### Test Verification (Fails Before $\rightarrow$ Passes After)
Added `rst-stream-is-sent-after-queued-headers-of-the-stream` in `test/client_test.dart`. The test keeps the client's `FrameWriter` in its "would buffer" state (the server's `StreamIterator` pauses the frame stream after each frame, and that pause propagates synchronously into the client's `BufferedSink`), opens a stream and cancels it immediately, then checks the wire order.
**Before fix:**
```text
00:00 +0 -1: client-tests client-errors rst-stream-is-sent-after-queued-headers-of-the-stream [E]
Expected: <Instance of 'HeadersFrame'> with `streamId`: <3>
Actual: <Instance of 'RstStreamFrame'>
Which: is not an instance of 'HeadersFrame'
```
**After fix:**
```text
00:00 +1: All tests passed!
```
A composable, Future-based library for making HTTP requests.
package:http contains a set of high-level functions and classes that make it easy to consume HTTP resources. It's multi-platform, and supports mobile, desktop, and the browser.
| Package | Description | Version |
|---|---|---|
| cronet_http | An Android Flutter plugin that provides access to the Cronet HTTP client. | |
| cupertino_http | A macOS/iOS Flutter plugin that provides access to the Foundation URL Loading System. | |
| flutter_http_example | Demonstrates how to use package:http in a Flutter app. | — |
| http | A composable, multi-platform, Future-based API for HTTP requests. | |
| http2 | A HTTP/2 implementation in Dart. | |
| http_client_conformance_tests | A library that tests whether implementations of package:http's Client class behave as expected. | — |
| http_multi_server | A dart:io HttpServer wrapper that handles requests from multiple servers. | |
| http_parser | A platform-independent package for parsing and serializing HTTP formats. | |
| http_profile | A library used by HTTP client authors to integrate with the DevTools Network View. | |
| ok_http | An Android Flutter plugin that provides access to the OkHttp HTTP client. | |
| web_socket | Any easy-to-use library for communicating with WebSockets that has multiple implementations. | |
| web_socket_channel | A WebSocket API that enables cross-platform communication via StreamChannel. | |
| web_socket_conformance_tests | A library that tests whether implementations of package:web_socket's WebSocket class behave as expected. | — |
If you'd like to contribute to any of these packages, see the Contributing Guide.