Open
Description
On ESP32, after receiving 5-10 full websocket frames, this read returns only a partial frame:
payload = await self.reader.read(length)
This is to be expected, as the read(length) call in MicroPython's asyncio.Stream is not guaranteed to return the full length bytes in a single call, especially for large payloads, due to non-blocking I/O or buffer constraints.
I didn't observe this issue on unix/desktop MicroPython, only on the ESP32.
The proper way is to re-do the read() for the remaining length until the entire frame has been received.
The patch below fixes that, as well as adding some error handling.
--- old/aiohttp_ws.py 2025-05-20 14:06:16.111521205 +0200
+++ aiohttp/aiohttp_ws.py 2025-05-20 14:16:28.985286423 +0200
@@ -197,13 +199,31 @@
return opcode, payload
fin, opcode, has_mask, length = self._parse_frame_header(header)
if length == 126: # Magic number, length header is 2 bytes
- (length,) = struct.unpack("!H", await self.reader.read(2))
+ length_data = await self.reader.read(2)
+ if len(length_data) != 2:
+ print("WARNING: aiohttp_ws.py failed to read 2-byte length, closing")
+ return self.CLOSE, b""
+ (length,) = struct.unpack("!H", length_data)
elif length == 127: # Magic number, length header is 8 bytes
- (length,) = struct.unpack("!Q", await self.reader.read(8))
-
+ length_data = await self.reader.read(8)
+ if len(length_data) != 8:
+ print("WARNING: aiohttp_ws.py failed to read 8-byte length, closing")
+ return self.CLOSE, b""
+ (length,) = struct.unpack("!Q", length_data)
if has_mask: # pragma: no cover
mask = await self.reader.read(4)
- payload = await self.reader.read(length)
+ if len(mask) != 4:
+ print("WARNING: aiohttp_ws.py failed to read mask, closing")
+ return self.CLOSE, b""
+ payload = b""
+ remaining_length = length
+ while remaining_length > 0:
+ chunk = await self.reader.read(remaining_length)
+ if not chunk: # Connection closed or error
+ print(f"WARNING: aiohttp_ws.py connection closed while reading payload, got {len(payload)}/{length} bytes, closing")
+ return self.CLOSE, b""
+ payload += chunk
+ remaining_length -= len(chunk)
if has_mask: # pragma: no cover
payload = bytes(x ^ mask[i % 4] for i, x in enumerate(payload))
return opcode, payload
Metadata
Metadata
Assignees
Labels
No labels