| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121 |
- from __future__ import annotations
- import struct
- from abc import ABC, abstractmethod
- from typing import TYPE_CHECKING, Any, Generic, TypeVar
- from ._compression import Compression, IdentityCompression
- from .code import Code
- from .errors import ConnectError
- if TYPE_CHECKING:
- from collections.abc import Iterator
- from pyqwest import Response, SyncResponse
- from ._codec import Codec
- from ._protocol import ConnectWireError
- from .request import Headers
- _RES = TypeVar("_RES")
- _T = TypeVar("_T")
- class EnvelopeReader(Generic[_RES]):
- _next_message_length: int | None
- def __init__(
- self,
- message_class: type[_RES],
- codec: Codec,
- compression: Compression,
- read_max_bytes: int | None,
- ) -> None:
- self._buffer = bytearray()
- self._message_class = message_class
- self._codec = codec
- self._compression = compression
- self._read_max_bytes = read_max_bytes
- self._next_message_length = None
- def feed(self, data: bytes | memoryview | bytearray) -> Iterator[_RES]:
- self._buffer.extend(data)
- return self._read_messages()
- def _read_messages(self) -> Iterator[_RES]:
- while self._buffer:
- if self._next_message_length is not None:
- if len(self._buffer) < self._next_message_length + 5:
- return
- prefix_byte = self._buffer[0]
- compressed = prefix_byte & 0b01 != 0
- message_data = self._buffer[5 : 5 + self._next_message_length]
- self._buffer = self._buffer[5 + self._next_message_length :]
- self._next_message_length = None
- if compressed:
- if isinstance(self._compression, IdentityCompression):
- raise ConnectError(
- Code.INTERNAL,
- "protocol error: sent compressed message without compression support",
- )
- message_data = self._compression.decompress(message_data)
- if (
- self._read_max_bytes is not None
- and len(message_data) > self._read_max_bytes
- ):
- raise ConnectError(
- Code.RESOURCE_EXHAUSTED,
- f"message is larger than configured max {self._read_max_bytes}",
- )
- if self.handle_end_message(prefix_byte, message_data):
- return
- res = self._codec.decode(message_data, self._message_class)
- yield res
- if len(self._buffer) < 5:
- return
- self._next_message_length = int.from_bytes(self._buffer[1:5], "big")
- def handle_end_message(
- self, prefix_byte: int, message_data: bytes | bytearray
- ) -> bool:
- """For client protocols with an end message like Connect and gRPC-Web, handle the end message.
- Returns True if the end message was handled, False otherwise.
- """
- return False
- def handle_response_complete(
- self, response: Response | SyncResponse, e: ConnectError | None = None
- ) -> None:
- """Handle any client finalization needed when the response is complete.
- This is typically used to process trailers for gRPC.
- """
- class EnvelopeWriter(ABC, Generic[_T]):
- def __init__(self, codec: Codec[_T, Any], compression: Compression | None) -> None:
- self._codec = codec
- self._compression = compression
- self._prefix = (
- 0 if not compression or isinstance(compression, IdentityCompression) else 1
- )
- def write(self, message: _T) -> bytes:
- data = self._codec.encode(message)
- if self._compression:
- data = self._compression.compress(data)
- # This copies data into the final envelope, but it is still better than issuing
- # I/O multiple times for small prefix / length elements.
- return struct.pack(">BI", self._prefix, len(data)) + data
- @abstractmethod
- def end(
- self, user_trailers: Headers, error: ConnectWireError | None
- ) -> bytes | Headers: ...
|