Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
138 commits
Select commit Hold shift + click to select a range
5968707
Test chunk splits after pause
Dreamsorcerer Jan 15, 2026
2eeeb5e
Update tests/test_http_parser.py
Dreamsorcerer Jan 15, 2026
8b0cfee
Apply suggestions from code review
Dreamsorcerer Jan 15, 2026
9cd9154
Update tests/test_http_parser.py
Dreamsorcerer Jan 15, 2026
83fc87c
Update tests/test_http_parser.py
Dreamsorcerer Jan 15, 2026
efe2ca9
Read small chunks from decompressors
Dreamsorcerer Jan 15, 2026
57bf4a2
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 15, 2026
82a4dfb
Fix
Dreamsorcerer Jan 15, 2026
c44fd1e
Merge branch 'Dreamsorcerer-patch-5' of github.com:aio-libs/aiohttp i…
Dreamsorcerer Jan 15, 2026
a85f38b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 15, 2026
602eb51
Update compression_utils.py
Dreamsorcerer Jan 15, 2026
32f0a84
Update test_http_parser.py
Dreamsorcerer Jan 16, 2026
94c70a9
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 16, 2026
bd34cea
Update test_http_parser.py
Dreamsorcerer Jan 16, 2026
0d4683c
Update test_http_parser.py
Dreamsorcerer Jan 16, 2026
d3df801
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 16, 2026
b81f901
Update test_http_parser.py
Dreamsorcerer Jan 16, 2026
4dd2279
Fix
Dreamsorcerer Jan 16, 2026
3a604a4
Update test_http_parser.py
Dreamsorcerer Jan 16, 2026
10feb8b
Fix
Dreamsorcerer Jan 16, 2026
d02bbf5
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 16, 2026
67bd57d
Update test_http_parser.py
Dreamsorcerer Jan 16, 2026
04b717f
Fix
Dreamsorcerer Jan 16, 2026
53ac968
Update test_http_parser.py
Dreamsorcerer Jan 16, 2026
44c12a1
Fix
Dreamsorcerer Jan 16, 2026
7986da3
Merge branch 'Dreamsorcerer-patch-5' of github.com:aio-libs/aiohttp i…
Dreamsorcerer Jan 16, 2026
75794c2
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 16, 2026
ab73626
Update test_http_parser.py
Dreamsorcerer Jan 16, 2026
ae46ee1
Update test_http_parser.py
Dreamsorcerer Jan 16, 2026
d738031
Update test_http_parser.py
Dreamsorcerer Jan 16, 2026
1d5fc0e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 16, 2026
28dffda
Update test_http_parser.py
Dreamsorcerer Jan 16, 2026
2eb92be
Update test_http_parser.py
Dreamsorcerer Jan 16, 2026
3dbf9c8
Update test_http_parser.py
Dreamsorcerer Jan 16, 2026
3ea25ca
Fix
Dreamsorcerer Jan 16, 2026
d99fc34
Update streams.py
Dreamsorcerer Jan 16, 2026
11cf432
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 16, 2026
de6fca2
Update http_parser.py
Dreamsorcerer Jan 16, 2026
3a80456
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 16, 2026
7da23c1
Update http_parser.py
Dreamsorcerer Jan 16, 2026
a4fc7c1
Update _http_parser.pyx
Dreamsorcerer Jan 16, 2026
b3c77d7
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 16, 2026
f26edc0
Update _http_parser.pyx
Dreamsorcerer Jan 16, 2026
1997c8b
Update _http_parser.pyx
Dreamsorcerer Jan 16, 2026
b4443b7
Apply suggestions from code review
Dreamsorcerer Jan 16, 2026
b17c013
Fix
Dreamsorcerer Jan 16, 2026
d628cee
Update _http_parser.pyx
Dreamsorcerer Jan 16, 2026
cd2e07a
Update http_parser.py
Dreamsorcerer Jan 17, 2026
fd74c03
Update compression_utils.py
Dreamsorcerer Jan 17, 2026
087de36
Update streams.py
Dreamsorcerer Jan 17, 2026
103c4a6
Update _http_parser.pyx
Dreamsorcerer Jan 17, 2026
a249c04
Update _http_parser.pyx
Dreamsorcerer Jan 17, 2026
f20ddc7
Update http_parser.py
Dreamsorcerer Jan 17, 2026
0fe7000
Update web_protocol.py
Dreamsorcerer Jan 17, 2026
5130266
Fix
Dreamsorcerer Jan 17, 2026
7db0120
Update web_protocol.py
Dreamsorcerer Jan 17, 2026
d113ccb
Update http_parser.py
Dreamsorcerer Jan 17, 2026
185db0b
Update aiohttp/_http_parser.pyx
Dreamsorcerer Jan 17, 2026
3be7ac6
Update base_protocol.py
Dreamsorcerer Jan 17, 2026
80eb8c7
Update _http_parser.pyx
Dreamsorcerer Jan 19, 2026
5183441
Update _http_parser.pyx
Dreamsorcerer Jan 19, 2026
60c61e3
Update test_http_parser.py
Dreamsorcerer Jan 19, 2026
2a6eff8
Update _http_parser.pyx
Dreamsorcerer Jan 19, 2026
dbfc0c8
Update _http_parser.pyx
Dreamsorcerer Jan 19, 2026
eac561e
Update _http_parser.pyx
Dreamsorcerer Jan 21, 2026
1a40781
Update _http_parser.pyx
Dreamsorcerer Jan 21, 2026
520b64a
Update _http_parser.pyx
Dreamsorcerer Jan 22, 2026
9c2987b
Update _http_parser.pyx
Dreamsorcerer Jan 25, 2026
c4b058d
Update _http_parser.pyx
Dreamsorcerer Jan 25, 2026
fa644c7
Update test_client_functional.py
Dreamsorcerer Jan 25, 2026
dd82b9f
Update test_base_protocol.py
Dreamsorcerer Jan 25, 2026
3099a40
Update _http_parser.pyx
Dreamsorcerer Jan 26, 2026
7775793
Update _http_parser.pyx
Dreamsorcerer Jan 26, 2026
9525459
Update base_protocol.py
Dreamsorcerer Jan 26, 2026
f4985a2
Update client_proto.py
Dreamsorcerer Jan 26, 2026
32d5c5f
Update web_protocol.py
Dreamsorcerer Jan 26, 2026
afa2b55
Update test_base_protocol.py
Dreamsorcerer Jan 26, 2026
30c23d4
Update test_client_proto.py
Dreamsorcerer Jan 26, 2026
6a5a2c7
Update test_http_parser.py
Dreamsorcerer Jan 26, 2026
45f66d1
Update test_http_parser.py
Dreamsorcerer Jan 26, 2026
80d955f
Update test_flowcontrol_streams.py
Dreamsorcerer Jan 26, 2026
0cc6275
Update test_client_proto.py
Dreamsorcerer Jan 26, 2026
69e3bbd
Update test_http_parser.py
Dreamsorcerer Jan 26, 2026
397e905
Update test_http_parser.py
Dreamsorcerer Jan 26, 2026
aed6863
Update test_websocket_parser.py
Dreamsorcerer Jan 26, 2026
a0fb83b
Update test_base_protocol.py
Dreamsorcerer Jan 26, 2026
360f6de
Update test_client_proto.py
Dreamsorcerer Jan 26, 2026
6e04d89
Update test_http_parser.py
Dreamsorcerer Jan 26, 2026
698e0cc
Update compression_utils.py
Dreamsorcerer Jan 27, 2026
48d4119
Update streams.py
Dreamsorcerer Jan 28, 2026
6598ff6
Update base_protocol.py
Dreamsorcerer Jan 28, 2026
3f76e2c
Update base_protocol.py
Dreamsorcerer Jan 28, 2026
faf6e40
Update client_proto.py
Dreamsorcerer Jan 28, 2026
add2b70
Update base_protocol.py
Dreamsorcerer Jan 28, 2026
3396079
Update compression_utils.py
Dreamsorcerer Jan 28, 2026
69a59a8
Update compression_utils.py
Dreamsorcerer Jan 28, 2026
c5f6e6a
Update compression_utils.py
Dreamsorcerer Jan 29, 2026
51eda1b
Update compression_utils.py
Dreamsorcerer Jan 29, 2026
156fb3c
Update base_protocol.py
Dreamsorcerer Jan 29, 2026
8b05bfc
Update streams.py
Dreamsorcerer Jan 29, 2026
f1bfba5
Update test_http_parser.py
Dreamsorcerer Jan 29, 2026
9c1ffa9
Update test_base_protocol.py
Dreamsorcerer Jan 29, 2026
79c9da6
Update test_streams.py
Dreamsorcerer Jan 29, 2026
9467ad2
Update test_http_parser.py
Dreamsorcerer Jan 29, 2026
5e8068f
Apply suggestions from code review
Dreamsorcerer Jan 30, 2026
e1de722
Update http_parser.py
Dreamsorcerer Jan 30, 2026
46713f5
Update test_client_functional.py
Dreamsorcerer Jan 30, 2026
67c210d
Update http_exceptions.py
Dreamsorcerer Jan 30, 2026
2bc5d17
Update test_multipart.py
Dreamsorcerer Feb 3, 2026
46eeeda
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 3, 2026
618309f
Update test_web_functional.py
Dreamsorcerer Feb 3, 2026
2cc7567
Apply suggestions from code review
Dreamsorcerer Feb 3, 2026
1f256df
Update test_multipart.py
Dreamsorcerer Feb 3, 2026
70a51f5
Update multipart.py
Dreamsorcerer Feb 3, 2026
06c5ec9
Update multipart.py
Dreamsorcerer Feb 3, 2026
69c1602
Update multipart.py
Dreamsorcerer Feb 3, 2026
36c2bc3
Update web_request.py
Dreamsorcerer Feb 3, 2026
78e7099
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 3, 2026
2c47750
Update test_multipart.py
Dreamsorcerer Feb 3, 2026
e47504c
Update multipart.py
Dreamsorcerer Feb 3, 2026
d92ed4c
Apply suggestions from code review
Dreamsorcerer Feb 3, 2026
ad2c9ab
Update multipart.py
Dreamsorcerer Feb 3, 2026
bd7c69c
Update multipart.py
Dreamsorcerer Feb 3, 2026
2106fbe
Update multipart.py
Dreamsorcerer Feb 3, 2026
4f7f928
Update multipart.py
Dreamsorcerer Feb 3, 2026
2677720
Update web_request.py
Dreamsorcerer Feb 4, 2026
5cb6324
Update multipart.py
Dreamsorcerer Feb 4, 2026
bd6b520
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 4, 2026
bd625ba
Apply suggestion from @Dreamsorcerer
Dreamsorcerer Feb 4, 2026
ef3b5d9
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 4, 2026
c2ba043
Update multipart.py
Dreamsorcerer Feb 4, 2026
dcdc386
Update multipart.py
Dreamsorcerer Feb 4, 2026
3525d3d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 4, 2026
271d10b
Update multipart.py
Dreamsorcerer Feb 4, 2026
6749571
Update multipart.py
Dreamsorcerer Feb 4, 2026
28c6bcf
Merge branch 'master' into Dreamsorcerer-patch-5
Dreamsorcerer Feb 5, 2026
e874ebe
Apply suggestion from @Dreamsorcerer
Dreamsorcerer Feb 5, 2026
389b0e6
Update test_multipart.py
Dreamsorcerer Feb 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions aiohttp/_cparser.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ cdef extern from "llhttp.h":

