codekingpro/portable-devtools
114k
1"""2wsproto/events3~~~~~~~~~~~~~~4 5Events that result from processing data on a WebSocket connection.6"""7from abc import ABC8from dataclasses import dataclass, field9from typing import Generic, List, Optional, Sequence, TypeVar, Union10 11from .extensions import Extension12from .typing import Headers13 14 15class Event(ABC):16 """17 Base class for wsproto events.18 """19 20 pass # noqa21 22 23@dataclass(frozen=True)24class Request(Event):25 """The beginning of a Websocket connection, the HTTP Upgrade request26 27 This event is fired when a SERVER connection receives a WebSocket28 handshake request (HTTP with upgrade header).29 30 Fields:31 32 .. attribute:: host33 34 (Required) The hostname, or host header value.35 36 .. attribute:: target37 38 (Required) The request target (path and query string)39 40 .. attribute:: extensions41 42 The proposed extensions.43 44 .. attribute:: extra_headers45 46 The additional request headers, excluding extensions, host, subprotocols,47 and version headers.48 49 .. attribute:: subprotocols50 51 A list of the subprotocols proposed in the request, as a list52 of strings.53 """54 55 host: str56 target: str57 extensions: Union[Sequence[Extension], Sequence[str]] = field( # type: ignore[assignment]58 default_factory=list59 )60 extra_headers: Headers = field(default_factory=list)61 subprotocols: List[str] = field(default_factory=list)62 63 64@dataclass(frozen=True)65class AcceptConnection(Event):66 """The acceptance of a Websocket upgrade request.67 68 This event is fired when a CLIENT receives an acceptance response69 from a server. It is also used to accept an upgrade request when70 acting as a SERVER.71 72 Fields:73 74 .. attribute:: extra_headers75 76 Any additional (non websocket related) headers present in the77 acceptance response.78 79 .. attribute:: subprotocol80 81 The accepted subprotocol to use.82 83 """84 85 subprotocol: Optional[str] = None86 extensions: List[Extension] = field(default_factory=list)87 extra_headers: Headers = field(default_factory=list)88 89 90@dataclass(frozen=True)91class RejectConnection(Event):92 """The rejection of a Websocket upgrade request, the HTTP response.93 94 The ``RejectConnection`` event sends the appropriate HTTP headers to95 communicate to the peer that the handshake has been rejected. You may also96 send an HTTP body by setting the ``has_body`` attribute to ``True`` and then97 sending one or more :class:`RejectData` events after this one. When sending98 a response body, the caller should set the ``Content-Length``,99 ``Content-Type``, and/or ``Transfer-Encoding`` headers as appropriate.100 101 When receiving a ``RejectConnection`` event, the ``has_body`` attribute will102 in almost all cases be ``True`` (even if the server set it to ``False``) and103 will be followed by at least one ``RejectData`` events, even though the data104 itself might be just ``b""``. (The only scenario in which the caller105 receives a ``RejectConnection`` with ``has_body == False`` is if the peer106 violates sends an informational status code (1xx) other than 101.)107 108 The ``has_body`` attribute should only be used when receiving the event. (It109 has ) is False the headers must include a110 content-length or transfer encoding.111 112 Fields:113 114 .. attribute:: headers (Headers)115 116 The headers to send with the response.117 118 .. attribute:: has_body119 120 This defaults to False, but set to True if there is a body. See121 also :class:`~RejectData`.122 123 .. attribute:: status_code124 125 The response status code.126 127 """128 129 status_code: int = 400130 headers: Headers = field(default_factory=list)131 has_body: bool = False132 133 134@dataclass(frozen=True)135class RejectData(Event):136 """The rejection HTTP response body.137 138 The caller may send multiple ``RejectData`` events. The final event should139 have the ``body_finished`` attribute set to ``True``.140 141 Fields:142 143 .. attribute:: body_finished144 145 True if this is the final chunk of the body data.146 147 .. attribute:: data (bytes)148 149 (Required) The raw body data.150 151 """152 153 data: bytes154 body_finished: bool = True155 156 157@dataclass(frozen=True)158class CloseConnection(Event):159 160 """The end of a Websocket connection, represents a closure frame.161 162 **wsproto does not automatically send a response to a close event.** To163 comply with the RFC you MUST send a close event back to the remote WebSocket164 if you have not already sent one. The :meth:`response` method provides a165 suitable event for this purpose, and you should check if a response needs166 to be sent by checking :func:`wsproto.WSConnection.state`.167 168 Fields:169 170 .. attribute:: code171 172 (Required) The integer close code to indicate why the connection173 has closed.174 175 .. attribute:: reason176 177 Additional reasoning for why the connection has closed.178 179 """180 181 code: int182 reason: Optional[str] = None183 184 def response(self) -> "CloseConnection":185 """Generate an RFC-compliant close frame to send back to the peer."""186 return CloseConnection(code=self.code, reason=self.reason)187 188 189T = TypeVar("T", bytes, str)190 191 192@dataclass(frozen=True)193class Message(Event, Generic[T]):194 """The websocket data message.195 196 Fields:197 198 .. attribute:: data199 200 (Required) The message data as byte string, can be decoded as UTF-8 for201 TEXT messages. This only represents a single chunk of data and202 not a full WebSocket message. You need to buffer and203 reassemble these chunks to get the full message.204 205 .. attribute:: frame_finished206 207 This has no semantic content, but is provided just in case some208 weird edge case user wants to be able to reconstruct the209 fragmentation pattern of the original stream.210 211 .. attribute:: message_finished212 213 True if this frame is the last one of this message, False if214 more frames are expected.215 216 """217 218 data: T219 frame_finished: bool = True220 message_finished: bool = True221 222 223@dataclass(frozen=True)224class TextMessage(Message[str]): # pylint: disable=unsubscriptable-object225 """This event is fired when a data frame with TEXT payload is received.226 227 Fields:228 229 .. attribute:: data230 231 The message data as string, This only represents a single chunk232 of data and not a full WebSocket message. You need to buffer233 and reassemble these chunks to get the full message.234 235 """236 237 # https://github.com/python/mypy/issues/5744238 data: str239 240 241@dataclass(frozen=True)242class BytesMessage(Message[bytes]): # pylint: disable=unsubscriptable-object243 """This event is fired when a data frame with BINARY payload is244 received.245 246 Fields:247 248 .. attribute:: data249 250 The message data as byte string, can be decoded as UTF-8 for251 TEXT messages. This only represents a single chunk of data and252 not a full WebSocket message. You need to buffer and253 reassemble these chunks to get the full message.254 """255 256 # https://github.com/python/mypy/issues/5744257 data: bytes258 259 260@dataclass(frozen=True)261class Ping(Event):262 """The Ping event can be sent to trigger a ping frame and is fired263 when a Ping is received.264 265 **wsproto does not automatically send a pong response to a ping event.** To266 comply with the RFC you MUST send a pong even as soon as is practical. The267 :meth:`response` method provides a suitable event for this purpose.268 269 Fields:270 271 .. attribute:: payload272 273 An optional payload to emit with the ping frame.274 """275 276 payload: bytes = b""277 278 def response(self) -> "Pong":279 """Generate an RFC-compliant :class:`Pong` response to this ping."""280 return Pong(payload=self.payload)281 282 283@dataclass(frozen=True)284class Pong(Event):285 """The Pong event is fired when a Pong is received.286 287 Fields:288 289 .. attribute:: payload290 291 An optional payload to emit with the pong frame.292 293 """294 295 payload: bytes = b""296 