feat(http2): configurable receive windows and frame size (#1999)

* feat(http2): configurable client receive windows and frame size

A client built on package:http2 moved at most 64 KiB per round trip: the
connection receive window is fixed at the protocol's 65535 bytes, so
streamWindowSize alone cannot raise throughput. With a 4 MiB body from a Go
net/http server through 100 ms of added RTT, HTTP/1.1 (dart:io HttpClient)
took about 350 ms and this package 7.1 s.

- Settings.connectionWindowSize grants a larger connection window with a
  WINDOW_UPDATE on stream 0 after the initial SETTINGS.
- Settings.maxFrameSize is sent as SETTINGS_MAX_FRAME_SIZE.
- The initial streamWindowSize and maxFrameSize apply to this end's receive
  side from the moment they are sent: no stream exists yet and the peer reads
  the SETTINGS before any stream. Waiting for the ACK let a large frame on an
  early stream fail flow control, and capped every early response's first
  round trip at 64 KiB.
- Above the default 65535-byte window, received data is acknowledged once
  per half window rather than once per DATA frame (the existing TODO in
  IncomingWindowHandler.dataProcessed); a default-sized window is still
  acknowledged at once.
- The new settings are validated up front with the others.

Defaults are unchanged. With streamWindowSize 6 MiB, connectionWindowSize
16 MiB and maxFrameSize 1 MiB, the same transfer takes about 340 ms (140 ms
without added delay, against 155 ms for HTTP/1.1).

test/flow_control_test.dart checks each behaviour frame by frame against a
hand-driven peer; each test fails with its part removed.

* fix(http2): address review on configurable receive windows

- SettingsHandler applies the peer's SETTINGS_MAX_FRAME_SIZE, so frames
  sent to it use its limit, and rejects a value outside 16384..16777215 as
  a PROTOCOL_ERROR (RFC 9113 section 6.5.2).
- The ACK of this end's own SETTINGS_INITIAL_WINDOW_SIZE no longer emits
  onInitialWindowSizeChange: only the peer's value sizes the send windows
  of open streams (RFC 9113 section 6.9.2).
- connectionWindowSize must be at least 65535, the connection window's
  starting size; a smaller value was silently ignored.
- IncomingWindowHandler.dataProcessed ignores 0 bytes, so an empty DATA
  frame never yields a WINDOW_UPDATE with an increment of 0 (RFC 9113
  section 6.9).

* fix(http2): pre-apply only a larger stream window; second review round

- _setupConnection pre-applies streamWindowSize only when it does not
  shrink the window. A client may send on a stream before it has read the
  server's SETTINGS (RFC 9113 section 3.4), within the default 65535 bytes
  (section 6.9.2); a server with a smaller window pre-applied tore such a
  connection down with FLOW_CONTROL_ERROR. A smaller window applies on ACK,
  as on master. Regression test from the review in flow_control_test.dart.
- WINDOW_UPDATEs are batched from twice the default window, so a window
  just above 65535 is no longer acknowledged less often than the default.
- flow_control_test.dart checks that a frame was not sent after a PING
  round trip instead of a wall-clock sleep.
- CHANGELOG condensed to the package's style.

* test(http2): the ACK test exercises the peerSettings guard

With a window above 65535 the pre-application already makes the ACK's
difference 0, so the test passed without the guard. It now uses a window
below 65535, which is not pre-applied, and checks that a further stream
WINDOW_UPDATE is fully usable after the ACK (fails without the guard:
66535 sent instead of 131070). Doc on streamWindowSize: batching starts
from 2 * 65535, inclusive.
9 files changed
tree: 2d77374230a1857e23b76be2ce560e02a204fe3e
  1. .gemini/
  2. .github/
  3. pkgs/
  4. tool/
  5. .gitattributes
  6. .gitignore
  7. analysis_options.yaml
  8. CONTRIBUTING.md
  9. LICENSE
  10. mono_repo.yaml
  11. README.md
README.md

Build Status

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.

Packages

PackageDescriptionVersion
cronet_httpAn Android Flutter plugin that provides access to the Cronet HTTP client.pub package
cupertino_httpA macOS/iOS Flutter plugin that provides access to the Foundation URL Loading System.pub package
flutter_http_exampleAn Flutter app that demonstrates how to configure and use package:http.—
httpA composable, multi-platform, Future-based API for HTTP requests.pub package
http2A HTTP/2 implementation in Dart.pub package
http_client_conformance_testsA library that tests whether implementations of package:http's Client class behave as expected.
http_multi_serverA dart:io HttpServer wrapper that handles requests from multiple servers.pub package
http_parserA platform-independent package for parsing and serializing HTTP formats.pub package
http_profileA library used by HTTP client authors to integrate with the DevTools Network View.pub package
ok_httpAn Android Flutter plugin that provides access to the OkHttp HTTP client and the OkHttp WebSocket API.pub package
web_socketAny easy-to-use library for communicating with WebSockets that has multiple implementations.pub package
web_socket_channelStreamChannel wrappers for WebSockets. Provides a cross-platform WebSocketChannel API.package issues
web_socket_conformance_testsA library that tests whether implementations of package:web_socket's WebSocket class behave as expected.

Contributing

If you'd like to contribute to any of these packages, see the Contributing Guide.