int llhttp_should_keep_alive(const llhttp_t* parser)

void llhttp_resume(llhttp_t* parser)
void llhttp_resume_after_upgrade(llhttp_t* parser)

llhttp_errno_t llhttp_get_errno(const llhttp_t* parser)
Expand Down
71 changes: 55 additions & 16 deletions aiohttp/_http_parser.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -291,15 +291,19 @@ cdef class HttpParser:
bint _response_with_body
bint _read_until_eof

bytes _tail
bint _started
object _url
bytearray _buf
str _path
str _reason
list _headers
bint _last_had_more_data
list _raw_headers
bint _upgraded
list _messages
bint _more_data_available
bint _paused
object _payload
bint _payload_error
object _payload_exception
Expand Down Expand Up @@ -345,13 +349,17 @@ cdef class HttpParser:
self._timer = timer

self._buf = bytearray()
self._last_had_more_data = False
self._more_data_available = False
self._paused = False
self._payload = None
self._payload_error = 0
self._payload_exception = payload_exception
self._messages = []

self._raw_name = EMPTY_BYTES
self._raw_value = EMPTY_BYTES
self._tail = b""
self._has_value = False
self._header_name_size = 0

Expand Down Expand Up @@ -503,6 +511,10 @@ cdef class HttpParser:

