Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
connection.py1079 linesDownload Raw Back to sync
1from __future__ import annotations
2
3import contextlib
4import logging
5import random
6import socket
7import struct
8import threading
9import time
10import uuid
11from collections.abc import Iterable, Iterator, Mapping
12from types import TracebackType
13from typing import Any, Literal, overload
14
15from ..exceptions import (
16    ConcurrencyError,
17    ConnectionClosed,
18    ConnectionClosedOK,
19    ProtocolError,
20)
21from ..frames import DATA_OPCODES, CloseCode, Frame, Opcode
22from ..http11 import Request, Response
23from ..protocol import CLOSED, OPEN, Event, Protocol, State
24from ..typing import BytesLike, Data, DataLike, LoggerLike, Subprotocol
25from .messages import Assembler
26from .utils import Deadline
27
28
29__all__ = ["Connection"]
30
31
32class Connection:
33    """
34    :mod:`threading` implementation of a WebSocket connection.
35
36    :class:`Connection` provides APIs shared between WebSocket servers and
37    clients.
38
39    You shouldn't use it directly. Instead, use
40    :class:`~websockets.sync.client.ClientConnection` or
41    :class:`~websockets.sync.server.ServerConnection`.
42
43    """
44
45    recv_bufsize = 65536
46
47    def __init__(
48        self,
49        socket: socket.socket,
50        protocol: Protocol,
51        *,
52        ping_interval: float | None = 20,
53        ping_timeout: float | None = 20,
54        close_timeout: float | None = 10,
55        max_queue: int | None | tuple[int | None, int | None] = 16,
56    ) -> None:
57        self.socket = socket
58        self.protocol = protocol
59        self.ping_interval = ping_interval
60        self.ping_timeout = ping_timeout
61        self.close_timeout = close_timeout
62        if isinstance(max_queue, int) or max_queue is None:
63            max_queue_high, max_queue_low = max_queue, None
64        else:
65            max_queue_high, max_queue_low = max_queue
66
67        # Inject reference to this instance in the protocol's logger.
68        self.protocol.logger = logging.LoggerAdapter(
69            self.protocol.logger,
70            {"websocket": self},
71        )
72
73        # Copy attributes from the protocol for convenience.
74        self.id: uuid.UUID = self.protocol.id
75        """Unique identifier of the connection. Useful in logs."""
76        self.logger: LoggerLike = self.protocol.logger
77        """Logger for this connection."""
78        self.debug = self.protocol.debug
79
80        # HTTP handshake request and response.
81        self.request: Request | None = None
82        """Opening handshake request."""
83        self.response: Response | None = None
84        """Opening handshake response."""
85
86        # Mutex serializing interactions with the protocol.
87        self.protocol_mutex = threading.Lock()
88
89        # Lock stopping reads when the assembler buffer is full.
90        self.recv_flow_control = threading.Lock()
91
92        # Assembler turning frames into messages and serializing reads.
93        self.recv_messages = Assembler(
94            max_queue_high,
95            max_queue_low,
96            pause=self.recv_flow_control.acquire,
97            resume=self.recv_flow_control.release,
98        )
99
100        # Deadline for the closing handshake.
101        self.close_deadline: Deadline | None = None
102
103        # Whether we are busy sending a fragmented message.
104        self.send_in_progress = False
105
106        # Mapping of ping IDs to pong waiters, in chronological order.
107        self.pending_pings: dict[bytes, tuple[threading.Event, float, bool]] = {}
108
109        self.latency: float = 0.0
110        """
111        Latency of the connection, in seconds.
112
113        Latency is defined as the round-trip time of the connection. It is
114        measured by sending a Ping frame and waiting for a matching Pong frame.
115        Before the first measurement, :attr:`latency` is ``0.0``.
116
117        By default, websockets enables a :ref:`keepalive <keepalive>` mechanism
118        that sends Ping frames automatically at regular intervals. You can also
119        send Ping frames and measure latency with :meth:`ping`.
120        """
121
122        # Thread that sends keepalive pings. None when ping_interval is None.
123        self.keepalive_thread: threading.Thread | None = None
124
125        # Exception raised while reading from the connection, to be chained to
126        # ConnectionClosed in order to show why the TCP connection dropped.
127        self.recv_exc: BaseException | None = None
128
129        # Receiving events from the socket. This thread is marked as daemon to
130        # allow creating a connection in a non-daemon thread and using it in a
131        # daemon thread. This mustn't prevent the interpreter from exiting.
132        self.recv_events_thread = threading.Thread(
133            target=self.recv_events,
134            daemon=True,
135        )
136
137        # Start recv_events only after all attributes are initialized.
138        self.recv_events_thread.start()
139
140    # Public attributes
141
142    @property
143    def local_address(self) -> Any:
144        """
145        Local address of the connection.
146
147        For IPv4 connections, this is a ``(host, port)`` tuple.
148
149        The format of the address depends on the address family.
150        See :meth:`~socket.socket.getsockname`.
151
152        """
153        return self.socket.getsockname()
154
155    @property
156    def remote_address(self) -> Any:
157        """
158        Remote address of the connection.
159
160        For IPv4 connections, this is a ``(host, port)`` tuple.
161
162        The format of the address depends on the address family.
163        See :meth:`~socket.socket.getpeername`.
164
165        """
166        return self.socket.getpeername()
167
168    @property
169    def state(self) -> State:
170        """
171        State of the WebSocket connection, defined in :rfc:`6455`.
172
173        This attribute is provided for completeness. Typical applications
174        shouldn't check its value. Instead, they should call :meth:`~recv` or
175        :meth:`send` and handle :exc:`~websockets.exceptions.ConnectionClosed`
176        exceptions.
177
178        """
179        return self.protocol.state
180
181    @property
182    def subprotocol(self) -> Subprotocol | None:
183        """
184        Subprotocol negotiated during the opening handshake.
185
186        :obj:`None` if no subprotocol was negotiated.
187
188        """
189        return self.protocol.subprotocol
190
191    @property
192    def close_code(self) -> int | None:
193        """
194        State of the WebSocket connection, defined in :rfc:`6455`.
195
196        This attribute is provided for completeness. Typical applications
197        shouldn't check its value. Instead, they should inspect attributes
198        of :exc:`~websockets.exceptions.ConnectionClosed` exceptions.
199
200        """
201        return self.protocol.close_code
202
203    @property
204    def close_reason(self) -> str | None:
205        """
206        State of the WebSocket connection, defined in :rfc:`6455`.
207
208        This attribute is provided for completeness. Typical applications
209        shouldn't check its value. Instead, they should inspect attributes
210        of :exc:`~websockets.exceptions.ConnectionClosed` exceptions.
211
212        """
213        return self.protocol.close_reason
214
215    # Public methods
216
217    def __enter__(self) -> Connection:
218        return self
219
220    def __exit__(
221        self,
222        exc_type: type[BaseException] | None,
223        exc_value: BaseException | None,
224        traceback: TracebackType | None,
225    ) -> None:
226        if exc_type is None:
227            self.close()
228        else:
229            self.close(CloseCode.INTERNAL_ERROR)
230
231    def __iter__(self) -> Iterator[Data]:
232        """
233        Iterate on incoming messages.
234
235        The iterator calls :meth:`recv` and yields messages in an infinite loop.
236
237        It exits when the connection is closed normally. It raises a
238        :exc:`~websockets.exceptions.ConnectionClosedError` exception after a
239        protocol error or a network failure.
240
241        """
242        try:
243            while True:
244                yield self.recv()
245        except ConnectionClosedOK:
246            return
247
248    # This overload structure is required to avoid the error:
249    # "parameter without a default follows parameter with a default"
250
251    @overload
252    def recv(self, timeout: float | None, decode: Literal[True]) -> str: ...
253
254    @overload
255    def recv(self, timeout: float | None, decode: Literal[False]) -> bytes: ...
256
257    @overload
258    def recv(self, timeout: float | None = None, *, decode: Literal[True]) -> str: ...
259
260    @overload
261    def recv(
262        self, timeout: float | None = None, *, decode: Literal[False]
263    ) -> bytes: ...
264
265    @overload
266    def recv(
267        self, timeout: float | None = None, decode: bool | None = None
268    ) -> Data: ...
269
270    def recv(self, timeout: float | None = None, decode: bool | None = None) -> Data:
271        """
272        Receive the next message.
273
274        When the connection is closed, :meth:`recv` raises
275        :exc:`~websockets.exceptions.ConnectionClosed`. Specifically, it raises
276        :exc:`~websockets.exceptions.ConnectionClosedOK` after a normal closure
277        and :exc:`~websockets.exceptions.ConnectionClosedError` after a protocol
278        error or a network failure. This is how you detect the end of the
279        message stream.
280
281        If ``timeout`` is :obj:`None`, block until a message is received. If
282        ``timeout`` is set, wait up to ``timeout`` seconds for a message to be
283        received and return it, else raise :exc:`TimeoutError`. If ``timeout``
284        is ``0`` or negative, check if a message has been received already and
285        return it, else raise :exc:`TimeoutError`.
286
287        When the message is fragmented, :meth:`recv` waits until all fragments
288        are received, reassembles them, and returns the whole message.
289
290        Args:
291            timeout: Timeout for receiving a message in seconds.
292            decode: Set this flag to override the default behavior of returning
293                :class:`str` or :class:`bytes`. See below for details.
294
295        Returns:
296            A string (:class:`str`) for a Text_ frame or a bytestring
297            (:class:`bytes`) for a Binary_ frame.
298
299            .. _Text: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6
300            .. _Binary: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6
301
302            You may override this behavior with the ``decode`` argument:
303
304            * Set ``decode=False`` to disable UTF-8 decoding of Text_ frames and
305              return a bytestring (:class:`bytes`). This improves performance
306              when decoding isn't needed, for example if the message contains
307              JSON and you're using a JSON library that expects a bytestring.
308            * Set ``decode=True`` to force UTF-8 decoding of Binary_ frames and
309              return strings (:class:`str`). This may be useful for servers that
310              send binary frames instead of text frames.
311
312        Raises:
313            ConnectionClosed: When the connection is closed.
314            ConcurrencyError: If two threads call :meth:`recv` or
315                :meth:`recv_streaming` concurrently.
316
317        """
318        try:
319            return self.recv_messages.get(timeout, decode)
320        except EOFError:
321            pass
322            # fallthrough
323        except ConcurrencyError:
324            raise ConcurrencyError(
325                "cannot call recv while another thread "
326                "is already running recv or recv_streaming"
327            ) from None
328        except UnicodeDecodeError as exc:
329            with self.send_context():
330                self.protocol.fail(
331                    CloseCode.INVALID_DATA,
332                    f"{exc.reason} at position {exc.start}",
333                )
334            # fallthrough
335
336        # Wait for the protocol state to be CLOSED before accessing close_exc.
337        self.recv_events_thread.join()
338        raise self.protocol.close_exc from self.recv_exc
339
340    @overload
341    def recv_streaming(self, decode: Literal[True]) -> Iterator[str]: ...
342
343    @overload
344    def recv_streaming(self, decode: Literal[False]) -> Iterator[bytes]: ...
345
346    @overload
347    def recv_streaming(self, decode: bool | None = None) -> Iterator[Data]: ...
348
349    def recv_streaming(self, decode: bool | None = None) -> Iterator[Data]:
350        """
351        Receive the next message frame by frame.
352
353        This method is designed for receiving fragmented messages. It returns an
354        iterator that yields each fragment as it is received. This iterator must
355        be fully consumed. Else, future calls to :meth:`recv` or
356        :meth:`recv_streaming` will raise
357        :exc:`~websockets.exceptions.ConcurrencyError`, making the connection
358        unusable.
359
360        :meth:`recv_streaming` raises the same exceptions as :meth:`recv`.
361
362        Args:
363            decode: Set this flag to override the default behavior of returning
364                :class:`str` or :class:`bytes`. See below for details.
365
366        Returns:
367            An iterator of strings (:class:`str`) for a Text_ frame or
368            bytestrings (:class:`bytes`) for a Binary_ frame.
369
370            .. _Text: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6
371            .. _Binary: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6
372
373            You may override this behavior with the ``decode`` argument:
374
375            * Set ``decode=False`` to disable UTF-8 decoding of Text_ frames and
376              yield bytestrings (:class:`bytes`). This improves performance
377              when decoding isn't needed.
378            * Set ``decode=True`` to force UTF-8 decoding of Binary_ frames and
379              yield strings (:class:`str`). This may be useful for servers that
380              send binary frames instead of text frames.
381
382        Raises:
383            ConnectionClosed: When the connection is closed.
384            ConcurrencyError: If two threads call :meth:`recv` or
385                :meth:`recv_streaming` concurrently.
386
387        """
388        try:
389            yield from self.recv_messages.get_iter(decode)
390            return
391        except EOFError:
392            pass
393            # fallthrough
394        except ConcurrencyError:
395            raise ConcurrencyError(
396                "cannot call recv_streaming while another thread "
397                "is already running recv or recv_streaming"
398            ) from None
399        except UnicodeDecodeError as exc:
400            with self.send_context():
401                self.protocol.fail(
402                    CloseCode.INVALID_DATA,
403                    f"{exc.reason} at position {exc.start}",
404                )
405            # fallthrough
406
407        # Wait for the protocol state to be CLOSED before accessing close_exc.
408        self.recv_events_thread.join()
409        raise self.protocol.close_exc from self.recv_exc
410
411    def send(
412        self,
413        message: DataLike | Iterable[DataLike],
414        text: bool | None = None,
415    ) -> None:
416        """
417        Send a message.
418
419        A string (:class:`str`) is sent as a Text_ frame. A bytestring or
420        bytes-like object (:class:`bytes`, :class:`bytearray`, or
421        :class:`memoryview`) is sent as a Binary_ frame.
422
423        .. _Text: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6
424        .. _Binary: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6
425
426        You may override this behavior with the ``text`` argument:
427
428        * Set ``text=True`` to send an UTF-8 bytestring or bytes-like object
429          (:class:`bytes`, :class:`bytearray`, or :class:`memoryview`) in a
430          Text_ frame. This improves performance when the message is already
431          UTF-8 encoded, for example if the message contains JSON and you're
432          using a JSON library that produces a bytestring.
433        * Set ``text=False`` to send a string (:class:`str`) in a Binary_
434          frame. This may be useful for servers that expect binary frames
435          instead of text frames.
436
437        :meth:`send` also accepts an iterable of strings, bytestrings, or
438        bytes-like objects to enable fragmentation_. Each item is treated as a
439        message fragment and sent in its own frame. All items must be of the
440        same type, or else :meth:`send` will raise a :exc:`TypeError` and the
441        connection will be closed.
442
443        .. _fragmentation: https://datatracker.ietf.org/doc/html/rfc6455#section-5.4
444
445        :meth:`send` rejects dict-like objects because this is often an error.
446        (If you really want to send the keys of a dict-like object as fragments,
447        call its :meth:`~dict.keys` method and pass the result to :meth:`send`.)
448
449        When the connection is closed, :meth:`send` raises
450        :exc:`~websockets.exceptions.ConnectionClosed`. Specifically, it
451        raises :exc:`~websockets.exceptions.ConnectionClosedOK` after a normal
452        connection closure and
453        :exc:`~websockets.exceptions.ConnectionClosedError` after a protocol
454        error or a network failure.
455
456        Args:
457            message: Message to send.
458
459        Raises:
460            ConnectionClosed: When the connection is closed.
461            ConcurrencyError: If the connection is sending a fragmented message.
462            TypeError: If ``message`` doesn't have a supported type.
463
464        """
465        # Unfragmented message -- this case must be handled first because
466        # strings and bytes-like objects are iterable.
467
468        if isinstance(message, str):
469            with self.send_context():
470                if self.send_in_progress:
471                    raise ConcurrencyError(
472                        "cannot call send while another thread is already running send"
473                    )
474                if text is False:
475                    self.protocol.send_binary(message.encode())
476                else:
477                    self.protocol.send_text(message.encode())
478
479        elif isinstance(message, BytesLike):
480            with self.send_context():
481                if self.send_in_progress:
482                    raise ConcurrencyError(
483                        "cannot call send while another thread is already running send"
484                    )
485                if text is True:
486                    self.protocol.send_text(message)
487                else:
488                    self.protocol.send_binary(message)
489
490        # Catch a common mistake -- passing a dict to send().
491
492        elif isinstance(message, Mapping):
493            raise TypeError("data is a dict-like object")
494
495        # Fragmented message -- regular iterator.
496
497        elif isinstance(message, Iterable):
498            chunks = iter(message)
499            try:
500                chunk = next(chunks)
501            except StopIteration:
502                return
503
504            try:
505                # First fragment.
506                if isinstance(chunk, str):
507                    with self.send_context():
508                        if self.send_in_progress:
509                            raise ConcurrencyError(
510                                "cannot call send while another thread "
511                                "is already running send"
512                            )
513                        self.send_in_progress = True
514                        if text is False:
515                            self.protocol.send_binary(chunk.encode(), fin=False)
516                        else:
517                            self.protocol.send_text(chunk.encode(), fin=False)
518                    encode = True
519                elif isinstance(chunk, BytesLike):
520                    with self.send_context():
521                        if self.send_in_progress:
522                            raise ConcurrencyError(
523                                "cannot call send while another thread "
524                                "is already running send"
525                            )
526                        self.send_in_progress = True
527                        if text is True:
528                            self.protocol.send_text(chunk, fin=False)
529                        else:
530                            self.protocol.send_binary(chunk, fin=False)
531                    encode = False
532                else:
533                    raise TypeError("iterable must contain bytes or str")
534
535                # Other fragments
536                for chunk in chunks:
537                    if isinstance(chunk, str) and encode:
538                        with self.send_context():
539                            assert self.send_in_progress
540                            self.protocol.send_continuation(chunk.encode(), fin=False)
541                    elif isinstance(chunk, BytesLike) and not encode:
542                        with self.send_context():
543                            assert self.send_in_progress
544                            self.protocol.send_continuation(chunk, fin=False)
545                    else:
546                        raise TypeError("iterable must contain uniform types")
547
548                # Final fragment.
549                with self.send_context():
550                    self.protocol.send_continuation(b"", fin=True)
551                    self.send_in_progress = False
552
553            except ConcurrencyError:
554                # We didn't start sending a fragmented message.
555                # The connection is still usable.
556                raise
557
558            except Exception:
559                # We're half-way through a fragmented message and we can't
560                # complete it. This makes the connection unusable.
561                with self.send_context():
562                    self.protocol.fail(
563                        CloseCode.INTERNAL_ERROR,
564                        "error in fragmented message",
565                    )
566                raise
567
568        else:
569            raise TypeError("data must be str, bytes, or iterable")
570
571    def close(
572        self,
573        code: CloseCode | int = CloseCode.NORMAL_CLOSURE,
574        reason: str = "",
575    ) -> None:
576        """
577        Perform the closing handshake.
578
579        :meth:`close` waits for the other end to complete the handshake and
580        for the TCP connection to terminate.
581
582        :meth:`close` is idempotent: it doesn't do anything once the
583        connection is closed.
584
585        Args:
586            code: WebSocket close code.
587            reason: WebSocket close reason.
588
589        """
590        try:
591            # The context manager takes care of waiting for the TCP connection
592            # to terminate after calling a method that sends a close frame.
593            with self.send_context():
594                if self.send_in_progress:
595                    self.protocol.fail(
596                        CloseCode.INTERNAL_ERROR,
597                        "close during fragmented message",
598                    )
599                else:
600                    self.protocol.send_close(code, reason)
601        except ConnectionClosed:
602            # Ignore ConnectionClosed exceptions raised from send_context().
603            # They mean that the connection is closed, which was the goal.
604            pass
605
606    def ping(
607        self,
608        data: DataLike | None = None,
609        ack_on_close: bool = False,
610    ) -> threading.Event:
611        """
612        Send a Ping_.
613
614        .. _Ping: https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2
615
616        A ping may serve as a keepalive or as a check that the remote endpoint
617        received all messages up to this point
618
619        Args:
620            data: Payload of the ping. A :class:`str` will be encoded to UTF-8.
621                If ``data`` is :obj:`None`, the payload is four random bytes.
622            ack_on_close: when this option is :obj:`True`, the event will also
623                be set when the connection is closed. While this avoids getting
624                stuck waiting for a pong that will never arrive, it requires
625                checking that the state of the connection is still ``OPEN`` to
626                confirm that a pong was received, rather than the connection
627                being closed.
628
629        Returns:
630            An event that will be set when the corresponding pong is received.
631            You can ignore it if you don't intend to wait.
632
633            ::
634
635                pong_received = ws.ping()
636                # only if you want to wait for the corresponding pong
637                pong_received.wait()
638
639        Raises:
640            ConnectionClosed: When the connection is closed.
641            ConcurrencyError: If another ping was sent with the same data and
642                the corresponding pong wasn't received yet.
643
644        """
645        if isinstance(data, BytesLike):
646            data = bytes(data)
647        elif isinstance(data, str):
648            data = data.encode()
649        elif data is not None:
650            raise TypeError("data must be str or bytes-like")
651
652        with self.send_context():
653            # Protect against duplicates if a payload is explicitly set.
654            if data in self.pending_pings:
655                raise ConcurrencyError("already waiting for a pong with the same data")
656
657            # Generate a unique random payload otherwise.
658            while data is None or data in self.pending_pings:
659                data = struct.pack("!I", random.getrandbits(32))
660
661            pong_received = threading.Event()
662            ping_timestamp = time.monotonic()
663            self.pending_pings[data] = (pong_received, ping_timestamp, ack_on_close)
664            self.protocol.send_ping(data)
665            return pong_received
666
667    def pong(self, data: DataLike = b"") -> None:
668        """
669        Send a Pong_.
670
671        .. _Pong: https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.3
672
673        An unsolicited pong may serve as a unidirectional heartbeat.
674
675        Args:
676            data: Payload of the pong. A :class:`str` will be encoded to UTF-8.
677
678        Raises:
679            ConnectionClosed: When the connection is closed.
680
681        """
682        if isinstance(data, BytesLike):
683            data = bytes(data)
684        elif isinstance(data, str):
685            data = data.encode()
686        else:
687            raise TypeError("data must be str or bytes-like")
688
689        with self.send_context():
690            self.protocol.send_pong(data)
691
692    # Private methods
693
694    def process_event(self, event: Event) -> None:
695        """
696        Process one incoming event.
697
698        This method is overridden in subclasses to handle the handshake.
699
700        """
701        assert isinstance(event, Frame)
702        if event.opcode in DATA_OPCODES:
703            self.recv_messages.put(event)
704
705        if event.opcode is Opcode.PONG:
706            self.acknowledge_pings(bytes(event.data))
707
708    def acknowledge_pings(self, data: bytes) -> None:
709        """
710        Acknowledge pings when receiving a pong.
711
712        """
713        with self.protocol_mutex:
714            # Ignore unsolicited pong.
715            if data not in self.pending_pings:
716                return
717
718            pong_timestamp = time.monotonic()
719
720            # Sending a pong for only the most recent ping is legal.
721            # Acknowledge all previous pings too in that case.
722            ping_id = None
723            ping_ids = []
724            for ping_id, (
725                pong_received,
726                ping_timestamp,
727                _ack_on_close,
728            ) in self.pending_pings.items():
729                ping_ids.append(ping_id)
730                pong_received.set()
731                if ping_id == data:
732                    self.latency = pong_timestamp - ping_timestamp
733                    break
734            else:
735                raise AssertionError("solicited pong not found in pings")
736
737            # Remove acknowledged pings from self.pending_pings.
738            for ping_id in ping_ids:
739                del self.pending_pings[ping_id]
740
741    def terminate_pending_pings(self) -> None:
742        """
743        Acknowledge pending pings when the connection is closed.
744
745        """
746        assert self.protocol_mutex.locked()
747        assert self.protocol.state is CLOSED
748
749        for pong_received, _ping_timestamp, ack_on_close in self.pending_pings.values():
750            if ack_on_close:
751                pong_received.set()
752
753        self.pending_pings.clear()
754
755    def keepalive(self) -> None:
756        """
757        Send a Ping frame and wait for a Pong frame at regular intervals.
758
759        """
760        assert self.ping_interval is not None
761        try:
762            while True:
763                # If self.ping_timeout > self.latency > self.ping_interval,
764                # pings will be sent immediately after receiving pongs.
765                # The period will be longer than self.ping_interval.
766                self.recv_events_thread.join(self.ping_interval - self.latency)
767                if not self.recv_events_thread.is_alive():
768                    break
769
770                try:
771                    pong_received = self.ping(ack_on_close=True)
772                except ConnectionClosed:
773                    break
774                if self.debug:
775                    self.logger.debug("% sent keepalive ping")
776
777                if self.ping_timeout is not None:
778                    if pong_received.wait(self.ping_timeout):
779                        if self.debug:
780                            self.logger.debug("% received keepalive pong")
781                    else:
782                        if self.debug:
783                            self.logger.debug("- timed out waiting for keepalive pong")
784                        with self.send_context():
785                            self.protocol.fail(
786                                CloseCode.INTERNAL_ERROR,
787                                "keepalive ping timeout",
788                            )
789                        break
790        except Exception:
791            self.logger.error("keepalive ping failed", exc_info=True)
792
793    def start_keepalive(self) -> None:
794        """
795        Run :meth:`keepalive` in a thread, unless keepalive is disabled.
796
797        """
798        if self.ping_interval is not None:
799            # This thread is marked as daemon like self.recv_events_thread.
800            self.keepalive_thread = threading.Thread(
801                target=self.keepalive,
802                daemon=True,
803            )
804            self.keepalive_thread.start()
805
806    def recv_events(self) -> None:
807        """
808        Read incoming data from the socket and process events.
809
810        Run this method in a thread as long as the connection is alive.
811
812        ``recv_events()`` exits immediately when ``self.socket`` is closed.
813
814        """
815        try:
816            while True:
817                try:
818                    # If the assembler buffer is full, block until it drains.
819                    with self.recv_flow_control:
820                        pass
821                    if self.close_deadline is not None:
822                        self.socket.settimeout(self.close_deadline.timeout())
823                    data = self.socket.recv(self.recv_bufsize)
824                except Exception as exc:
825                    if self.debug:
826                        self.logger.debug(
827                            "! error while receiving data",
828                            exc_info=True,
829                        )
830                    # When the closing handshake is initiated by our side,
831                    # recv() may block until send_context() closes the socket.
832                    # In that case, send_context() already set recv_exc.
833                    # Calling set_recv_exc() avoids overwriting it.
834                    with self.protocol_mutex:
835                        self.set_recv_exc(exc)
836                    break
837
838                if data == b"":
839                    break
840
841                # Acquire the connection lock.
842                with self.protocol_mutex:
843                    # Feed incoming data to the protocol.
844                    self.protocol.receive_data(data)
845
846                    # This isn't expected to raise an exception.
847                    events = self.protocol.events_received()
848
849                    # Write outgoing data to the socket.
850                    try:
851                        self.send_data()
852                    except Exception as exc:
853                        if self.debug:
854                            self.logger.debug(
855                                "! error while sending data",
856                                exc_info=True,
857                            )
858                        # Similarly to the above, avoid overriding an exception
859                        # set by send_context(), in case of a race condition
860                        # i.e. send_context() closes the socket after recv()
861                        # returns above but before send_data() calls send().
862                        self.set_recv_exc(exc)
863                        break
864
865                    # If needed, set the close deadline based on the close timeout.
866                    if self.protocol.close_expected():
867                        if self.close_deadline is None:
868                            self.close_deadline = Deadline(self.close_timeout)
869
870                # Unlock conn_mutex before processing events. Else, the
871                # application can't send messages in response to events.
872
873                # If self.send_data raised an exception, then events are lost.
874                # Given that automatic responses write small amounts of data,
875                # this should be uncommon, so we don't handle the edge case.
876
877                for event in events:
878                    # This isn't expected to raise an exception.
879                    self.process_event(event)
880
881            # Breaking out of the while True: ... loop means that we believe
882            # that the socket doesn't work anymore.
883
884            with self.protocol_mutex:
885                # Feed the end of the data stream to the protocol.
886                self.protocol.receive_eof()
887
888                # This isn't expected to raise an exception.
889                events = self.protocol.events_received()
890
891                # There is no error handling because send_data() can only write
892                # the end of the data stream and it handles errors by itself.
893                self.send_data()
894
895            # This code path is triggered when receiving an HTTP response
896            # without a Content-Length header. This is the only case where
897            # reading until EOF generates an event; all other events have
898            # a known length. Ignore for coverage measurement because tests
899            # are in test_client.py rather than test_connection.py.
900            for event in events:  # pragma: no cover
901                # This isn't expected to raise an exception.
902                self.process_event(event)
903
904        except Exception as exc:
905            # This branch should never run. It's a safety net in case of bugs.
906            self.logger.error("unexpected internal error", exc_info=True)
907            with self.protocol_mutex:
908                self.set_recv_exc(exc)
909        finally:
910            # This isn't expected to raise an exception.
911            self.close_socket()
912
913    @contextlib.contextmanager
914    def send_context(
915        self,
916        *,
917        expected_state: State = OPEN,  # CONNECTING during the opening handshake
918    ) -> Iterator[None]:
919        """
920        Create a context for writing to the connection from user code.
921
922        On entry, :meth:`send_context` acquires the connection lock and checks
923        that the connection is open; on exit, it writes outgoing data to the
924        socket and releases the connection lock::
925
926            with self.send_context():
927                self.protocol.send_text(message.encode())
928
929        When the connection isn't open on entry, when the connection is expected
930        to close on exit, or when an unexpected error happens, terminating the
931        connection, :meth:`send_context` waits until the connection is closed
932        then raises :exc:`~websockets.exceptions.ConnectionClosed`.
933
934        """
935        # Should we wait until the connection is closed?
936        wait_for_close = False
937        # Should we close the socket and raise ConnectionClosed?
938        raise_close_exc = False
939        # What exception should we chain ConnectionClosed to?
940        original_exc: BaseException | None = None
941
942        # Acquire the protocol lock.
943        with self.protocol_mutex:
944            if self.protocol.state is expected_state:
945                # Let the caller interact with the protocol.
946                try:
947                    yield
948                except (ProtocolError, ConcurrencyError):
949                    # The protocol state wasn't changed. Exit immediately.
950                    raise
951                except Exception as exc:
952                    self.logger.error("unexpected internal error", exc_info=True)
953                    # This branch should never run. It's a safety net in case of
954                    # bugs. Since we don't know what happened, we will close the
955                    # connection and raise the exception to the caller.
956                    wait_for_close = False
957                    raise_close_exc = True
958                    original_exc = exc
959                else:
960                    # Check if the connection is expected to close soon.
961                    if self.protocol.close_expected():
962                        wait_for_close = True
963                        # Set the close deadline based on the close timeout.
964                        # Since we tested earlier that protocol.state is OPEN
965                        # (or CONNECTING) and we didn't release protocol_mutex,
966                        # self.close_deadline is still None.
967                        assert self.close_deadline is None
968                        self.close_deadline = Deadline(self.close_timeout)
969                    # Write outgoing data to the socket.
970                    try:
971                        self.send_data()
972                    except Exception as exc:
973                        if self.debug:
974                            self.logger.debug(
975                                "! error while sending data",
976                                exc_info=True,
977                            )
978                        # While the only expected exception here is OSError,
979                        # other exceptions would be treated identically.
980                        wait_for_close = False
981                        raise_close_exc = True
982                        original_exc = exc
983
984            else:  # self.protocol.state is not expected_state
985                # Minor layering violation: we assume that the connection
986                # will be closing soon if it isn't in the expected state.
987                wait_for_close = True
988                # Calculate close_deadline if it wasn't set yet.
989                if self.close_deadline is None:
990                    self.close_deadline = Deadline(self.close_timeout)
991                raise_close_exc = True
992
993        # To avoid a deadlock, release the connection lock by exiting the
994        # context manager before waiting for recv_events() to terminate.
995
996        # If the connection is expected to close soon and the close timeout
997        # elapses, close the socket to terminate the connection.
998        if wait_for_close:
999            # Thread.join() returns immediately if timeout is negative.
1000            assert self.close_deadline is not None
1001            timeout = self.close_deadline.timeout(raise_if_elapsed=False)
1002            self.recv_events_thread.join(timeout)
1003            if self.recv_events_thread.is_alive():
1004                # There's no risk of overwriting another error because
1005                # original_exc is never set when wait_for_close is True.
1006                assert original_exc is None
1007                original_exc = TimeoutError("timed out while closing connection")
1008                # Set recv_exc before closing the socket in order to get
1009                # proper exception reporting.
1010                raise_close_exc = True
1011                with self.protocol_mutex:
1012                    self.set_recv_exc(original_exc)
1013
1014        # If an error occurred, close the socket to terminate the connection and
1015        # raise an exception.
1016        if raise_close_exc:
1017            self.close_socket()
1018            # Wait for the protocol state to be CLOSED before accessing close_exc.
1019            self.recv_events_thread.join()
1020            raise self.protocol.close_exc from original_exc
1021
1022    def send_data(self) -> None:
1023        """
1024        Send outgoing data.
1025
1026        This method requires holding protocol_mutex.
1027
1028        """
1029        assert self.protocol_mutex.locked()
1030        for data in self.protocol.data_to_send():
1031            if data:
1032                if self.close_deadline is not None:
1033                    self.socket.settimeout(self.close_deadline.timeout())
1034                self.socket.sendall(data)
1035            else:
1036                try:
1037                    self.socket.shutdown(socket.SHUT_WR)
1038                except OSError:  # socket already closed
1039                    pass
1040
1041    def set_recv_exc(self, exc: BaseException | None) -> None:
1042        """
1043        Set recv_exc, if not set yet.
1044
1045        This method requires holding protocol_mutex and must be called only from
1046        the thread running recv_events().
1047
1048        """
1049        assert self.protocol_mutex.locked()
1050        if self.recv_exc is None:
1051            self.recv_exc = exc
1052
1053    def close_socket(self) -> None:
1054        """
1055        Shutdown and close socket. Close message assembler.
1056
1057        Calling close_socket() guarantees that recv_events() terminates. Indeed,
1058        recv_events() may block only on socket.recv() or on recv_messages.put().
1059
1060        """
1061        # shutdown() is required to interrupt recv() on Linux.
1062        try:
1063            self.socket.shutdown(socket.SHUT_RDWR)
1064        except OSError:  # socket already closed
1065            pass
1066        self.socket.close()
1067
1068        # Calling protocol.receive_eof() is safe because it's idempotent.
1069        # This guarantees that the protocol state becomes CLOSED.
1070        with self.protocol_mutex:
1071            self.protocol.receive_eof()
1072            assert self.protocol.state is CLOSED
1073
1074            # Abort recv() with a ConnectionClosed exception.
1075            self.recv_messages.close()
1076
1077            # Acknowledge pings sent with the ack_on_close option.
1078            self.terminate_pending_pings()
1079 
codekingpro/portable-devtools · Team Ai