Add support for HTTP/2 - #13039
Conversation
This implementation is backwards compatible, functional, but still incomplete.
for more information, see https://pre-commit.ci
| self._handler: Optional[asyncio.Protocol] = None | ||
|
|
||
| # ---- Transport callbacks forwarded to the real handler ---- | ||
| def connection_made(self, transport: asyncio.BaseTransport) -> None: |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #13039 +/- ##
==========================================
- Coverage 98.98% 98.94% -0.04%
==========================================
Files 132 139 +7
Lines 49073 50403 +1330
Branches 2553 2647 +94
==========================================
+ Hits 48576 49873 +1297
- Misses 373 392 +19
- Partials 124 138 +14
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. |
Merging this PR will degrade performance by 9.65%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ❌ | test_one_thousand_round_trip_websocket_binary_messages[tcp-small] |
46.8 ms | 54.4 ms | -14.11% |
| ❌ | test_one_thousand_round_trip_websocket_text_messages |
48.1 ms | 55.3 ms | -12.95% |
| ❌ | test_one_hundred_simple_get_requests_multiple_methods_route |
137 ms | 149.9 ms | -8.6% |
| ❌ | test_one_hundred_simple_get_requests_alternating_clients |
140.5 ms | 153.5 ms | -8.5% |
| ❌ | test_one_hundred_simple_get_requests[tcp] |
137.9 ms | 150.6 ms | -8.43% |
| ❌ | test_one_hundred_get_requests_with_1024_content_length_payload |
148.3 ms | 161.6 ms | -8.2% |
| ❌ | test_one_hundred_get_requests_with_1024_chunked_payload[tcp] |
150.2 ms | 163.5 ms | -8.13% |
| ❌ | test_ten_web_middlewares |
146.7 ms | 159.5 ms | -8.02% |
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing Moist-Cat:master (742899f) with master (d5d068c)2
Footnotes
-
83 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩
-
No successful run was found on
master(c0ef574) during the generation of this report, so d5d068c was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩
|
|
||
| try: | ||
| import sphinxcontrib.spelling # noqa | ||
| import sphinxcontrib.spelling |
It was necessary to add a semaphore to ensure the requests connect sequentially to the hosts and reuse connections when necessary. HTTP/2 uses a single connection per host.
for more information, see https://pre-commit.ci
|
I ran tests against remote servers (httpbin.org) to verify HTTP/2 indeed reduces latency. HTTP/2 Performance Test ResultsSystem Specs:
Test Configuration:
Batch Mean Latency (seconds)
Individual Request Latency Distribution
Statistical Analysis
A simple bar chart with the means (results vary because they are from a second test): We lose efficiency in CPU bound tasks (see #13039 (comment)) but I/O bound tasks are significantly faster. This is specially true for batch requests that require multiple TCP connections to the same host. |
|
I would like to know if the trade-offs (I/O vs CPU) are acceptable before writing the docs. |
HTTP/1.1 regression not inherent to h2. Caused by global |
Bigger blocker than the CPU/IO trade-off. h2 path returns |
PR Review — Add support for HTTP/2Ambitious, self-contained h2 implementation — but not mergeable yet: it doesn't integrate with the ClientResponse API and it regresses the default HTTP/1.1 path. Strengths worth calling out:
Blocking issues:
Recommendation: keep the h2 core, but rework the integration so it produces a 🔴 Blocking
1. HTTP/2 path returns Http2Response, not ClientResponse — breaks the public API contract
|
|
Either inheriting from or using Regarding the To deal with |
At a glance, it looks like there's still a lot of shared code. I'd suggest a refactor that produces a generic ClientResponse and then have both versions subclass it. So we end up with ClientResponseHttp1 and ClientResponseHttp2 or similar. It would really help us to review if that refactor was in a separate PR. Also consider that we can make modest breaking changes in v4, if that's required for a clean solution. I don't have capacity to review the PR in full yet, but I'll come back round to it before the 3.15 release. Might be worth a couple of rounds with the bot before then. Thanks for looking into this complex feature. |
|
Also, we generally don't use envvars, I'd probably add this as a ClientSession parameter instead. |
| import json | ||
| import os | ||
| import sys | ||
| import time |
I will open another PR with the refactored code.
I'm aware but I would rather not change more lines than necessary before the PR is ready to merge to avoid conflicts. Switching to a session-level parameter can be done later. |

What do these changes do?
Add HTTP/2 client support.
Why
Faster I/O bound operations (e.g., many requests to the same host) via multiplexing (handling several streams/requests inside a single connection).
How
AIOHTTP_ENABLE_EXPERIMENTAL_PROTOCOLS=1to allowh2negotiation via ALPN during the TLS handshake.ResponseHandlerwas substituted by a wrapper that conditionally switches protocols depending on the negotiated protocol.Semaphoreto avoid race conditions.This opening many HTTP/1.1 connections in parallel is now slower since it's done sequentially. That said, to know if connections can be pooled or not it's only necessary to wait until the first connection is done. Once it's know whether the host supports HTTP/2 or not, the rest of the requests can be done in parallel so it's possible to mitigate this performance hit substantially.
Backward compatibility
Opt-in via
AIOHTTP_ENABLE_EXPERIMENTAL_PROTOCOLS=1.Testing
Dependencies
hpack
Is it a substantial burden for the maintainers to support this?
Yes.
Related issue number
refs #5999
The implementation is self-contained and the changes to the current codebase are minimal and backwards compatible (that said, I make use of some black magic to be able to conditionally switch protocols).
Missing features (to the date):