### Public API ###

def pause_reading(self):
assert self._payload is not None
self._paused = True

def feed_eof(self):
cdef bytes desc

Expand All @@ -529,6 +541,23 @@ cdef class HttpParser:
size_t nb
cdef cparser.llhttp_errno_t errno

if self._tail:
Copy link
Member

Choose a reason for hiding this comment

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

CFLAGS="-I$(pwd)/vendor/llhttp/build" cythonize -X language_level=3 -a -i aiohttp/_http_parser.pyx

Than open aiohttp/_http_parser.html and look for yellow. Anything that it white Cython can optimize to remove the Python calls.

data, self._tail = self._tail + data, EMPTY_BYTES

had_more_data = self._more_data_available
if self._more_data_available:
result = cb_on_body(self._cparser, EMPTY_BYTES, 0)
if result is cparser.HPE_PAUSED:
self._last_had_more_data = had_more_data
self._tail = data
return (), False, EMPTY_BYTES
Copy link
Member

Choose a reason for hiding this comment

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

nit: Newer version of Cython optimize literal b"" better since it knows its guaranteed to by PyBytes

# TODO: Do we need to handle error case (-1)?
Copy link
Member

Choose a reason for hiding this comment

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

cdef int cb_on_body(cparser.llhttp_t* parser,
                    const char *at, size_t length) except -1:

