@@ -314,7 +314,7 @@ async def read(self, *, decode: bool = False) -> bytes:
314314 data .extend (await self .read_chunk (self .chunk_size ))
315315 # https://github.com/python/mypy/issues/17537
316316 if decode : # type: ignore[unreachable]
317- return await self .decode (data )
317+ return await self .decode_async (data )
318318 return data
319319
320320 async def read_chunk (self , size : int = chunk_size ) -> bytes :
@@ -492,20 +492,58 @@ def at_eof(self) -> bool:
492492 """Returns True if the boundary was reached or False otherwise."""
493493 return self ._at_eof
494494
495- async def decode (self , data : bytes ) -> bytes :
496- """Decodes data.
495+ def _apply_content_transfer_decoding (self , data : bytes ) -> bytes :
496+ """Apply Content-Transfer-Encoding decoding if header is present."""
497+ if CONTENT_TRANSFER_ENCODING in self .headers :
498+ return self ._decode_content_transfer (data )
499+ return data
500+
501+ def _needs_content_decoding (self ) -> bool :
502+ """Check if Content-Encoding decoding should be applied."""
503+ # https://datatracker.ietf.org/doc/html/rfc7578#section-4.8
504+ return not self ._is_form_data and CONTENT_ENCODING in self .headers
505+
506+ def decode (self , data : bytes ) -> bytes :
507+ """Decodes data synchronously.
497508
498- Decoding is done according the specified Content-Encoding
509+ Decodes data according the specified Content-Encoding
499510 or Content-Transfer-Encoding headers value.
511+
512+ Note: For large payloads, consider using decode_async() instead
513+ to avoid blocking the event loop during decompression.
500514 """
501- if CONTENT_TRANSFER_ENCODING in self .headers :
502- data = self ._decode_content_transfer (data )
503- # https://datatracker.ietf.org/doc/html/rfc7578#section-4.8
504- if not self ._is_form_data and CONTENT_ENCODING in self .headers :
505- return await self ._decode_content (data )
515+ data = self ._apply_content_transfer_decoding (data )
516+ if self ._needs_content_decoding ():
517+ return self ._decode_content (data )
506518 return data
507519
508- async def _decode_content (self , data : bytes ) -> bytes :
520+ async def decode_async (self , data : bytes ) -> bytes :
521+ """Decodes data asynchronously.
522+
523+ Decodes data according the specified Content-Encoding
524+ or Content-Transfer-Encoding headers value.
525+
526+ This method offloads decompression to an executor for large payloads
527+ to avoid blocking the event loop.
528+ """
529+ data = self ._apply_content_transfer_decoding (data )
530+ if self ._needs_content_decoding ():
531+ return await self ._decode_content_async (data )
532+ return data
533+
534+ def _decode_content (self , data : bytes ) -> bytes :
535+ encoding = self .headers .get (CONTENT_ENCODING , "" ).lower ()
536+ if encoding == "identity" :
537+ return data
538+ if encoding in {"deflate" , "gzip" }:
539+ return ZLibDecompressor (
540+ encoding = encoding ,
541+ suppress_deflate_header = True ,
542+ ).decompress_sync (data , max_length = self ._max_decompress_size )
543+
544+ raise RuntimeError (f"unknown content encoding: { encoding } " )
545+
546+ async def _decode_content_async (self , data : bytes ) -> bytes :
509547 encoding = self .headers .get (CONTENT_ENCODING , "" ).lower ()
510548 if encoding == "identity" :
511549 return data
@@ -588,7 +626,7 @@ async def write(self, writer: AbstractStreamWriter) -> None:
588626 field = self ._value
589627 chunk = await field .read_chunk (size = 2 ** 16 )
590628 while chunk :
591- await writer .write (await field .decode (chunk ))
629+ await writer .write (await field .decode_async (chunk ))
592630 chunk = await field .read_chunk (size = 2 ** 16 )
593631
594632
0 commit comments