fix(http2): preserve the complete response on RST_STREAM(NO_ERROR) (#2012)
> [!NOTE]
> This PR was generated by an AI coding agent (Jetski) on behalf of @mosuem.
### Summary
RFC 9113 Section 8.1: *"A server can send a complete response prior to the client sending an entire request if the response does not depend on any portion of the request that has not been sent and received. When this is true, a server MAY request that the client abort transmission of a request without error by sending a RST_STREAM with an error code of NO_ERROR after sending a complete response."* grpc-go, for example, does exactly this after sending trailers on client-streaming calls.
Previously `StreamHandler._handleRstFrame` treated every `RST_STREAM` as abnormal: it called `_closeStreamAbnormally`, which dropped the messages still buffered in `ConnectionMessageQueueIn` / `StreamMessageQueueIn` (anything the application had not consumed yet, e.g. because its listener was paused) and failed the stream with `StreamTransportException (errorCode: 0)` — even though the complete response, including trailers, had already arrived.
### Changes
- **`lib/src/flowcontrol/connection_queues.dart`**: Add `ConnectionMessageQueueIn.forceDispatchStreamMessages(streamId)`, which hands the buffered messages of one stream to its `StreamMessageQueueIn` (crediting the connection window for the DATA bytes, as the regular dispatch path does).
- **`lib/src/streams/stream_handler.dart`**: In `_handleRstFrame`, when `errorCode == NO_ERROR` and the stream is `HalfClosedRemote` or `Closed` (the peer has already sent `END_STREAM`), flush the buffered messages, close the outgoing side and drop pending outgoing data, and complete the incoming side normally. `onTerminated` is still invoked with `NO_ERROR`. The stream keeps counting as active until the application has drained the preserved messages, exactly like a normally completed stream.
### Test Verification (Fails Before $\rightarrow$ Passes After)
Added `rst-stream-no-error-after-end-stream-preserves-messages` in `test/client_test.dart`: the server sends `HEADERS`, `DATA`, trailers with `END_STREAM`, `RST_STREAM(NO_ERROR)` while the client's listener is paused (a `PING`/ACK round trip is used as the barrier instead of a sleep).
**Before fix:**
```text
00:00 +0 -1: client-tests client-errors rst-stream-no-error-after-end-stream-preserves-messages [E]
Expected: empty
Actual: [
StreamTransportException:HTTP/2 error: Stream error: Stream was terminated by peer (errorCode: 0).
]
```
**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.