Features
-
Added :class:
aiohttp.UploadTrackerfor observing a client request's upload progress -- by :user:Dreamsorcerer.Related issues and pull requests on GitHub:
#13579. -
Switched
application/x-www-form-urlencodedparsing in
:meth:~aiohttp.web.BaseRequest.postto the faster :func:yarl.query_to_pairs
parser and added theclient_max_fieldsargument to
:class:~aiohttp.web.Application(default1000) to cap the number of form
fields accepted by :meth:~aiohttp.web.BaseRequest.post. Forms with more
than 1000 fields now receive a413response unless the cap is raised;
0disables it -- by :user:bdraco.Related issues and pull requests on GitHub:
#13738. -
Added constants to
aiohttp.hdrsfor widely used headers: those that
browsers send on every request (Sec-Fetch-*,Sec-CH-UA*,
Sec-GPC,Upgrade-Insecure-Requests,Priority), W3C trace context
(traceparent,tracestate,baggage), response security, reporting
and caching headers, the remaining RFC 9110 and RFC 9530 fields,
Content-IDand common de facto proxy and application headers, and
grouped the constants by where the header is defined. The C parser returns
these names as the shared :class:~multidict.istrconstants instead of new
:class:strobjects, which made parsing a typical browser request about
9% cheaper -- by :user:asvetlov.Related issues and pull requests on GitHub:
#13886.
Bug fixes
-
Remove overlapping slots in
RequestHandler,
fix broken slots inheritance in :py:class:~aiohttp.web.StreamResponse.Related issues and pull requests on GitHub:
#6547. -
Fixed a segmentation fault in the C HTTP parser on Python 3.12 and newer when payload decompression raised an error while pending decompressed data was being drained, as seen with
brotlicffi1.2 -- by :user:bdraco. -
Rejected control characters in the request target in the pure-Python HTTP parser,
matching the llhttp-backed parser, which already refuses them
-- by :user:arshsmith1.Related issues and pull requests on GitHub:
#13212. -
Stripped the trailing whitespace from header values in the C HTTP parser,
so that it matches the pure-Python parser and :rfc:9110#section-5.5
-- by :user:LuShadowX.Related issues and pull requests on GitHub:
#13246. -
Fixed the WebSocket reader rejecting a compressed data frame with close code 1002 when a control frame arrived before the first data frame (regression in 3.14.2) -- by :user:
Dreamsorcerer.Related issues and pull requests on GitHub:
#13274. -
Fixed internally retried requests sending a truncated body when the request
data was a file object. -
Fixed event loop state possibly being corrupted on Python 3.12+ -- by :user:
Dreamsorcerer.Related issues and pull requests on GitHub:
#13346. -
Fixed the HTTP parser raising :exc:
~aiohttp.ClientPayloadErrorwhen a fully receivedContent-Lengthbody was pending completion -- by :user:Dreamsorcerer.Related issues and pull requests on GitHub:
#13348. -
Bounded the per-read object overhead the WebSocket reader retains while reassembling a frame delivered across many small reads; the reads are joined once when the frame completes, and folded into a single buffer if they exceed a fragment cap, so a frame dribbled in tiny reads cannot pin unbounded per-read overhead -- by :user:
Dreamsorcererand :user:bdraco. -
Fixed requests pipelined behind a request whose upgrade the handler declined
going unanswered once there were more of them than the per-connection queue
holds. With the pure-Python parser the same requests were also served more
than once -- by :user:rodrigobnogueira.Related issues and pull requests on GitHub:
#13356. -
Reduced CPU consumption when encountering many concatenated members in a compressed payload and rejected large amounts of members -- by :user:
Dreamsorcerer.Related issues and pull requests on GitHub:
#13362. -
Fixed excessive memory consumption with small WebSocket messages -- by :user:
Dreamsorcerer.Related issues and pull requests on GitHub:
#13393. -
Fixed an integer overflow on too large messages -- by :user:
Dreamsorcerer.Related issues and pull requests on GitHub:
#13415. -
Switched multipart handling to use spooled temporary files to reduce number of file descriptors needed -- by :user:
Dreamsorcerer.Related issues and pull requests on GitHub:
#13426. -
Fixed a limit on message tail after an upgrade request -- by :user:
Dreamsorcerer.Related issues and pull requests on GitHub:
#13501. -
Fixed some edge case handling in multipart parts using base 64 encoding -- by :user:
Dreamsorcerer.Related issues and pull requests on GitHub:
#13509. -
Fixed Cython 3.3.0 failing to compile the WebSocket reader: dropped the
Final[...]annotation fromALLOWED_CLOSE_CODESand theint
annotation from the localstart_posinWebSocketReader._feed_data,
both of which conflicted with declarations inreader_c.pxdunder
Cython 3.3.0 -- by :user:Georgefifth.Related issues and pull requests on GitHub:
#13520. -
Fixed the WebSocket reader accepting a new data frame injected between the
fragments of an in-progress message; per :rfc:6455#section-5.4every frame
after the first fragment and before theFINmust be a continuation, and
such a stream is now rejected as a protocol error -- by :user:arshsmith1.Related issues and pull requests on GitHub:
#13553. -
Fixed a connection being eligible for reuse after its request was cancelled
or failed while waiting for a100 Continueresponse or finalizing the
body; the request headers were already sent, so reusing the connection
corrupted the next request on it -- by :user:Dreamsorcerer.Related issues and pull requests on GitHub:
#13579. -
Fixed
BaseRequest.http_rangenot accepting case-insensitive range units -- by :user:Manny7717. -
Fixed
CookieJar.update_cookies()to copy user-passed mutableMorselobjects -- by :user:Dreamsorcerer.Related issues and pull requests on GitHub:
#13637. -
Fixed unbounded memory growth on a client WebSocket connection. A frame
protocol error detaches the reader but leaves the connection upgraded, so a
peer could stream unlimited data into an internal buffer when the application
never called :meth:~aiohttp.ClientWebSocketResponse.receive; that data is
now discarded, since nothing can parse it. Data arriving before the reader is
installed is bounded byread_bufsize, which now applies to this buffer as
well as to :attr:~aiohttp.ClientResponse.content
-- by :user:bdraco. -
Fixed pure-Python request parser not reading a body in a
HEADrequest -- by :user:Dreamsorcerer.Related issues and pull requests on GitHub:
#13671. -
Fixed host-only cookie state being lost on expiration -- by :user:
Dreamsorcerer.Related issues and pull requests on GitHub:
#13674. -
Fixed a possible
OverflowErroron cookies and a connection not being closed properly -- by :user:Dreamsorcerer.Related issues and pull requests on GitHub:
#13677. -
The first-request deadline now also closes connections whose first request body stalls, while a body that is still arriving extends the deadline instead of being interrupted -- by :user:
Dreamsorcerer.Related issues and pull requests on GitHub:
#13681. -
Fixed idle connections not being closed if no request was received -- by :user:
Dreamsorcerer.Related issues and pull requests on GitHub:
#13681. -
Fixed :meth:
~aiohttp.web.Application.add_domainnot routing a request to
its domain application when theHostheader carried a port and the
domain was registered without one, or, for a wildcard domain, uppercase
letters -- by :user:rodrigobnogueira.Related issues and pull requests on GitHub:
#13693. -
Added empty
__slots__toAbstractRouteDefso thatRouteDefand
StaticDefinstances no longer carry an unused__dict__.Related issues and pull requests on GitHub:
#13716. -
Fixed
BaseConnector(keepalive_timeout=None)crashing on the second request to the same host withTypeError: '<=' not supported between instances of 'float' and 'NoneType'-- by :user:ishan-1010. -
Fixed a crash in :meth:
~aiohttp.BodyPartReader.read_chunkon a body part
with an explicitContent-Length: 0: the part fell through to the
streaming read strategy, whose minimum chunk size assertion then failed for
chunk sizes below the boundary length. Such parts now yield an immediate
empty chunk, like any other part with a known length
-- by :user:istoolsfox. -
Fixed
Set-Cookieparsing treating unrecognized attributes as additional
cookies. EachSet-Cookieheader now sets exactly one cookie and
unrecognized attributes are ignored, per :rfc:6265#section-5.2, preventing
a malicious server from creating an attacker-selected number of cookie
objects (and correspondingly large outgoingCookieheaders) from a
bounded amount of response data.DummyCookieJar, andCookieJarin
safe mode for IP-address origins, no longer parseSet-Cookieheaders at
all -- by :user:Dreamsorcerer.Related issues and pull requests on GitHub:
#13800. -
Fixed the web server trusting the scheme of an absolute-form request-target -- by :user:
Dreamsorcerer.Related issues and pull requests on GitHub:
#13821. -
Fixed
CookieJar.filter_cookies()sending shared cookies (cookies without aDomainattribute) markedSecureover unencrypted connections -- by :user:Dreamsorcerer.Related issues and pull requests on GitHub:
#13830. -
Fixed per-request cookies (the
cookiesargument of a request method) markedSecurenot being sent to origins listed inCookieJar'streat_as_secure_origin-- by :user:Dreamsorcerer.Related issues and pull requests on GitHub:
#13833. -
Fixed the connection to an HTTP proxy staying open until garbage collection, and being reported as
Unclosed connection, when sending theCONNECTrequest for an HTTPS tunnel failed -- by :user:Garbsener.Related issues and pull requests on GitHub:
#13841. -
Resolved a redirect
Locationwith the scheme of the current URL but
without//, such ashttp:/pathorhttp:path, against the
current URL, as browsers do, instead of treating it as an absolute URL
without a host
-- by :user:asvetlov.Related issues and pull requests on GitHub:
#13855. -
Rejected absolute-form request targets without
//or with an empty
host, such ashttp:/example.com/orhttp:///example.com/, before
parsing them with yarl, which reads a host from them in its WHATWG mode;
RFC 9110 requires a host forhttpandhttps. The invalid URL test
data no longer useshttp:///example.com, which such a yarl version
parses ashttp://example.com/, as browsers do
-- by :user:asvetlov.Related issues and pull requests on GitHub:
#13858. -
Fixed :py:meth:
~aiohttp.StreamReader.readuntilnot finding a multi-byte
separator whose bytes arrived in different chunks, which made it return data
past the separator -- by :user:andrewstellman.Related issues and pull requests on GitHub:
#13870. -
Fixed mixed-case
Content-Encodingvalues (for exampleGzip)
being accepted by the parser but failing decompression, a regression
from the CVE-2025-69224 hardening -- by :user:muhammad-a-dev.Related issues and pull requests on GitHub:
#13894. -
Added limits to client cookie parsing, :class:
~aiohttp.CookieJarstorage and
generatedCookieheaders, with Firefox-style eviction, and fixed a replaced cookie
keeping the previous cookie's expiry when the new cookie has none
-- by :user:iamibiand :user:bdraco.Related issues and pull requests on GitHub:
#13930. -
Improved performance in domain matching with
Application.add_domain()-- by :user:Dreamsorcerer.Related issues and pull requests on GitHub:
#13943.
Deprecations (removal in next major release)
-
Deprecated
ClientResponse.output_sizeandClientResponse.upload_complete;
useaiohttp.UploadTrackerinstead -- by :user:Dreamsorcerer.Related issues and pull requests on GitHub:
#13579.
Removals and backward incompatible breaking changes
-
The WebSocket receive queue now only holds a weak reference to the
WebSocketReaderwhile parsing is stalled; code constructing a reader directly and passing it toset_parser()must keep its own strong reference to it, or frames the reader stopped short of parsing are lost with it -- by :user:Dreamsorcerer.Related issues and pull requests on GitHub:
#13393. -
Removed the internal writer proxy used for upload progress accounting.
AbstractStreamWritergained an optionalon_body_writecallback that
write()/write_eof()implementations must invoke with each accepted
body chunk's byte length; custom writer implementations that do not call it
will reportPayload.bytes_writtenas0-- by :user:Dreamsorcerer.Related issues and pull requests on GitHub:
#13436. -
Increased minimum yarl version to 1.25.1 -- by :user:
bdraco.Related issues and pull requests on GitHub:
#13734. -
Changed
Set-Cookieparsing to create exactly one cookie per field, as RFC 6265
and browsers do. Latername=valuepairs and unknown attributes no longer create
extra cookies, a leading pair such asPath=/or$Version=1is the cookie itself,
and legacy$Pathand$Domainattributes are ignored -- by :user:iamibi.Related issues and pull requests on GitHub:
#13930.
Improved documentation
-
Documented valid request URL forms when using :class:
~aiohttp.UnixConnector, includingbase_urlwith an HTTP host -- by :user:muhammad-a-dev. -
Corrected the documented signature of :meth:
~aiohttp.StreamReader.read_nowait,
whosenparameter defaults to-1rather than theNonethat was
previously documented -- by :user:LALITH0110.Related issues and pull requests on GitHub:
#13295. -
Added
interlock-cb, an aiohttp client circuit breaker middleware, to the
third-party libraries page -- by :user:bagowix.Related issues and pull requests on GitHub:
#13336. -
Documented that
max_redirects=0means no limit and thatallow_redirects=Falsedisables redirects -- by :user:monasco.Related issues and pull requests on GitHub:
#13658. -
Corrected the documented signature of :py:meth:
~aiohttp.StreamReader.readuntil, which
showed astrseparator although the method takesbytes, and documented its
keyword-onlymax_sizeargument -- by :user:hxperl.Related issues and pull requests on GitHub:
#13686. -
Replaced most of the
sphinx.ext.extlinks-based roles in the documentation
with :pypi:sphinx-issues, which ships the
:issue:,:pr:,:commit:and:user:roles out of the box.
Pull request references are now captioned#Ninstead ofPR #N, and
commit references as abbreviated,@-prefixed hashes
-- by :user:aiolibsbot.Related issues and pull requests on GitHub:
#13752. -
Fixed the
Content-IDexample in the multipart docs, which used a
constant missing fromaiohttp.hdrsand a value that is not a valid
message ID -- by :user:asvetlov.Related issues and pull requests on GitHub:
#13886.
Packaging updates and notes for downstreams
-
Started publishing an additional pure-Python wheel alongside the existing
per-platform binary wheels and thesdist-- by :user:webknjaz.This gives users on platforms without a working C compiler, or without a
matching pre-built wheel, an installable fallback that does not require
compilation. -
Removed the
aiohttp/_websocket/reader_c.pysymlink from the source tree; theaiohttp._websocket.reader_cextension is now compiled directly fromreader_py.pyusingcython --module-name, so distributions no longer include areader_c.pyfile that showed up as an uncovered module in coverage reports -- by :user:bdraco.Related issues and pull requests on GitHub:
#13457. -
Adopted :pep:
639license metadata -- the license is now declared as the
SPDX expressionApache-2.0 AND MITandlicense-filesmoved to the
[project]table, which raises the build-time requirement to
setuptools >= 77.0. Built distributions now carry
License-Expressioninstead of the legacyLicensefield
-- by :user:aiolibsbot.Related issues and pull requests on GitHub:
#13891.
Contributor-facing changes
-
The CI/CD is now in sync with the rest of the projects in terms of where
thecibuildwheelworkflow lives -- by :user:webknjaz.Related commits on GitHub:
:commit:59c0123d. -
Moved the pytest configuration from :file:
setup.cfgto a dedicated
:file:pytest.inithat follows the layout shared withpropcacheand
otheraio-libsprojects. Compared to the old configuration,
minversionis raised from3.8.2to8.4;pytest-xdist
(--numprocesses=auto) andpytest-cov(--cov,
--cov-context=test,--no-cov-on-fail) are enabled by default
again, so pass--numprocesses=0and/or--no-covto opt out, as
the :file:Makefiletargets and CI jobs now do where needed;
--doctest-modules,--strict-markersand
faulthandler_timeout = 30are enabled;-vis no longer added;
empty parameter sets are markedxfailinstead of skipped;
--junitxmlreports usexunit1with captured output and
call-only durations; andnorecursedirsskips more directories,
including :file:tests/isolated/
-- by :user:aiolibsbot. -
Added check that change fragment matches PR number -- by :user:
Dreamsorcerer.Related issues and pull requests on GitHub:
#12788. -
CI now builds the
sdist(and a pure-Python wheel) once, in a new
build-pure-python-distsjob, and shares that build acrosstest,
autobahn,benchmark,build-wheels,test-mobileand the
sdist-based half of linting, instead of every one of those jobs
checking out the repository and runningmake cythonizeon its own
-- by :user:webknjaz.Linting is also now split into
lint-from-git(the
:file:requirements/runtime-deps.insync check and docs spell-checking,
which need real Git history) andlint-from-sdist(mypy,
slotscheck, the changelog fragment check, andtwine check, which
build from the shared artifact instead), since ansdisttarball never
contains :file:.git. -
Synchronized the
coverage.pyconfiguration (:file:.coveragerc.tomland
:file:.coveragerc-cython.toml) with the pattern already established in
:external+yarl:doc:yarl <index>, :external+multidict:doc:multidict <index>,frozenlistand other sibling projects
-- by :user:webknjaz.Both files now anchor package discovery through
source_pkgsinstead of
relying on a same-named directory happening to exist relative to the
working directory, and add a[paths]mapping so coverage recorded
against an installed copy ofaiohttpstill combines correctly with
coverage recorded from the Git checkout. CI now letspytest-covwrite
coverage.xmldirectly via--cov-report=xmlinstead of a separate
coverage xmlstep, and the Autobahn testsuite's subprocess-based
coverage collection (which usescoverage run --append, incompatible
with parallel mode) now opts out per-invocation via a
COVERAGE_PARALLEL_MODEenvironment variable instead of trying to
override it on the command line.Related issues and pull requests on GitHub:
#13422. -
Stopped the benchmark CI job from hanging in the CodSpeed runner's apt
install by installinglibc6-dbgup front with a bounded retry, and raised
the job timeout from 15 to 30 minutes -- by :user:bdraco.Related issues and pull requests on GitHub:
#13489. -
Added benchmarks for reading masked WebSocket messages and fixed the
existing read benchmarks, which stopped measuring the parser after the
eighth large frame due to the queue limit -- by :user:bdraco.Related issues and pull requests on GitHub:
#13561. -
Removed stale
filterwarningsignores from the pytest configuration
that are no longer triggered by aiohttp, the supported Python versions
or the pinned test dependencies -- by :user:aiolibsbot.Related issues and pull requests on GitHub:
#13717. -
Dropped the leftover
PIP_USERsetting and thepip --userPATH
prefix from the CI workflow; both became dead once the test jobs started
provisioning Python viaastral-sh/setup-uv
-- by :user:aiolibsbot. -
Changed the long host in the
Hostheader tests to one that is not made
only of digits, since yarl now parses such a host as an IP address in its
default mode and rejects this one as out of range
-- by :user:asvetlov.Related issues and pull requests on GitHub:
#13861. -
Fixed
tools/gen.pydropping a header name from the generated C lookup
when two names shared a prefix that differed only in letter case, such as
Accept-CHandAccept-Charset, and made the generated code compile
without warnings -- by :user:asvetlov.Related issues and pull requests on GitHub:
#13886.
Miscellaneous internal changes
-
Avoided formatting an unused fallback
Dateheader value when the response
already has one -- by :user:marcus-campbell.Related issues and pull requests on GitHub:
#13299. -
Improved header parsing performance in the C HTTP parser by reusing the
:class:~multidict.istrbuilt for a header name missing from
aiohttp.hdrsthe next time the same name arrives, from a bounded
cache of up to 512 names of at most 64 bytes -- by :user:asvetlov.Related issues and pull requests on GitHub:
#13887.