Latest Results
[PR #13886/b6c9917c backport][3.14] Add hdrs constants for widely used headers (#13889)
**This is a backport of PR #13886 as merged into master
(b6c9917c8c3bb3e2d4ce6f3d28f0851164014041).**
The cherry-pick conflicted only in the `tests/test_http_parser.py`
imports; the new tests also use the `loop` fixture that 3.14 has instead
of master's `event_loop`. Tested on 3.14 with the Cython parser built:
`tests/test_http_parser.py` passes; the full suite's only failures are 7
pytester-based tests that fail the same way on an unmodified 3.14 in
this environment.
---
<!-- Thank you for your contribution! -->
## What do these changes do?
Adds `hdrs` constants for widely used headers, 56 in all:
- sent by browsers on every request: `Sec-Fetch-Dest/Mode/Site/User`,
`Sec-CH-UA`, `Sec-CH-UA-Mobile`, `Sec-CH-UA-Platform`, `Sec-GPC`,
`Sec-Purpose`, `Upgrade-Insecure-Requests`, `Priority`;
- W3C trace context, on every request between OpenTelemetry-instrumented
services: `traceparent`, `tracestate`, `baggage` (spelled as the spec
does);
- response security and reporting: `Strict-Transport-Security`,
`Content-Security-Policy(-Report-Only)`, `Referrer-Policy`,
`Permissions-Policy`, `Cross-Origin-Embedder/Opener/Resource-Policy`,
`Origin-Agent-Cluster`, `X-Content-Type-Options`, `X-Frame-Options`,
`Server-Timing`, `Timing-Allow-Origin`, `NEL`, `Report-To`,
`Reporting-Endpoints`, `Accept-CH`, `Clear-Site-Data`;
- RFC fields that were missing: `Authentication-Info`,
`Proxy-Authentication-Info` (RFC 9110), `Content-Digest`, `Repr-Digest`,
`Want-Content-Digest`, `Want-Repr-Digest` (RFC 9530), `Alt-Svc`,
`Alt-Used`, `Cache-Status`, `CDN-Cache-Control`, `CDN-Loop`,
`Early-Data`, `Content-ID`;
- de facto: `X-Real-IP`, `X-Request-ID`, `X-Forwarded-Port` from
proxies, and `X-Correlation-ID`, `X-Powered-By`,
`X-RateLimit-Limit/Remaining/Reset`, `X-Requested-With`,
`X-XSS-Protection` from applications.
The constants are now grouped by where the header is defined (IETF RFCs,
web platform specs, de facto proxy headers, de facto application
headers), sorted within each group. Vendor headers (`CF-*`, `X-Amz*`,
`X-B3-*`) are left out.
The new test found a bug in `tools/gen.py`: it built the lookup trie
case-sensitively but named the C labels in upper case, so `Accept-CH`
and `Accept-Charset`, or `traceparent` and `TE`, produced the same label
and one branch was silently dropped. The trie is now keyed
case-insensitively; for the header list on master the generated
`_find_header.c` is byte-identical. The generated code also compiled
with warnings under `-Wall -Wextra` (a `const`-discarding pointer that
started one byte before the buffer, an unused label) and wrapped
`NEXT_CHAR()` in `{ } while(0);` rather than `do { } while (0)`; those
are fixed too.
`docs/multipart.rst` has used `aiohttp.hdrs.CONTENT_ID` since 2015, but
the constant never existed, so the example raised `AttributeError`; it
now exists, and the example's value is a valid message ID
(`<part1@example.com>`).
The generated `find_header()` picks the new names up, so the C parser
returns them as the shared `istr` constants instead of building a new
`str` per header. That saves the decode when parsing, and the `str` to
`istr` conversion `CIMultiDict` does when the key is read. Measured with
callgrind on 3.14, instructions per parsed request:
| request | parse | parse + `items()` | parse + 3 `get()` |
| --- | ---: | ---: | ---: |
| Chrome page load, 15 headers | 92,159 → 83,984 (-8.9%) | 146,100 →
132,423 (-9.4%) | 102,641 → 94,465 (-8.0%) |
| behind nginx, 7 headers | 57,322 → 55,209 (-3.7%) | 85,680 → 82,334
(-3.9%) | 67,823 → 65,710 (-3.1%) |
| curl, no new names (control) | 40,771 → 40,745 | 56,773 → 56,750 |
54,366 → 54,342 |
## Are there changes in behavior for the user?
New constants in `aiohttp.hdrs`. With the C parser, these header names
now come back as the `hdrs` `istr` objects rather than plain `str`; they
compare and hash the same.
## Is it a substantial burden for the maintainers to support this?
No, the lookup table is generated from `hdrs.py` by `tools/gen.py`.
## Related issue number
None.
## Checklist
- [x] I think the code is well written
- [x] Unit tests for the changes exist
- [x] Documentation reflects the changes (the multipart example; `hdrs`
constants are not documented individually)
- [x] If you provide code modification, please add yourself to
`CONTRIBUTORS.txt`: already listed
- [x] Add a new news fragment into the `CHANGES/` folder
Drafted with Claude Opus 5.5 (Claude Code); reviewed by @asvetlov.
<details>
<summary>Agent run details (optional, for reviewers)</summary>
- Built with Cython against multidict master (7.0.1.dev0), CPython
3.14.7.
- `pytest --numprocesses=8`: 5613 passed, 94 skipped, 17 xfailed.
- `AIOHTTP_NO_EXTENSIONS=1 pytest tests/test_http_parser.py
tests/test_web_functional.py tests/test_client_functional.py
tests/test_multipart.py`: 1090 passed, 443 skipped.
- New tests: every `hdrs` istr (except the always-rejected
`Sec-WebSocket-Key1`) in canonical, lower and upper case comes back from
the C parser as the same object; an unknown name still comes back as
`str`.
- pre-commit: all hooks pass except flake8, whose hook environment fails
to load `flake8-requirements` on 3.14 (`pkg_resources`); flake8 run
directly is clean.
- `make doc-spelling`: the only flagged words are 9 pre-existing ones in
`CHANGES.rst` and older fragments.
- Measurement: callgrind, `PYTHONHASHSEED=0`, instrumentation limited to
a loop of `HttpRequestParserC.feed_data()` on one request, (2000 runs -
1000 runs) / 1000.
- Re-measured after the full list was added: every row within 40
instructions of the table above, so the larger lookup costs nothing
measurable.
</details> [PR #13886/b6c9917c backport][3.15] Add hdrs constants for widely used headers (#13888)
**This is a backport of PR #13886 as merged into master
(b6c9917c8c3bb3e2d4ce6f3d28f0851164014041).**
The cherry-pick conflicted only in the `tests/test_http_parser.py`
imports; the new tests also use the `loop` fixture that 3.15 has instead
of master's `event_loop`. Tested on 3.15 with the Cython parser built:
`tests/test_http_parser.py` passes; the full suite's only failures are 7
pytester-based tests that fail the same way on an unmodified 3.15 in
this environment.
---
<!-- Thank you for your contribution! -->
## What do these changes do?
Adds `hdrs` constants for widely used headers, 56 in all:
- sent by browsers on every request: `Sec-Fetch-Dest/Mode/Site/User`,
`Sec-CH-UA`, `Sec-CH-UA-Mobile`, `Sec-CH-UA-Platform`, `Sec-GPC`,
`Sec-Purpose`, `Upgrade-Insecure-Requests`, `Priority`;
- W3C trace context, on every request between OpenTelemetry-instrumented
services: `traceparent`, `tracestate`, `baggage` (spelled as the spec
does);
- response security and reporting: `Strict-Transport-Security`,
`Content-Security-Policy(-Report-Only)`, `Referrer-Policy`,
`Permissions-Policy`, `Cross-Origin-Embedder/Opener/Resource-Policy`,
`Origin-Agent-Cluster`, `X-Content-Type-Options`, `X-Frame-Options`,
`Server-Timing`, `Timing-Allow-Origin`, `NEL`, `Report-To`,
`Reporting-Endpoints`, `Accept-CH`, `Clear-Site-Data`;
- RFC fields that were missing: `Authentication-Info`,
`Proxy-Authentication-Info` (RFC 9110), `Content-Digest`, `Repr-Digest`,
`Want-Content-Digest`, `Want-Repr-Digest` (RFC 9530), `Alt-Svc`,
`Alt-Used`, `Cache-Status`, `CDN-Cache-Control`, `CDN-Loop`,
`Early-Data`, `Content-ID`;
- de facto: `X-Real-IP`, `X-Request-ID`, `X-Forwarded-Port` from
proxies, and `X-Correlation-ID`, `X-Powered-By`,
`X-RateLimit-Limit/Remaining/Reset`, `X-Requested-With`,
`X-XSS-Protection` from applications.
The constants are now grouped by where the header is defined (IETF RFCs,
web platform specs, de facto proxy headers, de facto application
headers), sorted within each group. Vendor headers (`CF-*`, `X-Amz*`,
`X-B3-*`) are left out.
The new test found a bug in `tools/gen.py`: it built the lookup trie
case-sensitively but named the C labels in upper case, so `Accept-CH`
and `Accept-Charset`, or `traceparent` and `TE`, produced the same label
and one branch was silently dropped. The trie is now keyed
case-insensitively; for the header list on master the generated
`_find_header.c` is byte-identical. The generated code also compiled
with warnings under `-Wall -Wextra` (a `const`-discarding pointer that
started one byte before the buffer, an unused label) and wrapped
`NEXT_CHAR()` in `{ } while(0);` rather than `do { } while (0)`; those
are fixed too.
`docs/multipart.rst` has used `aiohttp.hdrs.CONTENT_ID` since 2015, but
the constant never existed, so the example raised `AttributeError`; it
now exists, and the example's value is a valid message ID
(`<part1@example.com>`).
The generated `find_header()` picks the new names up, so the C parser
returns them as the shared `istr` constants instead of building a new
`str` per header. That saves the decode when parsing, and the `str` to
`istr` conversion `CIMultiDict` does when the key is read. Measured with
callgrind on 3.14, instructions per parsed request:
| request | parse | parse + `items()` | parse + 3 `get()` |
| --- | ---: | ---: | ---: |
| Chrome page load, 15 headers | 92,159 → 83,984 (-8.9%) | 146,100 →
132,423 (-9.4%) | 102,641 → 94,465 (-8.0%) |
| behind nginx, 7 headers | 57,322 → 55,209 (-3.7%) | 85,680 → 82,334
(-3.9%) | 67,823 → 65,710 (-3.1%) |
| curl, no new names (control) | 40,771 → 40,745 | 56,773 → 56,750 |
54,366 → 54,342 |
## Are there changes in behavior for the user?
New constants in `aiohttp.hdrs`. With the C parser, these header names
now come back as the `hdrs` `istr` objects rather than plain `str`; they
compare and hash the same.
## Is it a substantial burden for the maintainers to support this?
No, the lookup table is generated from `hdrs.py` by `tools/gen.py`.
## Related issue number
None.
## Checklist
- [x] I think the code is well written
- [x] Unit tests for the changes exist
- [x] Documentation reflects the changes (the multipart example; `hdrs`
constants are not documented individually)
- [x] If you provide code modification, please add yourself to
`CONTRIBUTORS.txt`: already listed
- [x] Add a new news fragment into the `CHANGES/` folder
Drafted with Claude Opus 5.5 (Claude Code); reviewed by @asvetlov.
<details>
<summary>Agent run details (optional, for reviewers)</summary>
- Built with Cython against multidict master (7.0.1.dev0), CPython
3.14.7.
- `pytest --numprocesses=8`: 5613 passed, 94 skipped, 17 xfailed.
- `AIOHTTP_NO_EXTENSIONS=1 pytest tests/test_http_parser.py
tests/test_web_functional.py tests/test_client_functional.py
tests/test_multipart.py`: 1090 passed, 443 skipped.
- New tests: every `hdrs` istr (except the always-rejected
`Sec-WebSocket-Key1`) in canonical, lower and upper case comes back from
the C parser as the same object; an unknown name still comes back as
`str`.
- pre-commit: all hooks pass except flake8, whose hook environment fails
to load `flake8-requirements` on 3.14 (`pkg_resources`); flake8 run
directly is clean.
- `make doc-spelling`: the only flagged words are 9 pre-existing ones in
`CHANGES.rst` and older fragments.
- Measurement: callgrind, `PYTHONHASHSEED=0`, instrumentation limited to
a loop of `HttpRequestParserC.feed_data()` on one request, (2000 runs -
1000 runs) / 1000.
- Re-measured after the full list was added: every row within 40
instructions of the table above, so the larger lookup costs nothing
measurable.
</details> Latest Branches
0%
patchback/backports/3.14/d60a88e283e0dbc7d91d831b43e3f456d8d1a39f/pr-13891 0%
patchback/backports/3.15/d60a88e283e0dbc7d91d831b43e3f456d8d1a39f/pr-13891 0%
aiolibsbot:koan/pep639-license-metadata © 2026 CodSpeed Technology