123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135 |
- """
- :mod:`websockets.legacy.framing` reads and writes WebSocket frames.
- It deals with a single frame at a time. Anything that depends on the sequence
- of frames is implemented in :mod:`websockets.legacy.protocol`.
- See `section 5 of RFC 6455`_.
- .. _section 5 of RFC 6455: http://tools.ietf.org/html/rfc6455#section-5
- """
- import struct
- from typing import Any, Awaitable, Callable, Optional, Sequence
- from ..exceptions import PayloadTooBig, ProtocolError
- from ..frames import Frame as NewFrame, Opcode
- try:
- from ..speedups import apply_mask
- except ImportError: # pragma: no cover
- from ..utils import apply_mask
- class Frame(NewFrame):
- @classmethod
- async def read(
- cls,
- reader: Callable[[int], Awaitable[bytes]],
- *,
- mask: bool,
- max_size: Optional[int] = None,
- extensions: Optional[Sequence["extensions.Extension"]] = None,
- ) -> "Frame":
- """
- Read a WebSocket frame.
- :param reader: coroutine that reads exactly the requested number of
- bytes, unless the end of file is reached
- :param mask: whether the frame should be masked i.e. whether the read
- happens on the server side
- :param max_size: maximum payload size in bytes
- :param extensions: list of classes with a ``decode()`` method that
- transforms the frame and return a new frame; extensions are applied
- in reverse order
- :raises ~websockets.exceptions.PayloadTooBig: if the frame exceeds
- ``max_size``
- :raises ~websockets.exceptions.ProtocolError: if the frame
- contains incorrect values
- """
- # Read the header.
- data = await reader(2)
- head1, head2 = struct.unpack("!BB", data)
- # While not Pythonic, this is marginally faster than calling bool().
- fin = True if head1 & 0b10000000 else False
- rsv1 = True if head1 & 0b01000000 else False
- rsv2 = True if head1 & 0b00100000 else False
- rsv3 = True if head1 & 0b00010000 else False
- try:
- opcode = Opcode(head1 & 0b00001111)
- except ValueError as exc:
- raise ProtocolError("invalid opcode") from exc
- if (True if head2 & 0b10000000 else False) != mask:
- raise ProtocolError("incorrect masking")
- length = head2 & 0b01111111
- if length == 126:
- data = await reader(2)
- (length,) = struct.unpack("!H", data)
- elif length == 127:
- data = await reader(8)
- (length,) = struct.unpack("!Q", data)
- if max_size is not None and length > max_size:
- raise PayloadTooBig(f"over size limit ({length} > {max_size} bytes)")
- if mask:
- mask_bits = await reader(4)
- # Read the data.
- data = await reader(length)
- if mask:
- data = apply_mask(data, mask_bits)
- frame = cls(fin, opcode, data, rsv1, rsv2, rsv3)
- if extensions is None:
- extensions = []
- for extension in reversed(extensions):
- frame = cls(*extension.decode(frame, max_size=max_size))
- frame.check()
- return frame
- def write(
- self,
- write: Callable[[bytes], Any],
- *,
- mask: bool,
- extensions: Optional[Sequence["extensions.Extension"]] = None,
- ) -> None:
- """
- Write a WebSocket frame.
- :param frame: frame to write
- :param write: function that writes bytes
- :param mask: whether the frame should be masked i.e. whether the write
- happens on the client side
- :param extensions: list of classes with an ``encode()`` method that
- transform the frame and return a new frame; extensions are applied
- in order
- :raises ~websockets.exceptions.ProtocolError: if the frame
- contains incorrect values
- """
- # The frame is written in a single call to write in order to prevent
- # TCP fragmentation. See #68 for details. This also makes it safe to
- # send frames concurrently from multiple coroutines.
- write(self.serialize(mask=mask, extensions=extensions))
- # Backwards compatibility with previously documented public APIs
- from ..frames import parse_close # isort:skip # noqa
- from ..frames import prepare_ctrl as encode_data # isort:skip # noqa
- from ..frames import prepare_data # isort:skip # noqa
- from ..frames import serialize_close # isort:skip # noqa
- # at the bottom to allow circular import, because Extension depends on Frame
- from .. import extensions # isort:skip # noqa
|