I think this is already covered, but could use a test.

Marked with except -1 so should be automatic Cython should check PyErr_Occurred and re-raise the caught BaseException in cb_on_body

# If the last pause had more data, then we probably paused at the
# end of the body. Therefore we need to continue with empty bytes.
if not data and not self._last_had_more_data:
return (), False, EMPTY_BYTES
Copy link
Member

Choose a reason for hiding this comment

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

nit: Newer version of Cython optimize literal b"" better since it knows its guaranteed to by PyBytes

self._last_had_more_data = False

PyObject_GetBuffer(data, &self.py_buf, PyBUF_SIMPLE)
data_len = <size_t>self.py_buf.len

Expand All @@ -539,12 +568,15 @@ cdef class HttpParser:

if errno is cparser.HPE_PAUSED_UPGRADE:
cparser.llhttp_resume_after_upgrade(self._cparser)

nb = cparser.llhttp_get_error_pos(self._cparser) - <char*>self.py_buf.buf
elif errno is cparser.HPE_PAUSED:
cparser.llhttp_resume(self._cparser)
pos = cparser.llhttp_get_error_pos(self._cparser) - <char*>self.py_buf.buf
self._tail = data[pos:]

PyBuffer_Release(&self.py_buf)

if errno not in (cparser.HPE_OK, cparser.HPE_PAUSED_UPGRADE):
if errno not in (cparser.HPE_OK, cparser.HPE_PAUSED, cparser.HPE_PAUSED_UPGRADE):
if self._payload_error == 0:
if self._last_error is not None:
ex = self._last_error
Expand All @@ -569,7 +601,7 @@ cdef class HttpParser:
if self._upgraded:
return messages, True, data[nb:]
else:
return messages, False, b""
return messages, False, EMPTY_BYTES
Copy link
Member

Choose a reason for hiding this comment

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

nit: Newer version of Cython optimize literal b"" better since it knows its guaranteed to by PyBytes

Copy link
Member Author

Choose a reason for hiding this comment

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

So, I should make the change in the other direction and remove that global then?


def set_upgraded(self, val):
self._upgraded = val
Expand Down Expand Up @@ -762,19 +794,26 @@ cdef int cb_on_body(cparser.llhttp_t* parser,
const char *at, size_t length) except -1:
cdef HttpParser pyparser = <HttpParser>parser.data
cdef bytes body = at[:length]
try:
pyparser._payload.feed_data(body)
except BaseException as underlying_exc:
reraised_exc = underlying_exc
if pyparser._payload_exception is not None:
reraised_exc = pyparser._payload_exception(str(underlying_exc))

set_exception(pyparser._payload, reraised_exc, underlying_exc)

