Skip to content

Run prepare hooks for parser errors#12890

Closed
koriyoshi2041 wants to merge 1 commit into
aio-libs:masterfrom
koriyoshi2041:fix-parser-error-prepare-hooks
Closed

Run prepare hooks for parser errors#12890
koriyoshi2041 wants to merge 1 commit into
aio-libs:masterfrom
koriyoshi2041:fix-parser-error-prepare-hooks

Conversation

@koriyoshi2041

Copy link
Copy Markdown

What

Parser-error responses now flow through the application system-route path when the request factory can attach app match info. This lets middleware handle the 400 response and lets on_response_prepare callbacks adjust headers such as Server.

Fixes #6463.

Tests

  • python3 -m compileall -q aiohttp/web_app.py aiohttp/web_protocol.py aiohttp/web_runner.py tests/test_web_functional.py
  • git diff --check
  • PYTHONPATH=$PWD uv run --no-project --with pytest --with pytest-aiohttp --with pytest-mock --with pytest-timeout --with trustme --with cryptography --with proxy.py --with gunicorn --with uvloop --with dirty-equals --with coverage pytest tests/test_web_functional.py::test_signal_on_error_handler tests/test_web_functional.py::test_signal_on_parser_error_handler tests/test_web_functional.py::test_bad_method_for_c_http_parser_not_hangs -q

The C-parser-specific test is skipped locally because the C HTTP parser is unavailable in this checkout.

Risk

The change keeps the fallback protocol error handler for custom request factories that do not attach application match info.

@koriyoshi2041 koriyoshi2041 requested a review from asvetlov as a code owner June 10, 2026 05:35
@psf-chronographer psf-chronographer Bot added the bot:chronographer:provided There is a change note present in this PR label Jun 10, 2026
@codecov

codecov Bot commented Jun 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.95%. Comparing base (69344c6) to head (c8d844a).
⚠️ Report is 73 commits behind head on master.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #12890      +/-   ##
==========================================
+ Coverage   98.94%   98.95%   +0.01%     
==========================================
  Files         131      131              
  Lines       47099    47861     +762     
  Branches     2435     2483      +48     
==========================================
+ Hits        46600    47359     +759     
- Misses        376      377       +1     
- Partials      123      125       +2     
Flag Coverage Δ
Autobahn 22.28% <26.53%> (-0.12%) ⬇️
CI-GHA 98.92% <100.00%> (+0.01%) ⬆️
OS-Linux 98.68% <100.00%> (+0.01%) ⬆️
OS-Windows 97.04% <100.00%> (+<0.01%) ⬆️
OS-macOS 97.96% <100.00%> (+0.02%) ⬆️
Py-3.10 98.17% <100.00%> (+0.02%) ⬆️
Py-3.11 98.42% <100.00%> (+0.01%) ⬆️
Py-3.12 98.51% <100.00%> (+0.01%) ⬆️
Py-3.13 98.49% <100.00%> (+0.01%) ⬆️
Py-3.14 98.50% <100.00%> (+0.01%) ⬆️
Py-3.14t 97.59% <100.00%> (+0.03%) ⬆️
Py-pypy-3.11 97.44% <100.00%> (+0.02%) ⬆️
VM-macos 97.96% <100.00%> (+0.02%) ⬆️
VM-ubuntu 98.68% <100.00%> (+0.01%) ⬆️
VM-windows 97.04% <100.00%> (+<0.01%) ⬆️
cython-coverage 38.11% <100.00%> (+0.13%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@koriyoshi2041 koriyoshi2041 force-pushed the fix-parser-error-prepare-hooks branch from 39f6462 to c8d844a Compare June 10, 2026 05:42
@codspeed-hq

codspeed-hq Bot commented Jun 10, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 79 untouched benchmarks
⏩ 79 skipped benchmarks1


Comparing koriyoshi2041:fix-parser-error-prepare-hooks (c8d844a) with master (ff38c23)

Open in CodSpeed

Footnotes

  1. 79 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.

@koriyoshi2041

koriyoshi2041 commented Jun 10, 2026

Copy link
Copy Markdown
Author

CI update: the code/test/doc checks are green now. The only failing check I see is Backport label added, which is failing because the PR has no label starting with backport.

The contributing docs say an aiohttp committer adds the backport label when a bugfix should be backported, so I am leaving that label choice to maintainers rather than adding backport:skip myself.

Comment thread aiohttp/web_protocol.py
Comment on lines +631 to +634
if err_info is not None and getattr(request, "_match_info", None) is None:
request_handler: _RequestHandler[_Request] = cast(
_RequestHandler[_Request], self._make_error_handler(err_info)
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this end up doing about the same thing as the old code? Seems like it's not going to solve the features requested..

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the difference is where this lets the app request handler back in for the parser-error request.

For the default AppRunner path, web_runner._make_request() sees message is ERROR and attaches request._match_info = MatchInfoError(HTTPBadRequest()). With this change, RequestHandler.start() builds that request first; when _match_info exists, it uses the app-level request handler instead of the protocol _make_error_handler fallback.

Then Application._handle() takes the existing system-route match info, builds middleware for SystemRoute, and returns the HTTPBadRequest response through the normal app path. finish_response() still calls resp.prepare(request), so on_response_prepare fires. The fallback error handler remains for custom request factories that do not attach match info.

I rechecked the targeted coverage locally:

PYTHONPATH=$PWD uv run --no-project --with pytest --with pytest-aiohttp --with pytest-mock --with pytest-timeout --with trustme --with cryptography --with proxy.py --with gunicorn --with uvloop --with dirty-equals --with coverage pytest tests/test_web_functional.py::test_signal_on_error_handler tests/test_web_functional.py::test_signal_on_parser_error_handler tests/test_web_functional.py::test_bad_method_for_c_http_parser_not_hangs -q

Result: 2 passed, 1 skipped; the skipped one is the local checkout missing the C HTTP parser.

@Dreamsorcerer

Copy link
Copy Markdown
Member

Thanks for taking a look at this. Unfortunately, this task is deceptively complex and there's a number of details that need be addressed. I've created a new PR to resolve this issue instead: #12925.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:chronographer:provided There is a change note present in this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cannot remove "Server" header in all cases for security hardening

2 participants