# -*- coding: utf-8 -*-
"""
wsproto/events
~~~~~~~~~~~~~~
Events that result from processing data on a WebSocket connection.
"""
[docs]class Event(object):
"""
Base class for wsproto events.
"""
_fields = []
_defaults = {}
def __init__(self, **kwargs):
allowed = set(self._fields)
for kwarg in kwargs:
if kwarg not in allowed:
raise TypeError(
"unrecognized kwarg {} for {}".format(
kwarg, self.__class__.__name__
)
)
required = allowed.difference(self._defaults)
for field in required:
if field not in kwargs:
raise TypeError(
"missing required kwarg {} for {}".format(
field, self.__class__.__name__
)
)
defaults = {
key: value() if callable(value) else value
for key, value in self._defaults.items()
}
self.__dict__.update(defaults)
self.__dict__.update(kwargs)
def __repr__(self):
name = self.__class__.__name__
kwarg_strs = [
"{}={}".format(field, self.__dict__[field]) for field in self._fields
]
kwarg_str = ", ".join(kwarg_strs)
return "{}({})".format(name, kwarg_str)
def __eq__(self, other):
return self.__class__ == other.__class__ and self.__dict__ == other.__dict__
def __ne__(self, other):
return not self.__eq__(other)
# This is an unhashable type.
__hash__ = None
[docs]class Request(Event):
"""The beginning of a Websocket connection, the HTTP Upgrade request
This event is fired when a SERVER connection receives a WebSocket
handshake request (HTTP with upgrade header).
Fields:
.. attribute:: extensions (Union[List[Extension], List[str]])
.. attribute:: extra_headers
The additional request headers, excluding extensions, host, subprotocols,
and version headers.
.. attribute:: host (str)
The hostname, or host header value.
.. attribute:: subprotocols List[str]
A list of subprotocols ordered by preference.
.. attribute:: target (str)
A list of the subprotocols proposed in the request, as a list
of strings.
"""
_fields = ["extensions", "extra_headers", "host", "subprotocols", "target"]
_defaults = {"extensions": list, "extra_headers": list, "subprotocols": list}
[docs]class AcceptConnection(Event):
"""The acceptance of a Websocket upgrade request.
This event is fired when a CLIENT receives an acceptance response
from a server. It is also used to accept an upgrade request when
acting as a SERVER.
Fields:
.. attribute: extra_headers (List[Tuple[bytes, bytes]])
Any additional (non websocket related) headers present in the
acceptance response.
.. attribute: subprotocol (Optional[str])
The accepted subprotocol to use. Optional.
"""
_fields = ["extensions", "extra_headers", "subprotocol"]
_defaults = {"extensions": list, "extra_headers": list, "subprotocol": None}
[docs]class RejectConnection(Event):
"""The rejection of a Websocket upgrade request, the HTTP response.
This event is fired when a CLIENT receives a rejection response
from a server. It can be used to reject a request when sent from
as SERVER. If has_body is False the headers must include a
content-length or transfer encoding.
Fields:
.. attribute:: headers (List[Tuple[bytes, bytes]])
The headers to send with the response.
.. attribute:: has_body
This defaults to False, but set to True if there is a body. See
also :class:`~RejectData`.
.. attribute:: status_code
The response status code.
"""
_fields = ["headers", "has_body", "status_code"]
_defaults = {"headers": list, "has_body": False, "status_code": 400}
[docs]class RejectData(Event):
"""The rejection HTTP response body.
Fields:
.. attribute:: body_finished
True if this is the final chunk of the body data.
.. attribute:: data (bytes)
The raw body data.
"""
_fields = ["body_finished", "data"]
_defaults = {"body_finished": True}
[docs]class CloseConnection(Event):
"""The end of a Websocket connection, represents a closure frame.
This event is fired after the connection is considered closed.
wsproto automatically emits a CLOSE frame when it receives one, to
complete the close-handshake.
Fields:
.. attribute:: code
The integer close code to indicate why the connection
has closed.
.. attribute:: reason
Additional reasoning for why the connection has closed.
"""
_fields = ["code", "reason"]
_defaults = {"reason": None}
def response(self):
return CloseConnection(code=self.code, reason=self.reason)
[docs]class Message(Event):
"""The websocket data message.
Fields:
.. attribute:: data
The message data as byte string, can be decoded as UTF-8 for
TEXT messages. This only represents a single chunk of data and
not a full WebSocket message. You need to buffer and
reassemble these chunks to get the full message.
.. attribute:: frame_finished
This has no semantic content, but is provided just in case some
weird edge case user wants to be able to reconstruct the
fragmentation pattern of the original stream.
.. attribute:: message_finished
True if this frame is the last one of this message, False if
more frames are expected.
"""
_fields = ["data", "frame_finished", "message_finished"]
_defaults = {"frame_finished": True, "message_finished": True}
[docs]class TextMessage(Message):
"""This event is fired when a data frame with TEXT payload is received."""
pass
[docs]class BytesMessage(Message):
"""This event is fired when a data frame with BINARY payload is
received.
"""
pass
[docs]class Ping(Event):
"""The Ping event can be sent to trigger a ping frame and is fired
when a Ping is received.
wsproto automatically emits a PONG frame with the same payload.
Fields:
.. attribute:: payload
An optional payload to emit with the ping frame.
"""
_fields = ["payload"]
_defaults = {"payload": b""}
def response(self):
return Pong(payload=self.payload)
[docs]class Pong(Event):
"""The Pong event is fired when a Pong is received.
Fields:
.. attribute:: payload
An optional payload to emit with the pong frame.
"""
_fields = ["payload"]
_defaults = {"payload": b""}