pyparser._payload_error = 1
return -1
else:
return 0
while body or pyparser._more_data_available:
try:
pyparser._more_data_available = pyparser._payload.feed_data(body)
except BaseException as underlying_exc:
reraised_exc = underlying_exc
if pyparser._payload_exception is not None:
reraised_exc = pyparser._payload_exception(str(underlying_exc))

set_exception(pyparser._payload, reraised_exc, underlying_exc)

pyparser._payload_error = 1
pyparser._paused = False
return -1
body = EMPTY_BYTES
Copy link
Member

Choose a reason for hiding this comment

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

nit: Newer version of Cython optimize literal b"" better since it knows its guaranteed to by PyBytes


if pyparser._paused:
pyparser._paused = False
return cparser.HPE_PAUSED
pyparser._paused = False
return 0


cdef int cb_on_message_complete(cparser.llhttp_t* parser) except -1:
Expand Down
33 changes: 27 additions & 6 deletions aiohttp/base_protocol.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
import asyncio
from typing import cast
from typing import TYPE_CHECKING, Any, cast

from .client_exceptions import ClientConnectionResetError
from .helpers import set_exception
from .tcp_helpers import tcp_nodelay

if TYPE_CHECKING:
from .http_parser import HttpParser


class BaseProtocol(asyncio.Protocol):
__slots__ = (
"_loop",
"_paused",
"_parser",
"_drain_waiter",
"_connection_lost",
"_reading_paused",
"_upgraded",
"transport",
)

def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
def __init__(
self, loop: asyncio.AbstractEventLoop, parser: "HttpParser[Any] | None" = None
) -> None:
self._loop: asyncio.AbstractEventLoop = loop
self._paused = False
self._drain_waiter: asyncio.Future[None] | None = None
self._reading_paused = False
self._parser = parser
self._upgraded = False

self.transport: asyncio.Transport | None = None

Expand Down Expand Up @@ -48,15 +57,27 @@ def resume_writing(self) -> None:
waiter.set_result(None)

def pause_reading(self) -> None:
if not self._reading_paused and self.transport is not None:
self._reading_paused = True
# Parser shouldn't be paused on websockets.
if not self._upgraded:
assert self._parser is not None
self._parser.pause_reading()
if self.transport is not None:
try:
self.transport.pause_reading()
except (AttributeError, NotImplementedError, RuntimeError):
pass
self._reading_paused = True

def resume_reading(self) -> None:
if self._reading_paused and self.transport is not None:
def resume_reading(self, resume_parser: bool = True) -> None:
self._reading_paused = False

# This will resume parsing any unprocessed data from the last pause.
if not self._upgraded and resume_parser:
self.data_received(b"")

