diff --git a/.mypy.ini b/.mypy.ini index 2167434fff4..a4e914e757d 100644 --- a/.mypy.ini +++ b/.mypy.ini @@ -1,5 +1,5 @@ [mypy] -files = aiohttp, examples, tests +files = aiohttp, docs/code, examples, tests check_untyped_defs = True follow_imports_for_stubs = True disallow_any_decorated = True diff --git a/CHANGES.rst b/CHANGES.rst index a240be05961..c190d1aa69c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -10,6 +10,1690 @@ .. towncrier release notes start +3.12.0 (2025-05-24) +=================== + +Bug fixes +--------- + +- Fixed :py:attr:`~aiohttp.web.WebSocketResponse.prepared` property to correctly reflect the prepared state, especially during timeout scenarios -- by :user:`bdraco` + + + *Related issues and pull requests on GitHub:* + :issue:`6009`, :issue:`10988`. + + + +- Response is now always True, instead of using MutableMapping behaviour (False when map is empty) + + + *Related issues and pull requests on GitHub:* + :issue:`10119`. + + + +- Fixed connection reuse for file-like data payloads by ensuring buffer + truncation respects content-length boundaries and preventing premature + connection closure race -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10325`, :issue:`10915`, :issue:`10941`, :issue:`10943`. + + + +- Fixed pytest plugin to not use deprecated :py:mod:`asyncio` policy APIs. + + + *Related issues and pull requests on GitHub:* + :issue:`10851`. + + + +- Fixed :py:class:`~aiohttp.resolver.AsyncResolver` not using the ``loop`` argument in versions 3.x where it should still be supported -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10951`. + + + + +Features +-------- + +- Added a comprehensive HTTP Digest Authentication client middleware (DigestAuthMiddleware) + that implements RFC 7616. The middleware supports all standard hash algorithms + (MD5, SHA, SHA-256, SHA-512) with session variants, handles both 'auth' and + 'auth-int' quality of protection options, and automatically manages the + authentication flow by intercepting 401 responses and retrying with proper + credentials -- by :user:`feus4177`, :user:`TimMenninger`, and :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`2213`, :issue:`10725`. + + + +- Added client middleware support -- by :user:`bdraco` and :user:`Dreamsorcerer`. + + This change allows users to add middleware to the client session and requests, enabling features like + authentication, logging, and request/response modification without modifying the core + request logic. Additionally, the ``session`` attribute was added to ``ClientRequest``, + allowing middleware to access the session for making additional requests. + + + *Related issues and pull requests on GitHub:* + :issue:`9732`, :issue:`10902`, :issue:`10945`, :issue:`10952`, :issue:`10959`, :issue:`10968`. + + + +- Allow user setting zlib compression backend -- by :user:`TimMenninger` + + This change allows the user to call :func:`aiohttp.set_zlib_backend()` with the + zlib compression module of their choice. Default behavior continues to use + the builtin ``zlib`` library. + + + *Related issues and pull requests on GitHub:* + :issue:`9798`. + + + +- Added support for overriding the base URL with an absolute one in client sessions + -- by :user:`vivodi`. + + + *Related issues and pull requests on GitHub:* + :issue:`10074`. + + + +- Added ``host`` parameter to ``aiohttp_server`` fixture -- by :user:`christianwbrock`. + + + *Related issues and pull requests on GitHub:* + :issue:`10120`. + + + +- Detect blocking calls in coroutines using BlockBuster -- by :user:`cbornet`. + + + *Related issues and pull requests on GitHub:* + :issue:`10433`. + + + +- Added ``socket_factory`` to :py:class:`aiohttp.TCPConnector` to allow specifying custom socket options + -- by :user:`TimMenninger`. + + + *Related issues and pull requests on GitHub:* + :issue:`10474`, :issue:`10520`, :issue:`10961`, :issue:`10962`. + + + +- Started building armv7l manylinux wheels -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10797`. + + + +- Implemented shared DNS resolver management to fix excessive resolver object creation + when using multiple client sessions. The new ``_DNSResolverManager`` singleton ensures + only one ``DNSResolver`` object is created for default configurations, significantly + reducing resource usage and improving performance for applications using multiple + client sessions simultaneously -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10847`, :issue:`10923`, :issue:`10946`. + + + +- Upgraded to LLHTTP 9.3.0 -- by :user:`Dreamsorcerer`. + + + *Related issues and pull requests on GitHub:* + :issue:`10972`. + + + +- Optimized small HTTP requests/responses by coalescing headers and body into a single TCP packet -- by :user:`bdraco`. + + This change enhances network efficiency by reducing the number of packets sent for small HTTP payloads, improving latency and reducing overhead. Most importantly, this fixes compatibility with memory-constrained IoT devices that can only perform a single read operation and expect HTTP requests in one packet. The optimization uses zero-copy ``writelines`` when coalescing data and works with both regular and chunked transfer encoding. + + When ``aiohttp`` uses client middleware to communicate with an ``aiohttp`` server, connection reuse is more likely to occur since complete responses arrive in a single packet for small payloads. + + This aligns ``aiohttp`` with other popular HTTP clients that already coalesce small requests. + + + *Related issues and pull requests on GitHub:* + :issue:`10991`. + + + + +Improved documentation +---------------------- + +- Improved documentation for middleware by adding warnings and examples about + request body stream consumption. The documentation now clearly explains that + request body streams can only be read once and provides best practices for + sharing parsed request data between middleware and handlers -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`2914`. + + + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Removed non SPDX-license description from ``setup.cfg`` -- by :user:`devanshu-ziphq`. + + + *Related issues and pull requests on GitHub:* + :issue:`10662`. + + + +- Added support for building against system ``llhttp`` library -- by :user:`mgorny`. + + This change adds support for :envvar:`AIOHTTP_USE_SYSTEM_DEPS` environment variable that + can be used to build aiohttp against the system install of the ``llhttp`` library rather + than the vendored one. + + + *Related issues and pull requests on GitHub:* + :issue:`10759`. + + + +- ``aiodns`` is now installed on Windows with speedups extra -- by :user:`bdraco`. + + As of ``aiodns`` 3.3.0, ``SelectorEventLoop`` is no longer required when using ``pycares`` 4.7.0 or later. + + + *Related issues and pull requests on GitHub:* + :issue:`10823`. + + + +- Fixed compatibility issue with Cython 3.1.1 -- by :user:`bdraco` + + + *Related issues and pull requests on GitHub:* + :issue:`10877`. + + + + +Contributor-facing changes +-------------------------- + +- Sped up tests by disabling ``blockbuster`` fixture for ``test_static_file_huge`` and ``test_static_file_huge_cancel`` tests -- by :user:`dikos1337`. + + + *Related issues and pull requests on GitHub:* + :issue:`9705`, :issue:`10761`. + + + +- Updated tests to avoid using deprecated :py:mod:`asyncio` policy APIs and + make it compatible with Python 3.14. + + + *Related issues and pull requests on GitHub:* + :issue:`10851`. + + + +- Added Winloop to test suite to support in the future -- by :user:`Vizonex`. + + + *Related issues and pull requests on GitHub:* + :issue:`10922`. + + + + +Miscellaneous internal changes +------------------------------ + +- Added support for the ``partitioned`` attribute in the ``set_cookie`` method. + + + *Related issues and pull requests on GitHub:* + :issue:`9870`. + + + +- Setting :attr:`aiohttp.web.StreamResponse.last_modified` to an unsupported type will now raise :exc:`TypeError` instead of silently failing -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10146`. + + + + +---- + + +3.12.0rc1 (2025-05-24) +====================== + +Bug fixes +--------- + +- Fixed :py:attr:`~aiohttp.web.WebSocketResponse.prepared` property to correctly reflect the prepared state, especially during timeout scenarios -- by :user:`bdraco` + + + *Related issues and pull requests on GitHub:* + :issue:`6009`, :issue:`10988`. + + + +- Response is now always True, instead of using MutableMapping behaviour (False when map is empty) + + + *Related issues and pull requests on GitHub:* + :issue:`10119`. + + + +- Fixed connection reuse for file-like data payloads by ensuring buffer + truncation respects content-length boundaries and preventing premature + connection closure race -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10325`, :issue:`10915`, :issue:`10941`, :issue:`10943`. + + + +- Fixed pytest plugin to not use deprecated :py:mod:`asyncio` policy APIs. + + + *Related issues and pull requests on GitHub:* + :issue:`10851`. + + + +- Fixed :py:class:`~aiohttp.resolver.AsyncResolver` not using the ``loop`` argument in versions 3.x where it should still be supported -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10951`. + + + + +Features +-------- + +- Added a comprehensive HTTP Digest Authentication client middleware (DigestAuthMiddleware) + that implements RFC 7616. The middleware supports all standard hash algorithms + (MD5, SHA, SHA-256, SHA-512) with session variants, handles both 'auth' and + 'auth-int' quality of protection options, and automatically manages the + authentication flow by intercepting 401 responses and retrying with proper + credentials -- by :user:`feus4177`, :user:`TimMenninger`, and :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`2213`, :issue:`10725`. + + + +- Added client middleware support -- by :user:`bdraco` and :user:`Dreamsorcerer`. + + This change allows users to add middleware to the client session and requests, enabling features like + authentication, logging, and request/response modification without modifying the core + request logic. Additionally, the ``session`` attribute was added to ``ClientRequest``, + allowing middleware to access the session for making additional requests. + + + *Related issues and pull requests on GitHub:* + :issue:`9732`, :issue:`10902`, :issue:`10945`, :issue:`10952`, :issue:`10959`, :issue:`10968`. + + + +- Allow user setting zlib compression backend -- by :user:`TimMenninger` + + This change allows the user to call :func:`aiohttp.set_zlib_backend()` with the + zlib compression module of their choice. Default behavior continues to use + the builtin ``zlib`` library. + + + *Related issues and pull requests on GitHub:* + :issue:`9798`. + + + +- Added support for overriding the base URL with an absolute one in client sessions + -- by :user:`vivodi`. + + + *Related issues and pull requests on GitHub:* + :issue:`10074`. + + + +- Added ``host`` parameter to ``aiohttp_server`` fixture -- by :user:`christianwbrock`. + + + *Related issues and pull requests on GitHub:* + :issue:`10120`. + + + +- Detect blocking calls in coroutines using BlockBuster -- by :user:`cbornet`. + + + *Related issues and pull requests on GitHub:* + :issue:`10433`. + + + +- Added ``socket_factory`` to :py:class:`aiohttp.TCPConnector` to allow specifying custom socket options + -- by :user:`TimMenninger`. + + + *Related issues and pull requests on GitHub:* + :issue:`10474`, :issue:`10520`, :issue:`10961`, :issue:`10962`. + + + +- Started building armv7l manylinux wheels -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10797`. + + + +- Implemented shared DNS resolver management to fix excessive resolver object creation + when using multiple client sessions. The new ``_DNSResolverManager`` singleton ensures + only one ``DNSResolver`` object is created for default configurations, significantly + reducing resource usage and improving performance for applications using multiple + client sessions simultaneously -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10847`, :issue:`10923`, :issue:`10946`. + + + +- Upgraded to LLHTTP 9.3.0 -- by :user:`Dreamsorcerer`. + + + *Related issues and pull requests on GitHub:* + :issue:`10972`. + + + +- Optimized small HTTP requests/responses by coalescing headers and body into a single TCP packet -- by :user:`bdraco`. + + This change enhances network efficiency by reducing the number of packets sent for small HTTP payloads, improving latency and reducing overhead. Most importantly, this fixes compatibility with memory-constrained IoT devices that can only perform a single read operation and expect HTTP requests in one packet. The optimization uses zero-copy ``writelines`` when coalescing data and works with both regular and chunked transfer encoding. + + When ``aiohttp`` uses client middleware to communicate with an ``aiohttp`` server, connection reuse is more likely to occur since complete responses arrive in a single packet for small payloads. + + This aligns ``aiohttp`` with other popular HTTP clients that already coalesce small requests. + + + *Related issues and pull requests on GitHub:* + :issue:`10991`. + + + + +Improved documentation +---------------------- + +- Improved documentation for middleware by adding warnings and examples about + request body stream consumption. The documentation now clearly explains that + request body streams can only be read once and provides best practices for + sharing parsed request data between middleware and handlers -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`2914`. + + + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Removed non SPDX-license description from ``setup.cfg`` -- by :user:`devanshu-ziphq`. + + + *Related issues and pull requests on GitHub:* + :issue:`10662`. + + + +- Added support for building against system ``llhttp`` library -- by :user:`mgorny`. + + This change adds support for :envvar:`AIOHTTP_USE_SYSTEM_DEPS` environment variable that + can be used to build aiohttp against the system install of the ``llhttp`` library rather + than the vendored one. + + + *Related issues and pull requests on GitHub:* + :issue:`10759`. + + + +- ``aiodns`` is now installed on Windows with speedups extra -- by :user:`bdraco`. + + As of ``aiodns`` 3.3.0, ``SelectorEventLoop`` is no longer required when using ``pycares`` 4.7.0 or later. + + + *Related issues and pull requests on GitHub:* + :issue:`10823`. + + + +- Fixed compatibility issue with Cython 3.1.1 -- by :user:`bdraco` + + + *Related issues and pull requests on GitHub:* + :issue:`10877`. + + + + +Contributor-facing changes +-------------------------- + +- Sped up tests by disabling ``blockbuster`` fixture for ``test_static_file_huge`` and ``test_static_file_huge_cancel`` tests -- by :user:`dikos1337`. + + + *Related issues and pull requests on GitHub:* + :issue:`9705`, :issue:`10761`. + + + +- Updated tests to avoid using deprecated :py:mod:`asyncio` policy APIs and + make it compatible with Python 3.14. + + + *Related issues and pull requests on GitHub:* + :issue:`10851`. + + + +- Added Winloop to test suite to support in the future -- by :user:`Vizonex`. + + + *Related issues and pull requests on GitHub:* + :issue:`10922`. + + + + +Miscellaneous internal changes +------------------------------ + +- Added support for the ``partitioned`` attribute in the ``set_cookie`` method. + + + *Related issues and pull requests on GitHub:* + :issue:`9870`. + + + +- Setting :attr:`aiohttp.web.StreamResponse.last_modified` to an unsupported type will now raise :exc:`TypeError` instead of silently failing -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10146`. + + + + +---- + + +3.12.0rc0 (2025-05-23) +====================== + +Bug fixes +--------- + +- Fixed :py:attr:`~aiohttp.web.WebSocketResponse.prepared` property to correctly reflect the prepared state, especially during timeout scenarios -- by :user:`bdraco` + + + *Related issues and pull requests on GitHub:* + :issue:`6009`, :issue:`10988`. + + + +- Response is now always True, instead of using MutableMapping behaviour (False when map is empty) + + + *Related issues and pull requests on GitHub:* + :issue:`10119`. + + + +- Fixed connection reuse for file-like data payloads by ensuring buffer + truncation respects content-length boundaries and preventing premature + connection closure race -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10325`, :issue:`10915`, :issue:`10941`, :issue:`10943`. + + + +- Fixed pytest plugin to not use deprecated :py:mod:`asyncio` policy APIs. + + + *Related issues and pull requests on GitHub:* + :issue:`10851`. + + + +- Fixed :py:class:`~aiohttp.resolver.AsyncResolver` not using the ``loop`` argument in versions 3.x where it should still be supported -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10951`. + + + + +Features +-------- + +- Added a comprehensive HTTP Digest Authentication client middleware (DigestAuthMiddleware) + that implements RFC 7616. The middleware supports all standard hash algorithms + (MD5, SHA, SHA-256, SHA-512) with session variants, handles both 'auth' and + 'auth-int' quality of protection options, and automatically manages the + authentication flow by intercepting 401 responses and retrying with proper + credentials -- by :user:`feus4177`, :user:`TimMenninger`, and :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`2213`, :issue:`10725`. + + + +- Added client middleware support -- by :user:`bdraco` and :user:`Dreamsorcerer`. + + This change allows users to add middleware to the client session and requests, enabling features like + authentication, logging, and request/response modification without modifying the core + request logic. Additionally, the ``session`` attribute was added to ``ClientRequest``, + allowing middleware to access the session for making additional requests. + + + *Related issues and pull requests on GitHub:* + :issue:`9732`, :issue:`10902`, :issue:`10945`, :issue:`10952`, :issue:`10959`, :issue:`10968`. + + + +- Allow user setting zlib compression backend -- by :user:`TimMenninger` + + This change allows the user to call :func:`aiohttp.set_zlib_backend()` with the + zlib compression module of their choice. Default behavior continues to use + the builtin ``zlib`` library. + + + *Related issues and pull requests on GitHub:* + :issue:`9798`. + + + +- Added support for overriding the base URL with an absolute one in client sessions + -- by :user:`vivodi`. + + + *Related issues and pull requests on GitHub:* + :issue:`10074`. + + + +- Added ``host`` parameter to ``aiohttp_server`` fixture -- by :user:`christianwbrock`. + + + *Related issues and pull requests on GitHub:* + :issue:`10120`. + + + +- Detect blocking calls in coroutines using BlockBuster -- by :user:`cbornet`. + + + *Related issues and pull requests on GitHub:* + :issue:`10433`. + + + +- Added ``socket_factory`` to :py:class:`aiohttp.TCPConnector` to allow specifying custom socket options + -- by :user:`TimMenninger`. + + + *Related issues and pull requests on GitHub:* + :issue:`10474`, :issue:`10520`, :issue:`10961`, :issue:`10962`. + + + +- Started building armv7l manylinux wheels -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10797`. + + + +- Implemented shared DNS resolver management to fix excessive resolver object creation + when using multiple client sessions. The new ``_DNSResolverManager`` singleton ensures + only one ``DNSResolver`` object is created for default configurations, significantly + reducing resource usage and improving performance for applications using multiple + client sessions simultaneously -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10847`, :issue:`10923`, :issue:`10946`. + + + +- Upgraded to LLHTTP 9.3.0 -- by :user:`Dreamsorcerer`. + + + *Related issues and pull requests on GitHub:* + :issue:`10972`. + + + + +Improved documentation +---------------------- + +- Improved documentation for middleware by adding warnings and examples about + request body stream consumption. The documentation now clearly explains that + request body streams can only be read once and provides best practices for + sharing parsed request data between middleware and handlers -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`2914`. + + + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Removed non SPDX-license description from ``setup.cfg`` -- by :user:`devanshu-ziphq`. + + + *Related issues and pull requests on GitHub:* + :issue:`10662`. + + + +- Added support for building against system ``llhttp`` library -- by :user:`mgorny`. + + This change adds support for :envvar:`AIOHTTP_USE_SYSTEM_DEPS` environment variable that + can be used to build aiohttp against the system install of the ``llhttp`` library rather + than the vendored one. + + + *Related issues and pull requests on GitHub:* + :issue:`10759`. + + + +- ``aiodns`` is now installed on Windows with speedups extra -- by :user:`bdraco`. + + As of ``aiodns`` 3.3.0, ``SelectorEventLoop`` is no longer required when using ``pycares`` 4.7.0 or later. + + + *Related issues and pull requests on GitHub:* + :issue:`10823`. + + + +- Fixed compatibility issue with Cython 3.1.1 -- by :user:`bdraco` + + + *Related issues and pull requests on GitHub:* + :issue:`10877`. + + + + +Contributor-facing changes +-------------------------- + +- Sped up tests by disabling ``blockbuster`` fixture for ``test_static_file_huge`` and ``test_static_file_huge_cancel`` tests -- by :user:`dikos1337`. + + + *Related issues and pull requests on GitHub:* + :issue:`9705`, :issue:`10761`. + + + +- Updated tests to avoid using deprecated :py:mod:`asyncio` policy APIs and + make it compatible with Python 3.14. + + + *Related issues and pull requests on GitHub:* + :issue:`10851`. + + + +- Added Winloop to test suite to support in the future -- by :user:`Vizonex`. + + + *Related issues and pull requests on GitHub:* + :issue:`10922`. + + + + +Miscellaneous internal changes +------------------------------ + +- Added support for the ``partitioned`` attribute in the ``set_cookie`` method. + + + *Related issues and pull requests on GitHub:* + :issue:`9870`. + + + +- Setting :attr:`aiohttp.web.StreamResponse.last_modified` to an unsupported type will now raise :exc:`TypeError` instead of silently failing -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10146`. + + + + +---- + + +3.12.0b3 (2025-05-22) +===================== + +Bug fixes +--------- + +- Response is now always True, instead of using MutableMapping behaviour (False when map is empty) + + + *Related issues and pull requests on GitHub:* + :issue:`10119`. + + + +- Fixed connection reuse for file-like data payloads by ensuring buffer + truncation respects content-length boundaries and preventing premature + connection closure race -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10325`, :issue:`10915`, :issue:`10941`, :issue:`10943`. + + + +- Fixed pytest plugin to not use deprecated :py:mod:`asyncio` policy APIs. + + + *Related issues and pull requests on GitHub:* + :issue:`10851`. + + + +- Fixed :py:class:`~aiohttp.resolver.AsyncResolver` not using the ``loop`` argument in versions 3.x where it should still be supported -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10951`. + + + + +Features +-------- + +- Added a comprehensive HTTP Digest Authentication client middleware (DigestAuthMiddleware) + that implements RFC 7616. The middleware supports all standard hash algorithms + (MD5, SHA, SHA-256, SHA-512) with session variants, handles both 'auth' and + 'auth-int' quality of protection options, and automatically manages the + authentication flow by intercepting 401 responses and retrying with proper + credentials -- by :user:`feus4177`, :user:`TimMenninger`, and :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`2213`, :issue:`10725`. + + + +- Added client middleware support -- by :user:`bdraco` and :user:`Dreamsorcerer`. + + This change allows users to add middleware to the client session and requests, enabling features like + authentication, logging, and request/response modification without modifying the core + request logic. Additionally, the ``session`` attribute was added to ``ClientRequest``, + allowing middleware to access the session for making additional requests. + + + *Related issues and pull requests on GitHub:* + :issue:`9732`, :issue:`10902`, :issue:`10952`. + + + +- Allow user setting zlib compression backend -- by :user:`TimMenninger` + + This change allows the user to call :func:`aiohttp.set_zlib_backend()` with the + zlib compression module of their choice. Default behavior continues to use + the builtin ``zlib`` library. + + + *Related issues and pull requests on GitHub:* + :issue:`9798`. + + + +- Added support for overriding the base URL with an absolute one in client sessions + -- by :user:`vivodi`. + + + *Related issues and pull requests on GitHub:* + :issue:`10074`. + + + +- Added ``host`` parameter to ``aiohttp_server`` fixture -- by :user:`christianwbrock`. + + + *Related issues and pull requests on GitHub:* + :issue:`10120`. + + + +- Detect blocking calls in coroutines using BlockBuster -- by :user:`cbornet`. + + + *Related issues and pull requests on GitHub:* + :issue:`10433`. + + + +- Added ``socket_factory`` to :py:class:`aiohttp.TCPConnector` to allow specifying custom socket options + -- by :user:`TimMenninger`. + + + *Related issues and pull requests on GitHub:* + :issue:`10474`, :issue:`10520`. + + + +- Started building armv7l manylinux wheels -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10797`. + + + +- Implemented shared DNS resolver management to fix excessive resolver object creation + when using multiple client sessions. The new ``_DNSResolverManager`` singleton ensures + only one ``DNSResolver`` object is created for default configurations, significantly + reducing resource usage and improving performance for applications using multiple + client sessions simultaneously -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10847`, :issue:`10923`, :issue:`10946`. + + + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Removed non SPDX-license description from ``setup.cfg`` -- by :user:`devanshu-ziphq`. + + + *Related issues and pull requests on GitHub:* + :issue:`10662`. + + + +- Added support for building against system ``llhttp`` library -- by :user:`mgorny`. + + This change adds support for :envvar:`AIOHTTP_USE_SYSTEM_DEPS` environment variable that + can be used to build aiohttp against the system install of the ``llhttp`` library rather + than the vendored one. + + + *Related issues and pull requests on GitHub:* + :issue:`10759`. + + + +- ``aiodns`` is now installed on Windows with speedups extra -- by :user:`bdraco`. + + As of ``aiodns`` 3.3.0, ``SelectorEventLoop`` is no longer required when using ``pycares`` 4.7.0 or later. + + + *Related issues and pull requests on GitHub:* + :issue:`10823`. + + + +- Fixed compatibility issue with Cython 3.1.1 -- by :user:`bdraco` + + + *Related issues and pull requests on GitHub:* + :issue:`10877`. + + + + +Contributor-facing changes +-------------------------- + +- Sped up tests by disabling ``blockbuster`` fixture for ``test_static_file_huge`` and ``test_static_file_huge_cancel`` tests -- by :user:`dikos1337`. + + + *Related issues and pull requests on GitHub:* + :issue:`9705`, :issue:`10761`. + + + +- Updated tests to avoid using deprecated :py:mod:`asyncio` policy APIs and + make it compatible with Python 3.14. + + + *Related issues and pull requests on GitHub:* + :issue:`10851`. + + + +- Added Winloop to test suite to support in the future -- by :user:`Vizonex`. + + + *Related issues and pull requests on GitHub:* + :issue:`10922`. + + + + +Miscellaneous internal changes +------------------------------ + +- Added support for the ``partitioned`` attribute in the ``set_cookie`` method. + + + *Related issues and pull requests on GitHub:* + :issue:`9870`. + + + +- Setting :attr:`aiohttp.web.StreamResponse.last_modified` to an unsupported type will now raise :exc:`TypeError` instead of silently failing -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10146`. + + + + +---- + + +3.12.0b2 (2025-05-22) +===================== + +Bug fixes +--------- + +- Response is now always True, instead of using MutableMapping behaviour (False when map is empty) + + + *Related issues and pull requests on GitHub:* + :issue:`10119`. + + + +- Fixed connection reuse for file-like data payloads by ensuring buffer + truncation respects content-length boundaries and preventing premature + connection closure race -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10325`, :issue:`10915`, :issue:`10941`, :issue:`10943`. + + + +- Fixed pytest plugin to not use deprecated :py:mod:`asyncio` policy APIs. + + + *Related issues and pull requests on GitHub:* + :issue:`10851`. + + + + +Features +-------- + +- Added a comprehensive HTTP Digest Authentication client middleware (DigestAuthMiddleware) + that implements RFC 7616. The middleware supports all standard hash algorithms + (MD5, SHA, SHA-256, SHA-512) with session variants, handles both 'auth' and + 'auth-int' quality of protection options, and automatically manages the + authentication flow by intercepting 401 responses and retrying with proper + credentials -- by :user:`feus4177`, :user:`TimMenninger`, and :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`2213`, :issue:`10725`. + + + +- Added client middleware support -- by :user:`bdraco` and :user:`Dreamsorcerer`. + + This change allows users to add middleware to the client session and requests, enabling features like + authentication, logging, and request/response modification without modifying the core + request logic. Additionally, the ``session`` attribute was added to ``ClientRequest``, + allowing middleware to access the session for making additional requests. + + + *Related issues and pull requests on GitHub:* + :issue:`9732`, :issue:`10902`. + + + +- Allow user setting zlib compression backend -- by :user:`TimMenninger` + + This change allows the user to call :func:`aiohttp.set_zlib_backend()` with the + zlib compression module of their choice. Default behavior continues to use + the builtin ``zlib`` library. + + + *Related issues and pull requests on GitHub:* + :issue:`9798`. + + + +- Added support for overriding the base URL with an absolute one in client sessions + -- by :user:`vivodi`. + + + *Related issues and pull requests on GitHub:* + :issue:`10074`. + + + +- Added ``host`` parameter to ``aiohttp_server`` fixture -- by :user:`christianwbrock`. + + + *Related issues and pull requests on GitHub:* + :issue:`10120`. + + + +- Detect blocking calls in coroutines using BlockBuster -- by :user:`cbornet`. + + + *Related issues and pull requests on GitHub:* + :issue:`10433`. + + + +- Added ``socket_factory`` to :py:class:`aiohttp.TCPConnector` to allow specifying custom socket options + -- by :user:`TimMenninger`. + + + *Related issues and pull requests on GitHub:* + :issue:`10474`, :issue:`10520`. + + + +- Started building armv7l manylinux wheels -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10797`. + + + +- Implemented shared DNS resolver management to fix excessive resolver object creation + when using multiple client sessions. The new ``_DNSResolverManager`` singleton ensures + only one ``DNSResolver`` object is created for default configurations, significantly + reducing resource usage and improving performance for applications using multiple + client sessions simultaneously -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10847`, :issue:`10923`, :issue:`10946`. + + + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Removed non SPDX-license description from ``setup.cfg`` -- by :user:`devanshu-ziphq`. + + + *Related issues and pull requests on GitHub:* + :issue:`10662`. + + + +- Added support for building against system ``llhttp`` library -- by :user:`mgorny`. + + This change adds support for :envvar:`AIOHTTP_USE_SYSTEM_DEPS` environment variable that + can be used to build aiohttp against the system install of the ``llhttp`` library rather + than the vendored one. + + + *Related issues and pull requests on GitHub:* + :issue:`10759`. + + + +- ``aiodns`` is now installed on Windows with speedups extra -- by :user:`bdraco`. + + As of ``aiodns`` 3.3.0, ``SelectorEventLoop`` is no longer required when using ``pycares`` 4.7.0 or later. + + + *Related issues and pull requests on GitHub:* + :issue:`10823`. + + + +- Fixed compatibility issue with Cython 3.1.1 -- by :user:`bdraco` + + + *Related issues and pull requests on GitHub:* + :issue:`10877`. + + + + +Contributor-facing changes +-------------------------- + +- Sped up tests by disabling ``blockbuster`` fixture for ``test_static_file_huge`` and ``test_static_file_huge_cancel`` tests -- by :user:`dikos1337`. + + + *Related issues and pull requests on GitHub:* + :issue:`9705`, :issue:`10761`. + + + +- Updated tests to avoid using deprecated :py:mod:`asyncio` policy APIs and + make it compatible with Python 3.14. + + + *Related issues and pull requests on GitHub:* + :issue:`10851`. + + + +- Added Winloop to test suite to support in the future -- by :user:`Vizonex`. + + + *Related issues and pull requests on GitHub:* + :issue:`10922`. + + + + +Miscellaneous internal changes +------------------------------ + +- Added support for the ``partitioned`` attribute in the ``set_cookie`` method. + + + *Related issues and pull requests on GitHub:* + :issue:`9870`. + + + +- Setting :attr:`aiohttp.web.StreamResponse.last_modified` to an unsupported type will now raise :exc:`TypeError` instead of silently failing -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10146`. + + + + +---- + + +3.12.0b1 (2025-05-22) +===================== + +Bug fixes +--------- + +- Response is now always True, instead of using MutableMapping behaviour (False when map is empty) + + + *Related issues and pull requests on GitHub:* + :issue:`10119`. + + + +- Fixed connection reuse for file-like data payloads by ensuring buffer + truncation respects content-length boundaries and preventing premature + connection closure race -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10325`, :issue:`10915`. + + + +- Fixed pytest plugin to not use deprecated :py:mod:`asyncio` policy APIs. + + + *Related issues and pull requests on GitHub:* + :issue:`10851`. + + + + +Features +-------- + +- Added a comprehensive HTTP Digest Authentication client middleware (DigestAuthMiddleware) + that implements RFC 7616. The middleware supports all standard hash algorithms + (MD5, SHA, SHA-256, SHA-512) with session variants, handles both 'auth' and + 'auth-int' quality of protection options, and automatically manages the + authentication flow by intercepting 401 responses and retrying with proper + credentials -- by :user:`feus4177`, :user:`TimMenninger`, and :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`2213`, :issue:`10725`. + + + +- Added client middleware support -- by :user:`bdraco` and :user:`Dreamsorcerer`. + + This change allows users to add middleware to the client session and requests, enabling features like + authentication, logging, and request/response modification without modifying the core + request logic. Additionally, the ``session`` attribute was added to ``ClientRequest``, + allowing middleware to access the session for making additional requests. + + + *Related issues and pull requests on GitHub:* + :issue:`9732`, :issue:`10902`. + + + +- Allow user setting zlib compression backend -- by :user:`TimMenninger` + + This change allows the user to call :func:`aiohttp.set_zlib_backend()` with the + zlib compression module of their choice. Default behavior continues to use + the builtin ``zlib`` library. + + + *Related issues and pull requests on GitHub:* + :issue:`9798`. + + + +- Added support for overriding the base URL with an absolute one in client sessions + -- by :user:`vivodi`. + + + *Related issues and pull requests on GitHub:* + :issue:`10074`. + + + +- Added ``host`` parameter to ``aiohttp_server`` fixture -- by :user:`christianwbrock`. + + + *Related issues and pull requests on GitHub:* + :issue:`10120`. + + + +- Detect blocking calls in coroutines using BlockBuster -- by :user:`cbornet`. + + + *Related issues and pull requests on GitHub:* + :issue:`10433`. + + + +- Added ``socket_factory`` to :py:class:`aiohttp.TCPConnector` to allow specifying custom socket options + -- by :user:`TimMenninger`. + + + *Related issues and pull requests on GitHub:* + :issue:`10474`, :issue:`10520`. + + + +- Started building armv7l manylinux wheels -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10797`. + + + +- Implemented shared DNS resolver management to fix excessive resolver object creation + when using multiple client sessions. The new ``_DNSResolverManager`` singleton ensures + only one ``DNSResolver`` object is created for default configurations, significantly + reducing resource usage and improving performance for applications using multiple + client sessions simultaneously -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10847`, :issue:`10923`. + + + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Removed non SPDX-license description from ``setup.cfg`` -- by :user:`devanshu-ziphq`. + + + *Related issues and pull requests on GitHub:* + :issue:`10662`. + + + +- Added support for building against system ``llhttp`` library -- by :user:`mgorny`. + + This change adds support for :envvar:`AIOHTTP_USE_SYSTEM_DEPS` environment variable that + can be used to build aiohttp against the system install of the ``llhttp`` library rather + than the vendored one. + + + *Related issues and pull requests on GitHub:* + :issue:`10759`. + + + +- ``aiodns`` is now installed on Windows with speedups extra -- by :user:`bdraco`. + + As of ``aiodns`` 3.3.0, ``SelectorEventLoop`` is no longer required when using ``pycares`` 4.7.0 or later. + + + *Related issues and pull requests on GitHub:* + :issue:`10823`. + + + +- Fixed compatibility issue with Cython 3.1.1 -- by :user:`bdraco` + + + *Related issues and pull requests on GitHub:* + :issue:`10877`. + + + + +Contributor-facing changes +-------------------------- + +- Sped up tests by disabling ``blockbuster`` fixture for ``test_static_file_huge`` and ``test_static_file_huge_cancel`` tests -- by :user:`dikos1337`. + + + *Related issues and pull requests on GitHub:* + :issue:`9705`, :issue:`10761`. + + + +- Updated tests to avoid using deprecated :py:mod:`asyncio` policy APIs and + make it compatible with Python 3.14. + + + *Related issues and pull requests on GitHub:* + :issue:`10851`. + + + +- Added Winloop to test suite to support in the future -- by :user:`Vizonex`. + + + *Related issues and pull requests on GitHub:* + :issue:`10922`. + + + + +Miscellaneous internal changes +------------------------------ + +- Added support for the ``partitioned`` attribute in the ``set_cookie`` method. + + + *Related issues and pull requests on GitHub:* + :issue:`9870`. + + + +- Setting :attr:`aiohttp.web.StreamResponse.last_modified` to an unsupported type will now raise :exc:`TypeError` instead of silently failing -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10146`. + + + + +---- + + +3.12.0b0 (2025-05-20) +===================== + +Bug fixes +--------- + +- Response is now always True, instead of using MutableMapping behaviour (False when map is empty) + + + *Related issues and pull requests on GitHub:* + :issue:`10119`. + + + +- Fixed pytest plugin to not use deprecated :py:mod:`asyncio` policy APIs. + + + *Related issues and pull requests on GitHub:* + :issue:`10851`. + + + + +Features +-------- + +- Added a comprehensive HTTP Digest Authentication client middleware (DigestAuthMiddleware) + that implements RFC 7616. The middleware supports all standard hash algorithms + (MD5, SHA, SHA-256, SHA-512) with session variants, handles both 'auth' and + 'auth-int' quality of protection options, and automatically manages the + authentication flow by intercepting 401 responses and retrying with proper + credentials -- by :user:`feus4177`, :user:`TimMenninger`, and :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`2213`, :issue:`10725`. + + + +- Added client middleware support -- by :user:`bdraco` and :user:`Dreamsorcerer`. + + This change allows users to add middleware to the client session and requests, enabling features like + authentication, logging, and request/response modification without modifying the core + request logic. Additionally, the ``session`` attribute was added to ``ClientRequest``, + allowing middleware to access the session for making additional requests. + + + *Related issues and pull requests on GitHub:* + :issue:`9732`, :issue:`10902`. + + + +- Allow user setting zlib compression backend -- by :user:`TimMenninger` + + This change allows the user to call :func:`aiohttp.set_zlib_backend()` with the + zlib compression module of their choice. Default behavior continues to use + the builtin ``zlib`` library. + + + *Related issues and pull requests on GitHub:* + :issue:`9798`. + + + +- Added support for overriding the base URL with an absolute one in client sessions + -- by :user:`vivodi`. + + + *Related issues and pull requests on GitHub:* + :issue:`10074`. + + + +- Added ``host`` parameter to ``aiohttp_server`` fixture -- by :user:`christianwbrock`. + + + *Related issues and pull requests on GitHub:* + :issue:`10120`. + + + +- Detect blocking calls in coroutines using BlockBuster -- by :user:`cbornet`. + + + *Related issues and pull requests on GitHub:* + :issue:`10433`. + + + +- Added ``socket_factory`` to :py:class:`aiohttp.TCPConnector` to allow specifying custom socket options + -- by :user:`TimMenninger`. + + + *Related issues and pull requests on GitHub:* + :issue:`10474`, :issue:`10520`. + + + +- Started building armv7l manylinux wheels -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10797`. + + + +- Implemented shared DNS resolver management to fix excessive resolver object creation + when using multiple client sessions. The new ``_DNSResolverManager`` singleton ensures + only one ``DNSResolver`` object is created for default configurations, significantly + reducing resource usage and improving performance for applications using multiple + client sessions simultaneously -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10847`. + + + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Removed non SPDX-license description from ``setup.cfg`` -- by :user:`devanshu-ziphq`. + + + *Related issues and pull requests on GitHub:* + :issue:`10662`. + + + +- ``aiodns`` is now installed on Windows with speedups extra -- by :user:`bdraco`. + + As of ``aiodns`` 3.3.0, ``SelectorEventLoop`` is no longer required when using ``pycares`` 4.7.0 or later. + + + *Related issues and pull requests on GitHub:* + :issue:`10823`. + + + +- Fixed compatibility issue with Cython 3.1.1 -- by :user:`bdraco` + + + *Related issues and pull requests on GitHub:* + :issue:`10877`. + + + + +Contributor-facing changes +-------------------------- + +- Sped up tests by disabling ``blockbuster`` fixture for ``test_static_file_huge`` and ``test_static_file_huge_cancel`` tests -- by :user:`dikos1337`. + + + *Related issues and pull requests on GitHub:* + :issue:`9705`, :issue:`10761`. + + + +- Updated tests to avoid using deprecated :py:mod:`asyncio` policy APIs and + make it compatible with Python 3.14. + + + *Related issues and pull requests on GitHub:* + :issue:`10851`. + + + + +Miscellaneous internal changes +------------------------------ + +- Added support for the ``partitioned`` attribute in the ``set_cookie`` method. + + + *Related issues and pull requests on GitHub:* + :issue:`9870`. + + + +- Setting :attr:`aiohttp.web.StreamResponse.last_modified` to an unsupported type will now raise :exc:`TypeError` instead of silently failing -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`10146`. + + + + +---- + + 3.11.18 (2025-04-20) ==================== diff --git a/CHANGES/10074.feature.rst b/CHANGES/10074.feature.rst deleted file mode 100644 index d956c38af57..00000000000 --- a/CHANGES/10074.feature.rst +++ /dev/null @@ -1,2 +0,0 @@ -Added support for overriding the base URL with an absolute one in client sessions --- by :user:`vivodi`. diff --git a/CHANGES/10119.bugfix.rst b/CHANGES/10119.bugfix.rst deleted file mode 100644 index 86d2511f5b5..00000000000 --- a/CHANGES/10119.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Response is now always True, instead of using MutableMapping behaviour (False when map is empty) diff --git a/CHANGES/10120.feature.rst b/CHANGES/10120.feature.rst deleted file mode 100644 index 98cee5650d6..00000000000 --- a/CHANGES/10120.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Added ``host`` parameter to ``aiohttp_server`` fixture -- by :user:`christianwbrock`. diff --git a/CHANGES/10146.misc.rst b/CHANGES/10146.misc.rst deleted file mode 100644 index bee4ef68fb3..00000000000 --- a/CHANGES/10146.misc.rst +++ /dev/null @@ -1 +0,0 @@ -Setting :attr:`aiohttp.web.StreamResponse.last_modified` to an unsupported type will now raise :exc:`TypeError` instead of silently failing -- by :user:`bdraco`. diff --git a/CHANGES/10325.bugfix.rst b/CHANGES/10325.bugfix.rst deleted file mode 120000 index aa085cc590d..00000000000 --- a/CHANGES/10325.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -10915.bugfix.rst \ No newline at end of file diff --git a/CHANGES/10433.feature.rst b/CHANGES/10433.feature.rst deleted file mode 100644 index 11a29d6e368..00000000000 --- a/CHANGES/10433.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Detect blocking calls in coroutines using BlockBuster -- by :user:`cbornet`. diff --git a/CHANGES/10474.feature.rst b/CHANGES/10474.feature.rst deleted file mode 120000 index 7c4f9a7b83b..00000000000 --- a/CHANGES/10474.feature.rst +++ /dev/null @@ -1 +0,0 @@ -10520.feature.rst \ No newline at end of file diff --git a/CHANGES/10520.feature.rst b/CHANGES/10520.feature.rst deleted file mode 100644 index 3d2877b5c09..00000000000 --- a/CHANGES/10520.feature.rst +++ /dev/null @@ -1,2 +0,0 @@ -Added ``socket_factory`` to :py:class:`aiohttp.TCPConnector` to allow specifying custom socket options --- by :user:`TimMenninger`. diff --git a/CHANGES/10662.packaging.rst b/CHANGES/10662.packaging.rst deleted file mode 100644 index 2ed3a69cb56..00000000000 --- a/CHANGES/10662.packaging.rst +++ /dev/null @@ -1 +0,0 @@ -Removed non SPDX-license description from ``setup.cfg`` -- by :user:`devanshu-ziphq`. diff --git a/CHANGES/10725.feature.rst b/CHANGES/10725.feature.rst deleted file mode 100644 index 2cb096a58e7..00000000000 --- a/CHANGES/10725.feature.rst +++ /dev/null @@ -1,6 +0,0 @@ -Added a comprehensive HTTP Digest Authentication client middleware (DigestAuthMiddleware) -that implements RFC 7616. The middleware supports all standard hash algorithms -(MD5, SHA, SHA-256, SHA-512) with session variants, handles both 'auth' and -'auth-int' quality of protection options, and automatically manages the -authentication flow by intercepting 401 responses and retrying with proper -credentials -- by :user:`feus4177`, :user:`TimMenninger`, and :user:`bdraco`. diff --git a/CHANGES/10759.packaging.rst b/CHANGES/10759.packaging.rst deleted file mode 100644 index 6f41e873229..00000000000 --- a/CHANGES/10759.packaging.rst +++ /dev/null @@ -1,5 +0,0 @@ -Added support for building against system ``llhttp`` library -- by :user:`mgorny`. - -This change adds support for :envvar:`AIOHTTP_USE_SYSTEM_DEPS` environment variable that -can be used to build aiohttp against the system install of the ``llhttp`` library rather -than the vendored one. diff --git a/CHANGES/10761.contrib.rst b/CHANGES/10761.contrib.rst deleted file mode 120000 index 3d35184e09d..00000000000 --- a/CHANGES/10761.contrib.rst +++ /dev/null @@ -1 +0,0 @@ -9705.contrib.rst \ No newline at end of file diff --git a/CHANGES/10797.feature.rst b/CHANGES/10797.feature.rst deleted file mode 100644 index fc68d09f34e..00000000000 --- a/CHANGES/10797.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Started building armv7l manylinux wheels -- by :user:`bdraco`. diff --git a/CHANGES/10823.packaging.rst b/CHANGES/10823.packaging.rst deleted file mode 100644 index c65f8bea795..00000000000 --- a/CHANGES/10823.packaging.rst +++ /dev/null @@ -1,3 +0,0 @@ -``aiodns`` is now installed on Windows with speedups extra -- by :user:`bdraco`. - -As of ``aiodns`` 3.3.0, ``SelectorEventLoop`` is no longer required when using ``pycares`` 4.7.0 or later. diff --git a/CHANGES/10847.feature.rst b/CHANGES/10847.feature.rst deleted file mode 100644 index bfa7f6d498a..00000000000 --- a/CHANGES/10847.feature.rst +++ /dev/null @@ -1,5 +0,0 @@ -Implemented shared DNS resolver management to fix excessive resolver object creation -when using multiple client sessions. The new ``_DNSResolverManager`` singleton ensures -only one ``DNSResolver`` object is created for default configurations, significantly -reducing resource usage and improving performance for applications using multiple -client sessions simultaneously -- by :user:`bdraco`. diff --git a/CHANGES/10851.bugfix.rst b/CHANGES/10851.bugfix.rst deleted file mode 100644 index 9c47cc95905..00000000000 --- a/CHANGES/10851.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed pytest plugin to not use deprecated :py:mod:`asyncio` policy APIs. diff --git a/CHANGES/10851.contrib.rst b/CHANGES/10851.contrib.rst deleted file mode 100644 index 623f96bc227..00000000000 --- a/CHANGES/10851.contrib.rst +++ /dev/null @@ -1,2 +0,0 @@ -Updated tests to avoid using deprecated :py:mod:`asyncio` policy APIs and -make it compatible with Python 3.14. diff --git a/CHANGES/10902.feature.rst b/CHANGES/10902.feature.rst deleted file mode 120000 index b565aa68ee0..00000000000 --- a/CHANGES/10902.feature.rst +++ /dev/null @@ -1 +0,0 @@ -9732.feature.rst \ No newline at end of file diff --git a/CHANGES/10915.bugfix.rst b/CHANGES/10915.bugfix.rst deleted file mode 100644 index f564603306b..00000000000 --- a/CHANGES/10915.bugfix.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fixed connection reuse for file-like data payloads by ensuring buffer -truncation respects content-length boundaries and preventing premature -connection closure race -- by :user:`bdraco`. diff --git a/CHANGES/10922.contrib.rst b/CHANGES/10922.contrib.rst deleted file mode 100644 index e5e1cfd8af6..00000000000 --- a/CHANGES/10922.contrib.rst +++ /dev/null @@ -1 +0,0 @@ -Added Winloop to test suite to support in the future -- by :user:`Vizonex`. diff --git a/CHANGES/10923.feature.rst b/CHANGES/10923.feature.rst deleted file mode 120000 index 879a4227358..00000000000 --- a/CHANGES/10923.feature.rst +++ /dev/null @@ -1 +0,0 @@ -10847.feature.rst \ No newline at end of file diff --git a/CHANGES/10941.bugfix.rst b/CHANGES/10941.bugfix.rst deleted file mode 120000 index aa085cc590d..00000000000 --- a/CHANGES/10941.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -10915.bugfix.rst \ No newline at end of file diff --git a/CHANGES/10943.bugfix.rst b/CHANGES/10943.bugfix.rst deleted file mode 120000 index aa085cc590d..00000000000 --- a/CHANGES/10943.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -10915.bugfix.rst \ No newline at end of file diff --git a/CHANGES/10945.feature.rst b/CHANGES/10945.feature.rst deleted file mode 120000 index b565aa68ee0..00000000000 --- a/CHANGES/10945.feature.rst +++ /dev/null @@ -1 +0,0 @@ -9732.feature.rst \ No newline at end of file diff --git a/CHANGES/10946.feature.rst b/CHANGES/10946.feature.rst deleted file mode 120000 index 879a4227358..00000000000 --- a/CHANGES/10946.feature.rst +++ /dev/null @@ -1 +0,0 @@ -10847.feature.rst \ No newline at end of file diff --git a/CHANGES/10952.feature.rst b/CHANGES/10952.feature.rst deleted file mode 120000 index b565aa68ee0..00000000000 --- a/CHANGES/10952.feature.rst +++ /dev/null @@ -1 +0,0 @@ -9732.feature.rst \ No newline at end of file diff --git a/CHANGES/10959.feature.rst b/CHANGES/10959.feature.rst deleted file mode 120000 index b565aa68ee0..00000000000 --- a/CHANGES/10959.feature.rst +++ /dev/null @@ -1 +0,0 @@ -9732.feature.rst \ No newline at end of file diff --git a/CHANGES/10961.feature.rst b/CHANGES/10961.feature.rst deleted file mode 120000 index 7c4f9a7b83b..00000000000 --- a/CHANGES/10961.feature.rst +++ /dev/null @@ -1 +0,0 @@ -10520.feature.rst \ No newline at end of file diff --git a/CHANGES/10962.feature.rst b/CHANGES/10962.feature.rst deleted file mode 120000 index 7c4f9a7b83b..00000000000 --- a/CHANGES/10962.feature.rst +++ /dev/null @@ -1 +0,0 @@ -10520.feature.rst \ No newline at end of file diff --git a/CHANGES/10968.bugfix.rst b/CHANGES/10968.bugfix.rst deleted file mode 100644 index 052a7d2b8f9..00000000000 --- a/CHANGES/10968.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed double compression issue when :py:meth:`~aiohttp.web.StreamResponse.enable_compression` is called on a response with pre-existing Content-Encoding header -- by :user:`bdraco`. diff --git a/CHANGES/10968.feature.rst b/CHANGES/10968.feature.rst deleted file mode 120000 index b565aa68ee0..00000000000 --- a/CHANGES/10968.feature.rst +++ /dev/null @@ -1 +0,0 @@ -9732.feature.rst \ No newline at end of file diff --git a/CHANGES/10972.feature.rst b/CHANGES/10972.feature.rst deleted file mode 100644 index 1d3779a3969..00000000000 --- a/CHANGES/10972.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Upgraded to LLHTTP 9.3.0 -- by :user:`Dreamsorcerer`. diff --git a/CHANGES/10988.bugfix.rst b/CHANGES/10988.bugfix.rst deleted file mode 120000 index 6e737bb336c..00000000000 --- a/CHANGES/10988.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -6009.bugfix.rst \ No newline at end of file diff --git a/CHANGES/10991.feature.rst b/CHANGES/10991.feature.rst deleted file mode 100644 index 687a1a752f6..00000000000 --- a/CHANGES/10991.feature.rst +++ /dev/null @@ -1,7 +0,0 @@ -Optimized small HTTP requests/responses by coalescing headers and body into a single TCP packet -- by :user:`bdraco`. - -This change enhances network efficiency by reducing the number of packets sent for small HTTP payloads, improving latency and reducing overhead. Most importantly, this fixes compatibility with memory-constrained IoT devices that can only perform a single read operation and expect HTTP requests in one packet. The optimization uses zero-copy ``writelines`` when coalescing data and works with both regular and chunked transfer encoding. - -When ``aiohttp`` uses client middleware to communicate with an ``aiohttp`` server, connection reuse is more likely to occur since complete responses arrive in a single packet for small payloads. - -This aligns ``aiohttp`` with other popular HTTP clients that already coalesce small requests. diff --git a/CHANGES/2213.feature.rst b/CHANGES/2213.feature.rst deleted file mode 120000 index d118975e478..00000000000 --- a/CHANGES/2213.feature.rst +++ /dev/null @@ -1 +0,0 @@ -10725.feature.rst \ No newline at end of file diff --git a/CHANGES/2914.doc.rst b/CHANGES/2914.doc.rst deleted file mode 100644 index 25592bf79bc..00000000000 --- a/CHANGES/2914.doc.rst +++ /dev/null @@ -1,4 +0,0 @@ -Improved documentation for middleware by adding warnings and examples about -request body stream consumption. The documentation now clearly explains that -request body streams can only be read once and provides best practices for -sharing parsed request data between middleware and handlers -- by :user:`bdraco`. diff --git a/CHANGES/6009.bugfix.rst b/CHANGES/6009.bugfix.rst deleted file mode 100644 index a530832c8a9..00000000000 --- a/CHANGES/6009.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed :py:attr:`~aiohttp.web.WebSocketResponse.prepared` property to correctly reflect the prepared state, especially during timeout scenarios -- by :user:`bdraco` diff --git a/CHANGES/9705.contrib.rst b/CHANGES/9705.contrib.rst deleted file mode 100644 index 5d23e964fa1..00000000000 --- a/CHANGES/9705.contrib.rst +++ /dev/null @@ -1 +0,0 @@ -Sped up tests by disabling ``blockbuster`` fixture for ``test_static_file_huge`` and ``test_static_file_huge_cancel`` tests -- by :user:`dikos1337`. diff --git a/CHANGES/9732.feature.rst b/CHANGES/9732.feature.rst deleted file mode 100644 index bf6dd8ebde3..00000000000 --- a/CHANGES/9732.feature.rst +++ /dev/null @@ -1,6 +0,0 @@ -Added client middleware support -- by :user:`bdraco` and :user:`Dreamsorcerer`. - -This change allows users to add middleware to the client session and requests, enabling features like -authentication, logging, and request/response modification without modifying the core -request logic. Additionally, the ``session`` attribute was added to ``ClientRequest``, -allowing middleware to access the session for making additional requests. diff --git a/CHANGES/9798.feature.rst b/CHANGES/9798.feature.rst deleted file mode 100644 index c1584b04491..00000000000 --- a/CHANGES/9798.feature.rst +++ /dev/null @@ -1,5 +0,0 @@ -Allow user setting zlib compression backend -- by :user:`TimMenninger` - -This change allows the user to call :func:`aiohttp.set_zlib_backend()` with the -zlib compression module of their choice. Default behavior continues to use -the builtin ``zlib`` library. diff --git a/CHANGES/9870.misc.rst b/CHANGES/9870.misc.rst deleted file mode 100644 index caa8f45e522..00000000000 --- a/CHANGES/9870.misc.rst +++ /dev/null @@ -1 +0,0 @@ -Added support for the ``partitioned`` attribute in the ``set_cookie`` method. diff --git a/docs/client_advanced.rst b/docs/client_advanced.rst index dcbd743d6fb..09ec0f1f356 100644 --- a/docs/client_advanced.rst +++ b/docs/client_advanced.rst @@ -131,29 +131,33 @@ Client Middleware ----------------- The client supports middleware to intercept requests and responses. This can be -useful for authentication, logging, request/response modification, and retries. +useful for authentication, logging, request/response modification, retries etc. -For practical examples and common middleware patterns, see the :ref:`aiohttp-client-middleware-cookbook`. +For more examples and common middleware patterns, see the :ref:`aiohttp-client-middleware-cookbook`. -Creating Middleware -^^^^^^^^^^^^^^^^^^^ +Creating a middleware +^^^^^^^^^^^^^^^^^^^^^ -To create a middleware, define an async function (or callable class) that accepts a request -and a handler function, and returns a response. Middleware must follow the -:type:`ClientMiddlewareType` signature (see :ref:`aiohttp-client-reference` for details). +To create a middleware, define an async function (or callable class) that accepts a request object +and a handler function, and returns a response. Middlewares must follow the +:type:`ClientMiddlewareType` signature:: -Using Middleware -^^^^^^^^^^^^^^^^ + async def auth_middleware(req: ClientRequest, handler: ClientHandlerType) -> ClientResponse: + req.headers["Authorization"] = get_auth_header() + return await handler(req) + +Using Middlewares +^^^^^^^^^^^^^^^^^ -You can apply middleware to a client session or to individual requests:: +You can apply middlewares to a client session or to individual requests:: # Apply to all requests in a session async with ClientSession(middlewares=(my_middleware,)) as session: - resp = await session.get('http://example.com') + resp = await session.get("http://example.com") # Apply to a specific request async with ClientSession() as session: - resp = await session.get('http://example.com', middlewares=(my_middleware,)) + resp = await session.get("http://example.com", middlewares=(my_middleware,)) Middleware Chaining ^^^^^^^^^^^^^^^^^^^ @@ -162,13 +166,14 @@ Multiple middlewares are applied in the order they are listed:: # Middlewares are applied in order: logging -> auth -> request async with ClientSession(middlewares=(logging_middleware, auth_middleware)) as session: - resp = await session.get('http://example.com') + async with session.get("http://example.com") as resp: + ... -A key aspect to understand about the flat middleware structure is that the execution flow follows this pattern: +A key aspect to understand about the middleware sequence is that the execution flow follows this pattern: 1. The first middleware in the list is called first and executes its code before calling the handler -2. The handler is the next middleware in the chain (or the actual request handler if there are no more middleware) -3. When the handler returns a response, execution continues in the first middleware after the handler call +2. The handler is the next middleware in the chain (or the request handler if there are no more middlewares) +3. When the handler returns a response, execution continues from the last middleware right after the handler call 4. This creates a nested "onion-like" pattern for execution For example, with ``middlewares=(middleware1, middleware2)``, the execution order would be: @@ -179,7 +184,12 @@ For example, with ``middlewares=(middleware1, middleware2)``, the execution orde 4. Exit ``middleware2`` (post-response code) 5. Exit ``middleware1`` (post-response code) -This flat structure means that middleware is applied on each retry attempt inside the client's retry loop, not just once before all retries. This allows middleware to modify requests freshly on each retry attempt. +This flat structure means that a middleware is applied on each retry attempt inside the client's retry loop, +not just once before all retries. This allows middleware to modify requests freshly on each retry attempt. + +For example, if we had a retry middleware and a logging middleware, and we want every retried request to be +logged separately, then we'd need to specify ``middlewares=(retry_mw, logging_mw)``. If we reversed the order +to ``middlewares=(logging_mw, retry_mw)``, then we'd only log once regardless of how many retries are done. .. note:: @@ -188,157 +198,6 @@ This flat structure means that middleware is applied on each retry attempt insid like adding static headers, you can often use request parameters (e.g., ``headers``) or session configuration instead. -Common Middleware Patterns -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. _client-middleware-retry: - -Authentication and Retry -"""""""""""""""""""""""" - -There are two recommended approaches for implementing retry logic: - -1. **For Loop Pattern (Simple Cases)** - - Use a bounded ``for`` loop when the number of retry attempts is known and fixed:: - - import hashlib - from aiohttp import ClientSession, ClientRequest, ClientResponse, ClientHandlerType - - async def auth_retry_middleware( - request: ClientRequest, - handler: ClientHandlerType - ) -> ClientResponse: - # Try up to 3 authentication methods - for attempt in range(3): - if attempt == 0: - # First attempt: use API key - request.headers["X-API-Key"] = "my-api-key" - elif attempt == 1: - # Second attempt: use Bearer token - request.headers["Authorization"] = "Bearer fallback-token" - else: - # Third attempt: use hash-based signature - secret_key = "my-secret-key" - url_path = str(request.url.path) - signature = hashlib.sha256(f"{url_path}{secret_key}".encode()).hexdigest() - request.headers["X-Signature"] = signature - - # Send the request - response = await handler(request) - - # If successful or not an auth error, return immediately - if response.status != 401: - return response - - # Return the last response if all retries are exhausted - return response - -2. **While Loop Pattern (Complex Cases)** - - For more complex scenarios, use a ``while`` loop with strict exit conditions:: - - import logging - - _LOGGER = logging.getLogger(__name__) - - class RetryMiddleware: - def __init__(self, max_retries: int = 3): - self.max_retries = max_retries - - async def __call__( - self, - request: ClientRequest, - handler: ClientHandlerType - ) -> ClientResponse: - retry_count = 0 - - # Always have clear exit conditions - while retry_count <= self.max_retries: - # Send the request - response = await handler(request) - - # Exit conditions - if 200 <= response.status < 400 or retry_count >= self.max_retries: - return response - - # Retry logic for different status codes - if response.status in (401, 429, 500, 502, 503, 504): - retry_count += 1 - _LOGGER.debug(f"Retrying request (attempt {retry_count}/{self.max_retries})") - continue - - # For any other status code, don't retry - return response - - # Safety return (should never reach here) - return response - -Request Modification -"""""""""""""""""""" - -Modify request properties based on request content:: - - async def content_type_middleware( - request: ClientRequest, - handler: ClientHandlerType - ) -> ClientResponse: - # Examine URL path to determine content-type - if request.url.path.endswith('.json'): - request.headers['Content-Type'] = 'application/json' - elif request.url.path.endswith('.xml'): - request.headers['Content-Type'] = 'application/xml' - - # Add custom headers based on HTTP method - if request.method == 'POST': - request.headers['X-Request-ID'] = f"post-{id(request)}" - - return await handler(request) - -Avoiding Infinite Recursion -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. warning:: - - Using the same session from within middleware can cause infinite recursion if - the middleware makes HTTP requests using the same session that has the middleware - applied. This is especially risky in token refresh middleware or retry logic. - - When implementing retry or refresh logic, always use bounded loops - (e.g., ``for _ in range(2):`` instead of ``while True:``) to prevent infinite recursion. - -To avoid recursion when making requests inside middleware, use one of these approaches: - -**Option 1:** Disable middleware for internal requests:: - - async def log_middleware( - request: ClientRequest, - handler: ClientHandlerType - ) -> ClientResponse: - async with request.session.post( - "https://logapi.example/log", - json={"url": str(request.url)}, - middlewares=() # This prevents infinite recursion - ) as resp: - pass - - return await handler(request) - -**Option 2:** Check request details to avoid recursive application:: - - async def log_middleware( - request: ClientRequest, - handler: ClientHandlerType - ) -> ClientResponse: - if request.url.host != "logapi.example": # Avoid infinite recursion - async with request.session.post( - "https://logapi.example/log", - json={"url": str(request.url)} - ) as resp: - pass - - return await handler(request) - Custom Cookies -------------- diff --git a/docs/client_middleware_cookbook.rst b/docs/client_middleware_cookbook.rst index 4b8d6ddd5f8..33994160fba 100644 --- a/docs/client_middleware_cookbook.rst +++ b/docs/client_middleware_cookbook.rst @@ -5,331 +5,102 @@ Client Middleware Cookbook ========================== -This cookbook provides practical examples of implementing client middleware for common use cases. +This cookbook provides examples of how client middlewares can be used for common use cases. -.. note:: +Simple Retry Middleware +----------------------- - All examples in this cookbook are also available as complete, runnable scripts in the - ``examples/`` directory of the aiohttp repository. Look for files named ``*_middleware.py``. +It's very easy to create middlewares that can retry a connection on a given condition: -.. _cookbook-basic-auth-middleware: +.. literalinclude:: code/client_middleware_cookbook.py + :pyobject: retry_middleware -Basic Authentication Middleware -------------------------------- +.. warning:: -Basic authentication is a simple authentication scheme built into the HTTP protocol. -Here's a middleware that automatically adds Basic Auth headers to all requests: + It is recommended to ensure loops are bounded (e.g. using a ``for`` loop) to avoid + creating an infinite loop. -.. code-block:: python +Logging to an external service +------------------------------ - import base64 - from aiohttp import ClientRequest, ClientResponse, ClientHandlerType, hdrs +If we needed to log our requests via an API call to an external server or similar, we could +create a simple middleware like this: - class BasicAuthMiddleware: - """Middleware that adds Basic Authentication to all requests.""" +.. literalinclude:: code/client_middleware_cookbook.py + :pyobject: api_logging_middleware - def __init__(self, username: str, password: str) -> None: - self.username = username - self.password = password - self._auth_header = self._encode_credentials() +.. warning:: - def _encode_credentials(self) -> str: - """Encode username and password to base64.""" - credentials = f"{self.username}:{self.password}" - encoded = base64.b64encode(credentials.encode()).decode() - return f"Basic {encoded}" + Using the same session from within a middleware can cause infinite recursion if + that request gets processed again by the middleware. - async def __call__( - self, - request: ClientRequest, - handler: ClientHandlerType - ) -> ClientResponse: - """Add Basic Auth header to the request.""" - # Only add auth if not already present - if hdrs.AUTHORIZATION not in request.headers: - request.headers[hdrs.AUTHORIZATION] = self._auth_header + To avoid such recursion a middleware should typically make requests with + ``middlewares=()`` or else contain some condition to stop the request triggering + the same logic when it is processed again by the middleware (e.g by whitelisting + the API domain of the request). - # Proceed with the request - return await handler(request) +Token Refresh Middleware +------------------------ -Usage example: +If you need to refresh access tokens to continue accessing an API, this is also a good +candidate for a middleware. For example, you could check for a 401 response, then +refresh the token and retry: -.. code-block:: python +.. literalinclude:: code/client_middleware_cookbook.py + :pyobject: TokenRefresh401Middleware - import aiohttp - import asyncio - import logging +If you have an expiry time for the token, you could refresh at the expiry time, to avoid the +failed request: - _LOGGER = logging.getLogger(__name__) +.. literalinclude:: code/client_middleware_cookbook.py + :pyobject: TokenRefreshExpiryMiddleware - async def main(): - # Create middleware instance - auth_middleware = BasicAuthMiddleware("user", "pass") +Or you could even refresh preemptively in a background task to avoid any API delays. This is probably more +efficient to implement without a middleware: - # Use middleware in session - async with aiohttp.ClientSession(middlewares=(auth_middleware,)) as session: - async with session.get("https://httpbin.org/basic-auth/user/pass") as resp: - _LOGGER.debug("Status: %s", resp.status) - data = await resp.json() - _LOGGER.debug("Response: %s", data) +.. literalinclude:: code/client_middleware_cookbook.py + :pyobject: token_refresh_preemptively_example - asyncio.run(main()) +Or combine the above approaches to create a more robust solution. -.. _cookbook-retry-middleware: +.. note:: -Simple Retry Middleware ------------------------ + These can also be adjusted to handle proxy auth by modifying + :attr:`ClientRequest.proxy_headers`. -A retry middleware that automatically retries failed requests with exponential backoff: - -.. code-block:: python - - import asyncio - import logging - from http import HTTPStatus - from typing import Union, Set - from aiohttp import ClientRequest, ClientResponse, ClientHandlerType - - _LOGGER = logging.getLogger(__name__) - - DEFAULT_RETRY_STATUSES = { - HTTPStatus.TOO_MANY_REQUESTS, - HTTPStatus.INTERNAL_SERVER_ERROR, - HTTPStatus.BAD_GATEWAY, - HTTPStatus.SERVICE_UNAVAILABLE, - HTTPStatus.GATEWAY_TIMEOUT - } - - class RetryMiddleware: - """Middleware that retries failed requests with exponential backoff.""" - - def __init__( - self, - max_retries: int = 3, - retry_statuses: Union[Set[int], None] = None, - initial_delay: float = 1.0, - backoff_factor: float = 2.0 - ) -> None: - self.max_retries = max_retries - self.retry_statuses = retry_statuses or DEFAULT_RETRY_STATUSES - self.initial_delay = initial_delay - self.backoff_factor = backoff_factor - - async def __call__( - self, - request: ClientRequest, - handler: ClientHandlerType - ) -> ClientResponse: - """Execute request with retry logic.""" - last_response = None - delay = self.initial_delay - - for attempt in range(self.max_retries + 1): - if attempt > 0: - _LOGGER.info( - "Retrying request to %s (attempt %s/%s)", - request.url, - attempt + 1, - self.max_retries + 1 - ) - - # Execute the request - response = await handler(request) - last_response = response - - # Check if we should retry - if response.status not in self.retry_statuses: - return response - - # Don't retry if we've exhausted attempts - if attempt >= self.max_retries: - _LOGGER.warning( - "Max retries (%s) exceeded for %s", - self.max_retries, - request.url - ) - return response - - # Wait before retrying - _LOGGER.debug("Waiting %ss before retry...", delay) - await asyncio.sleep(delay) - delay *= self.backoff_factor - - # Return the last response - return last_response - -Usage example: - -.. code-block:: python - - import aiohttp - import asyncio - import logging - from http import HTTPStatus - - _LOGGER = logging.getLogger(__name__) - - RETRY_STATUSES = { - HTTPStatus.TOO_MANY_REQUESTS, - HTTPStatus.INTERNAL_SERVER_ERROR, - HTTPStatus.BAD_GATEWAY, - HTTPStatus.SERVICE_UNAVAILABLE, - HTTPStatus.GATEWAY_TIMEOUT - } - - async def main(): - # Create retry middleware with custom settings - retry_middleware = RetryMiddleware( - max_retries=3, - retry_statuses=RETRY_STATUSES, - initial_delay=0.5, - backoff_factor=2.0 - ) - - async with aiohttp.ClientSession(middlewares=(retry_middleware,)) as session: - # This will automatically retry on server errors - async with session.get("https://httpbin.org/status/500") as resp: - _LOGGER.debug("Final status: %s", resp.status) - - asyncio.run(main()) - -.. _cookbook-combining-middleware: - -Combining Multiple Middleware ------------------------------ - -You can combine multiple middleware to create powerful request pipelines: - -.. code-block:: python - - import time - import logging - from aiohttp import ClientRequest, ClientResponse, ClientHandlerType - - _LOGGER = logging.getLogger(__name__) - - class LoggingMiddleware: - """Middleware that logs request timing and response status.""" - - async def __call__( - self, - request: ClientRequest, - handler: ClientHandlerType - ) -> ClientResponse: - start_time = time.monotonic() - - # Log request - _LOGGER.debug("[REQUEST] %s %s", request.method, request.url) - - # Execute request - response = await handler(request) +Server-side Request Forgery Protection +-------------------------------------- - # Log response - duration = time.monotonic() - start_time - _LOGGER.debug("[RESPONSE] %s in %.2fs", response.status, duration) +To provide protection against server-side request forgery, we could blacklist any internal +IPs or domains. We could create a middleware that rejects requests made to a blacklist: - return response +.. literalinclude:: code/client_middleware_cookbook.py + :pyobject: ssrf_middleware - # Combine multiple middleware - async def main(): - # Middleware are applied in order: logging -> auth -> retry -> request - logging_middleware = LoggingMiddleware() - auth_middleware = BasicAuthMiddleware("user", "pass") - retry_middleware = RetryMiddleware(max_retries=2) +.. warning:: - async with aiohttp.ClientSession( - middlewares=(logging_middleware, auth_middleware, retry_middleware) - ) as session: - async with session.get("https://httpbin.org/basic-auth/user/pass") as resp: - text = await resp.text() - _LOGGER.debug("Response text: %s", text) + The above example is simplified for demonstration purposes. A production-ready + implementation should also check IPv6 addresses (``::1``), private IP ranges, + link-local addresses, and other internal hostnames. Consider using a well-tested + library for SSRF protection in production environments. -.. _cookbook-token-refresh-middleware: +If you know that your services correctly reject requests with an incorrect `Host` header, then +that may provide sufficient protection. Otherwise, we still have a concern with an attacker's +own domain resolving to a blacklisted IP. To provide complete protection, we can also +create a custom resolver: -Token Refresh Middleware ------------------------- +.. literalinclude:: code/client_middleware_cookbook.py + :pyobject: SSRFConnector + +Using both of these together in a session should provide full SSRF protection. -A more advanced example showing JWT token refresh: - -.. code-block:: python - - import asyncio - import time - from http import HTTPStatus - from typing import Union - from aiohttp import ClientRequest, ClientResponse, ClientHandlerType, hdrs - - class TokenRefreshMiddleware: - """Middleware that handles JWT token refresh automatically.""" - - def __init__(self, token_endpoint: str, refresh_token: str) -> None: - self.token_endpoint = token_endpoint - self.refresh_token = refresh_token - self.access_token: Union[str, None] = None - self.token_expires_at: Union[float, None] = None - self._refresh_lock = asyncio.Lock() - - async def _refresh_access_token(self, session) -> str: - """Refresh the access token using the refresh token.""" - async with self._refresh_lock: - # Check if another coroutine already refreshed the token - if self.token_expires_at and time.time() < self.token_expires_at: - return self.access_token - - # Make refresh request without middleware to avoid recursion - async with session.post( - self.token_endpoint, - json={"refresh_token": self.refresh_token}, - middlewares=() # Disable middleware for this request - ) as resp: - resp.raise_for_status() - data = await resp.json() - - if "access_token" not in data: - raise ValueError("No access_token in refresh response") - - self.access_token = data["access_token"] - # Token expires in 1 hour for demo, refresh 5 min early - expires_in = data.get("expires_in", 3600) - self.token_expires_at = time.time() + expires_in - 300 - return self.access_token - - async def __call__( - self, - request: ClientRequest, - handler: ClientHandlerType - ) -> ClientResponse: - """Add auth token to request, refreshing if needed.""" - # Skip token for refresh endpoint - if str(request.url).endswith('/token/refresh'): - return await handler(request) - - # Refresh token if needed - if not self.access_token or ( - self.token_expires_at and time.time() >= self.token_expires_at - ): - await self._refresh_access_token(request.session) - - # Add token to request - request.headers[hdrs.AUTHORIZATION] = f"Bearer {self.access_token}" - - # Execute request - response = await handler(request) - - # If we get 401, try refreshing token once - if response.status == HTTPStatus.UNAUTHORIZED: - await self._refresh_access_token(request.session) - request.headers[hdrs.AUTHORIZATION] = f"Bearer {self.access_token}" - response = await handler(request) - - return response Best Practices -------------- 1. **Keep middleware focused**: Each middleware should have a single responsibility. -2. **Order matters**: Middleware execute in the order they're listed. Place logging first, +2. **Order matters**: Middlewares execute in the order they're listed. Place logging first, authentication before retry, etc. 3. **Avoid infinite recursion**: When making HTTP requests inside middleware, either: diff --git a/docs/client_reference.rst b/docs/client_reference.rst index 40b4f7bcbf9..faae389f95c 100644 --- a/docs/client_reference.rst +++ b/docs/client_reference.rst @@ -1848,6 +1848,133 @@ manually. :raise TypeError: if message is :const:`~aiohttp.WSMsgType.BINARY`. :raise ValueError: if message is not valid JSON. +ClientRequest +------------- + +.. class:: ClientRequest + + Represents an HTTP request to be sent by the client. + + This object encapsulates all the details of an HTTP request before it is sent. + It is primarily used within client middleware to inspect or modify requests. + + .. note:: + + You typically don't create ``ClientRequest`` instances directly. They are + created internally by :class:`ClientSession` methods and passed to middleware. + + For more information about using middleware, see :ref:`aiohttp-client-middleware`. + + .. attribute:: body + :type: Payload | FormData + + The request body payload. This can be: + + - A :class:`Payload` object for raw data (default is empty bytes ``b""``) + - A :class:`FormData` object for form submissions + + .. attribute:: chunked + :type: bool | None + + Whether to use chunked transfer encoding: + + - ``True``: Use chunked encoding + - ``False``: Don't use chunked encoding + - ``None``: Automatically determine based on body + + .. attribute:: compress + :type: str | None + + The compression encoding for the request body. Common values include + ``'gzip'`` and ``'deflate'``, but any string value is technically allowed. + ``None`` means no compression. + + .. attribute:: headers + :type: multidict.CIMultiDict + + The HTTP headers that will be sent with the request. This is a case-insensitive + multidict that can be modified by middleware. + + .. code-block:: python + + # Add or modify headers + request.headers['X-Custom-Header'] = 'value' + request.headers['User-Agent'] = 'MyApp/1.0' + + .. attribute:: is_ssl + :type: bool + + ``True`` if the request uses a secure scheme (e.g., HTTPS, WSS), ``False`` otherwise. + + .. attribute:: method + :type: str + + The HTTP method of the request (e.g., ``'GET'``, ``'POST'``, ``'PUT'``, etc.). + + .. attribute:: original_url + :type: yarl.URL + + The original URL passed to the request method, including any fragment. + This preserves the exact URL as provided by the user. + + .. attribute:: proxy + :type: yarl.URL | None + + The proxy URL if the request will be sent through a proxy, ``None`` otherwise. + + .. attribute:: proxy_headers + :type: multidict.CIMultiDict | None + + Headers to be sent to the proxy server (e.g., ``Proxy-Authorization``). + Only set when :attr:`proxy` is not ``None``. + + .. attribute:: response_class + :type: type[ClientResponse] + + The class to use for creating the response object. Defaults to + :class:`ClientResponse` but can be customized for special handling. + + .. attribute:: server_hostname + :type: str | None + + Override the hostname for SSL certificate verification. Useful when + connecting through proxies or to IP addresses. + + .. attribute:: session + :type: ClientSession + + The client session that created this request. Useful for accessing + session-level configuration or making additional requests within middleware. + + .. warning:: + Be careful when making requests with the same session inside middleware + to avoid infinite recursion. Use ``middlewares=()`` parameter when needed. + + .. attribute:: ssl + :type: ssl.SSLContext | bool | Fingerprint + + SSL validation configuration for this request: + + - ``True``: Use default SSL verification + - ``False``: Skip SSL verification + - :class:`ssl.SSLContext`: Custom SSL context + - :class:`Fingerprint`: Verify specific certificate fingerprint + + .. attribute:: url + :type: yarl.URL + + The target URL of the request with the fragment (``#...``) part stripped. + This is the actual URL that will be used for the connection. + + .. note:: + To access the original URL with fragment, use :attr:`original_url`. + + .. attribute:: version + :type: HttpVersion + + The HTTP version to use for the request (e.g., ``HttpVersion(1, 1)`` for HTTP/1.1). + + Utilities --------- diff --git a/docs/code/client_middleware_cookbook.py b/docs/code/client_middleware_cookbook.py new file mode 100644 index 00000000000..b013e76b206 --- /dev/null +++ b/docs/code/client_middleware_cookbook.py @@ -0,0 +1,143 @@ +"""This is a collection of semi-complete examples that get included into the cookbook page.""" + +import asyncio +import logging +import time +from collections.abc import AsyncIterator, Sequence +from contextlib import asynccontextmanager, suppress + +from aiohttp import ( + ClientError, + ClientHandlerType, + ClientRequest, + ClientResponse, + ClientSession, + TCPConnector, +) +from aiohttp.abc import ResolveResult +from aiohttp.tracing import Trace + + +class SSRFError(ClientError): + """A request was made to a blacklisted host.""" + + +async def retry_middleware( + req: ClientRequest, handler: ClientHandlerType +) -> ClientResponse: + for _ in range(3): # Try up to 3 times + resp = await handler(req) + if resp.ok: + return resp + return resp # type: ignore[possibly-undefined] + + +async def api_logging_middleware( + req: ClientRequest, handler: ClientHandlerType +) -> ClientResponse: + # We use middlewares=() to avoid infinite recursion. + async with req.session.post("/log", data=req.url.host, middlewares=()) as resp: + if not resp.ok: + logging.warning("Log endpoint failed") + + return await handler(req) + + +class TokenRefresh401Middleware: + def __init__(self, refresh_token: str, access_token: str): + self.access_token = access_token + self.refresh_token = refresh_token + self.lock = asyncio.Lock() + + async def __call__( + self, req: ClientRequest, handler: ClientHandlerType + ) -> ClientResponse: + for _ in range(2): # Retry at most one time + token = self.access_token + req.headers["Authorization"] = f"Bearer {token}" + resp = await handler(req) + if resp.status != 401: + return resp + async with self.lock: + if token != self.access_token: # Already refreshed + continue + url = "https://api.example/refresh" + async with req.session.post(url, data=self.refresh_token) as resp: + # Add error handling as needed + data = await resp.json() + self.access_token = data["access_token"] + return resp # type: ignore[possibly-undefined] + + +class TokenRefreshExpiryMiddleware: + def __init__(self, refresh_token: str): + self.access_token = "" + self.expires_at = 0 + self.refresh_token = refresh_token + self.lock = asyncio.Lock() + + async def __call__( + self, req: ClientRequest, handler: ClientHandlerType + ) -> ClientResponse: + if self.expires_at <= time.time(): + token = self.access_token + async with self.lock: + if token == self.access_token: # Still not refreshed + url = "https://api.example/refresh" + async with req.session.post(url, data=self.refresh_token) as resp: + # Add error handling as needed + data = await resp.json() + self.access_token = data["access_token"] + self.expires_at = data["expires_at"] + + req.headers["Authorization"] = f"Bearer {self.access_token}" + return await handler(req) + + +async def token_refresh_preemptively_example() -> None: + async def set_token(session: ClientSession, event: asyncio.Event) -> None: + while True: + async with session.post("/refresh") as resp: + token = await resp.json() + session.headers["Authorization"] = f"Bearer {token['auth']}" + event.set() + await asyncio.sleep(token["valid_duration"]) + + @asynccontextmanager + async def auto_refresh_client() -> AsyncIterator[ClientSession]: + async with ClientSession() as session: + ready = asyncio.Event() + t = asyncio.create_task(set_token(session, ready)) + await ready.wait() + yield session + t.cancel() + with suppress(asyncio.CancelledError): + await t + + async with auto_refresh_client() as sess: + ... + + +async def ssrf_middleware( + req: ClientRequest, handler: ClientHandlerType +) -> ClientResponse: + # WARNING: This is a simplified example for demonstration purposes only. + # A complete implementation should also check: + # - IPv6 loopback (::1) + # - Private IP ranges (10.x.x.x, 192.168.x.x, 172.16-31.x.x) + # - Link-local addresses (169.254.x.x, fe80::/10) + # - Other internal hostnames and aliases + if req.url.host in {"127.0.0.1", "localhost"}: + raise SSRFError(req.url.host) + return await handler(req) + + +class SSRFConnector(TCPConnector): + async def _resolve_host( + self, host: str, port: int, traces: Sequence[Trace] | None = None + ) -> list[ResolveResult]: + res = await super()._resolve_host(host, port, traces) + # WARNING: This is a simplified example - should also check ::1, private ranges, etc. + if any(r["host"] in {"127.0.0.1"} for r in res): + raise SSRFError() + return res diff --git a/docs/conf.py b/docs/conf.py index 0be0e21eaef..505359b917e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -396,8 +396,9 @@ ("py:class", "aiohttp.web.RequestHandler"), # undocumented ("py:class", "aiohttp.NamedPipeConnector"), # undocumented ("py:class", "aiohttp.protocol.HttpVersion"), # undocumented - ("py:class", "aiohttp.ClientRequest"), # undocumented + ("py:class", "HttpVersion"), # undocumented ("py:class", "aiohttp.payload.Payload"), # undocumented + ("py:class", "Payload"), # undocumented ("py:class", "aiohttp.resolver.AsyncResolver"), # undocumented ("py:class", "aiohttp.resolver.ThreadedResolver"), # undocumented ("py:func", "aiohttp.ws_connect"), # undocumented diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 68d4693bac0..ff8bfb8b508 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -146,6 +146,7 @@ HTTPException HttpProcessingError httpretty https +hostname impl incapsulates Indices diff --git a/setup.cfg b/setup.cfg index 30aa2e87838..c4ab069f396 100644 --- a/setup.cfg +++ b/setup.cfg @@ -98,6 +98,7 @@ max-line-length = 88 per-file-ignores = # I900: Shouldn't appear in requirements for examples. examples/*:I900 + docs/code/*:F841 # flake8-requirements known-modules = proxy.py:[proxy]