# Reading may have been paused again in the above call if there was a lot of
# compressed data still pending.
if not self._reading_paused and self.transport is not None:
try:
self.transport.resume_reading()
except (AttributeError, NotImplementedError, RuntimeError):
Expand Down
12 changes: 3 additions & 9 deletions aiohttp/client_proto.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class ResponseHandler(BaseProtocol, DataQueue[tuple[RawResponseMessage, StreamRe
"""Helper class to adapt between Protocol and StreamReader."""

def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
BaseProtocol.__init__(self, loop=loop)
BaseProtocol.__init__(self, loop=loop, parser=None)
DataQueue.__init__(self, loop)

self._should_close = False
Expand All @@ -36,10 +36,7 @@ def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
self._payload_parser: WebSocketReader | None = None

self._timer = None

self._tail = b""
self._upgraded = False
self._parser: HttpResponseParser | None = None

self._read_timeout: float | None = None
self._read_timeout_handle: asyncio.TimerHandle | None = None
Expand Down Expand Up @@ -190,8 +187,8 @@ def pause_reading(self) -> None:
super().pause_reading()
self._drop_timeout()

def resume_reading(self) -> None:
super().resume_reading()
def resume_reading(self, resume_parser: bool = True) -> None:
super().resume_reading(resume_parser)
self._reschedule_timeout()

def set_exception(
Expand Down Expand Up @@ -293,9 +290,6 @@ def _on_read_timeout(self) -> None:
def data_received(self, data: bytes) -> None:
self._reschedule_timeout()

if not data:
return

# custom payload parser - currently always WebSocketReader
if self._payload_parser is not None:
eof, tail = self._payload_parser.feed_data(data)
Expand Down
37 changes: 33 additions & 4 deletions aiohttp/compression_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@


MAX_SYNC_CHUNK_SIZE = 4096
DEFAULT_MAX_DECOMPRESS_SIZE = 2**25 # 32MiB
# Matches the max size we receive from sockets:
# https://github.com/python/cpython/blob/1857a40807daeae3a1bf5efb682de9c9ae6df845/Lib/asyncio/selector_events.py#L766
DEFAULT_MAX_DECOMPRESS_SIZE = 256 * 1024

# Unlimited decompression constants - different libraries use different conventions
ZLIB_MAX_LENGTH_UNLIMITED = 0 # zlib uses 0 to mean unlimited
Expand All @@ -53,6 +55,9 @@ def flush(self, length: int = ..., /) -> bytes: ...
@property
def eof(self) -> bool: ...

@property
def unconsumed_tail(self) -> bytes: ...


class ZLibBackendProtocol(Protocol):
MAX_WBITS: int
Expand Down Expand Up @@ -179,6 +184,11 @@ async def decompress(
)
return self.decompress_sync(data, max_length)

@property
@abstractmethod
def data_available(self) -> bool:
"""Return True if more output is available by passing b""."""


class ZLibCompressor:
def __init__(
Expand Down Expand Up @@ -271,7 +281,9 @@ def __init__(
def decompress_sync(
self, data: Buffer, max_length: int = ZLIB_MAX_LENGTH_UNLIMITED
) -> bytes:
return self._decompressor.decompress(data, max_length)
return self._decompressor.decompress(
self._decompressor.unconsumed_tail + data, max_length
)

def flush(self, length: int = 0) -> bytes:
return (
Expand All @@ -280,6 +292,10 @@ def flush(self, length: int = 0) -> bytes:
else self._decompressor.flush()
)

@property
def data_available(self) -> bool:
return bool(self._decompressor.unconsumed_tail)

@property
def eof(self) -> bool:
return self._decompressor.eof
Expand All @@ -301,22 +317,31 @@ def __init__(
"Please install `Brotli` module"
)
self._obj = brotli.Decompressor()
self._last_empty = False
super().__init__(executor=executor, max_sync_chunk_size=max_sync_chunk_size)

def decompress_sync(
self, data: Buffer, max_length: int = ZLIB_MAX_LENGTH_UNLIMITED
) -> bytes:
"""Decompress the given data."""
if hasattr(self._obj, "decompress"):
return cast(bytes, self._obj.decompress(data, max_length))
return cast(bytes, self._obj.process(data, max_length))
result = cast(bytes, self._obj.decompress(data, max_length))
else:
result = cast(bytes, self._obj.process(data, max_length))
# Only way to know that brotli has no further data is checking we get no output
self._last_empty = result == b""
Comment on lines +331 to +332
Copy link
Member

Choose a reason for hiding this comment

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

I think this is what it is, but if we only need it for Brotli maybe move the self._last_empty to the brotli classes so its clear its only for brotli?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is the BrotliDecompressor class.

return result

def flush(self) -> bytes:
"""Flush the decompressor."""
if hasattr(self._obj, "flush"):
return cast(bytes, self._obj.flush())
return b""

@property
def data_available(self) -> bool:
return not self._obj.is_finished() and not self._last_empty


class ZSTDDecompressor(DecompressionBaseHandler):
def __init__(
Expand Down Expand Up @@ -346,3 +371,7 @@ def decompress_sync(

def flush(self) -> bytes:
return b""

@property
def data_available(self) -> bool:
return not self._obj.needs_input and not self._obj.eof
4 changes: 0 additions & 4 deletions aiohttp/http_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,6 @@ class ContentLengthError(PayloadEncodingError):
"""Not enough data to satisfy content length header."""


class DecompressSizeError(PayloadEncodingError):
Copy link
Member Author

Choose a reason for hiding this comment

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

Need to retain this in the backport, in case anybody is referencing it.

"""Decompressed size exceeds the configured limit."""


class LineTooLong(BadHttpMessage):
def __init__(self, line: bytes, limit: int) -> None:
super().__init__(f"Got more than {limit} bytes when reading: {line!r}.")
Expand Down
Loading
Loading