Edit on GitHub

mitmproxy.websocket

Mitmproxy used to have its own WebSocketFlow type until mitmproxy 6, but now WebSocket connections now are represented as HTTP flows as well. They can be distinguished from regular HTTP requests by having the mitmproxy.http.HTTPFlow.websocket attribute set.

This module only defines the classes for individual WebSocketMessages and the WebSocketData container.

  1"""
  2Mitmproxy used to have its own WebSocketFlow type until mitmproxy 6, but now WebSocket connections now are represented
  3as HTTP flows as well. They can be distinguished from regular HTTP requests by having the
  4`mitmproxy.http.HTTPFlow.websocket` attribute set.
  5
  6This module only defines the classes for individual `WebSocketMessage`s and the `WebSocketData` container.
  7"""
  8import time
  9import warnings
 10from typing import Union
 11from typing import Optional
 12
 13from mitmproxy import stateobject
 14from mitmproxy.coretypes import serializable
 15from wsproto.frame_protocol import Opcode
 16
 17WebSocketMessageState = tuple[int, bool, bytes, float, bool, bool]
 18
 19
 20class WebSocketMessage(serializable.Serializable):
 21    """
 22    A single WebSocket message sent from one peer to the other.
 23
 24    Fragmented WebSocket messages are reassembled by mitmproxy and then
 25    represented as a single instance of this class.
 26
 27    The [WebSocket RFC](https://tools.ietf.org/html/rfc6455) specifies both
 28    text and binary messages. To avoid a whole class of nasty type confusion bugs,
 29    mitmproxy stores all message contents as `bytes`. If you need a `str`, you can access the `text` property
 30    on text messages:
 31
 32    >>> if message.is_text:
 33    >>>     text = message.text
 34    """
 35
 36    from_client: bool
 37    """True if this messages was sent by the client."""
 38    type: Opcode
 39    """
 40    The message type, as per RFC 6455's [opcode](https://tools.ietf.org/html/rfc6455#section-5.2).
 41
 42    Mitmproxy currently only exposes messages assembled from `TEXT` and `BINARY` frames.
 43    """
 44    content: bytes
 45    """A byte-string representing the content of this message."""
 46    timestamp: float
 47    """Timestamp of when this message was received or created."""
 48    dropped: bool
 49    """True if the message has not been forwarded by mitmproxy, False otherwise."""
 50    injected: bool
 51    """True if the message was injected and did not originate from a client/server, False otherwise"""
 52
 53    def __init__(
 54        self,
 55        type: Union[int, Opcode],
 56        from_client: bool,
 57        content: bytes,
 58        timestamp: Optional[float] = None,
 59        dropped: bool = False,
 60        injected: bool = False,
 61    ) -> None:
 62        self.from_client = from_client
 63        self.type = Opcode(type)
 64        self.content = content
 65        self.timestamp: float = timestamp or time.time()
 66        self.dropped = dropped
 67        self.injected = injected
 68
 69    @classmethod
 70    def from_state(cls, state: WebSocketMessageState):
 71        return cls(*state)
 72
 73    def get_state(self) -> WebSocketMessageState:
 74        return (
 75            int(self.type),
 76            self.from_client,
 77            self.content,
 78            self.timestamp,
 79            self.dropped,
 80            self.injected,
 81        )
 82
 83    def set_state(self, state: WebSocketMessageState) -> None:
 84        (
 85            typ,
 86            self.from_client,
 87            self.content,
 88            self.timestamp,
 89            self.dropped,
 90            self.injected,
 91        ) = state
 92        self.type = Opcode(typ)
 93
 94    def __repr__(self):
 95        if self.type == Opcode.TEXT:
 96            return repr(self.content.decode(errors="replace"))
 97        else:
 98            return repr(self.content)
 99
100    @property
101    def is_text(self) -> bool:
102        """
103        `True` if this message is assembled from WebSocket `TEXT` frames,
104        `False` if it is assembled from `BINARY` frames.
105        """
106        return self.type == Opcode.TEXT
107
108    def drop(self):
109        """Drop this message, i.e. don't forward it to the other peer."""
110        self.dropped = True
111
112    def kill(self):  # pragma: no cover
113        """A deprecated alias for `.drop()`."""
114        warnings.warn(
115            "WebSocketMessage.kill() is deprecated, use .drop() instead.",
116            DeprecationWarning,
117            stacklevel=2,
118        )
119        self.drop()
120
121    @property
122    def text(self) -> str:
123        """
124        The message content as text.
125
126        This attribute is only available if `WebSocketMessage.is_text` is `True`.
127
128        *See also:* `WebSocketMessage.content`
129        """
130        if self.type != Opcode.TEXT:
131            raise AttributeError(
132                f"{self.type.name.title()} WebSocket frames do not have a 'text' attribute."
133            )
134
135        return self.content.decode()
136
137    @text.setter
138    def text(self, value: str) -> None:
139        if self.type != Opcode.TEXT:
140            raise AttributeError(
141                f"{self.type.name.title()} WebSocket frames do not have a 'text' attribute."
142            )
143
144        self.content = value.encode()
145
146
147class WebSocketData(stateobject.StateObject):
148    """
149    A data container for everything related to a single WebSocket connection.
150    This is typically accessed as `mitmproxy.http.HTTPFlow.websocket`.
151    """
152
153    messages: list[WebSocketMessage]
154    """All `WebSocketMessage`s transferred over this connection."""
155
156    closed_by_client: Optional[bool] = None
157    """
158    `True` if the client closed the connection,
159    `False` if the server closed the connection,
160    `None` if the connection is active.
161    """
162    close_code: Optional[int] = None
163    """[Close Code](https://tools.ietf.org/html/rfc6455#section-7.1.5)"""
164    close_reason: Optional[str] = None
165    """[Close Reason](https://tools.ietf.org/html/rfc6455#section-7.1.6)"""
166
167    timestamp_end: Optional[float] = None
168    """*Timestamp:* WebSocket connection closed."""
169
170    _stateobject_attributes = dict(
171        messages=list[WebSocketMessage],
172        closed_by_client=bool,
173        close_code=int,
174        close_reason=str,
175        timestamp_end=float,
176    )
177
178    def __init__(self):
179        self.messages = []
180
181    def __repr__(self):
182        return f"<WebSocketData ({len(self.messages)} messages)>"
183
184    @classmethod
185    def from_state(cls, state):
186        d = WebSocketData()
187        d.set_state(state)
188        return d
class WebSocketMessage(mitmproxy.coretypes.serializable.Serializable):
 21class WebSocketMessage(serializable.Serializable):
 22    """
 23    A single WebSocket message sent from one peer to the other.
 24
 25    Fragmented WebSocket messages are reassembled by mitmproxy and then
 26    represented as a single instance of this class.
 27
 28    The [WebSocket RFC](https://tools.ietf.org/html/rfc6455) specifies both
 29    text and binary messages. To avoid a whole class of nasty type confusion bugs,
 30    mitmproxy stores all message contents as `bytes`. If you need a `str`, you can access the `text` property
 31    on text messages:
 32
 33    >>> if message.is_text:
 34    >>>     text = message.text
 35    """
 36
 37    from_client: bool
 38    """True if this messages was sent by the client."""
 39    type: Opcode
 40    """
 41    The message type, as per RFC 6455's [opcode](https://tools.ietf.org/html/rfc6455#section-5.2).
 42
 43    Mitmproxy currently only exposes messages assembled from `TEXT` and `BINARY` frames.
 44    """
 45    content: bytes
 46    """A byte-string representing the content of this message."""
 47    timestamp: float
 48    """Timestamp of when this message was received or created."""
 49    dropped: bool
 50    """True if the message has not been forwarded by mitmproxy, False otherwise."""
 51    injected: bool
 52    """True if the message was injected and did not originate from a client/server, False otherwise"""
 53
 54    def __init__(
 55        self,
 56        type: Union[int, Opcode],
 57        from_client: bool,
 58        content: bytes,
 59        timestamp: Optional[float] = None,
 60        dropped: bool = False,
 61        injected: bool = False,
 62    ) -> None:
 63        self.from_client = from_client
 64        self.type = Opcode(type)
 65        self.content = content
 66        self.timestamp: float = timestamp or time.time()
 67        self.dropped = dropped
 68        self.injected = injected
 69
 70    @classmethod
 71    def from_state(cls, state: WebSocketMessageState):
 72        return cls(*state)
 73
 74    def get_state(self) -> WebSocketMessageState:
 75        return (
 76            int(self.type),
 77            self.from_client,
 78            self.content,
 79            self.timestamp,
 80            self.dropped,
 81            self.injected,
 82        )
 83
 84    def set_state(self, state: WebSocketMessageState) -> None:
 85        (
 86            typ,
 87            self.from_client,
 88            self.content,
 89            self.timestamp,
 90            self.dropped,
 91            self.injected,
 92        ) = state
 93        self.type = Opcode(typ)
 94
 95    def __repr__(self):
 96        if self.type == Opcode.TEXT:
 97            return repr(self.content.decode(errors="replace"))
 98        else:
 99            return repr(self.content)
100
101    @property
102    def is_text(self) -> bool:
103        """
104        `True` if this message is assembled from WebSocket `TEXT` frames,
105        `False` if it is assembled from `BINARY` frames.
106        """
107        return self.type == Opcode.TEXT
108
109    def drop(self):
110        """Drop this message, i.e. don't forward it to the other peer."""
111        self.dropped = True
112
113    def kill(self):  # pragma: no cover
114        """A deprecated alias for `.drop()`."""
115        warnings.warn(
116            "WebSocketMessage.kill() is deprecated, use .drop() instead.",
117            DeprecationWarning,
118            stacklevel=2,
119        )
120        self.drop()
121
122    @property
123    def text(self) -> str:
124        """
125        The message content as text.
126
127        This attribute is only available if `WebSocketMessage.is_text` is `True`.
128
129        *See also:* `WebSocketMessage.content`
130        """
131        if self.type != Opcode.TEXT:
132            raise AttributeError(
133                f"{self.type.name.title()} WebSocket frames do not have a 'text' attribute."
134            )
135
136        return self.content.decode()
137
138    @text.setter
139    def text(self, value: str) -> None:
140        if self.type != Opcode.TEXT:
141            raise AttributeError(
142                f"{self.type.name.title()} WebSocket frames do not have a 'text' attribute."
143            )
144
145        self.content = value.encode()

A single WebSocket message sent from one peer to the other.

Fragmented WebSocket messages are reassembled by mitmproxy and then represented as a single instance of this class.

The WebSocket RFC specifies both text and binary messages. To avoid a whole class of nasty type confusion bugs, mitmproxy stores all message contents as bytes. If you need a str, you can access the text property on text messages:

>>> if message.is_text:
>>>     text = message.text
WebSocketMessage( type: Union[int, wsproto.frame_protocol.Opcode], from_client: bool, content: bytes, timestamp: Optional[float] = None, dropped: bool = False, injected: bool = False)
54    def __init__(
55        self,
56        type: Union[int, Opcode],
57        from_client: bool,
58        content: bytes,
59        timestamp: Optional[float] = None,
60        dropped: bool = False,
61        injected: bool = False,
62    ) -> None:
63        self.from_client = from_client
64        self.type = Opcode(type)
65        self.content = content
66        self.timestamp: float = timestamp or time.time()
67        self.dropped = dropped
68        self.injected = injected
from_client: bool

True if this messages was sent by the client.

content: bytes

A byte-string representing the content of this message.

timestamp: float

Timestamp of when this message was received or created.

dropped: bool

True if the message has not been forwarded by mitmproxy, False otherwise.

injected: bool

True if the message was injected and did not originate from a client/server, False otherwise

is_text: bool

True if this message is assembled from WebSocket TEXT frames, False if it is assembled from BINARY frames.

def drop(self):
109    def drop(self):
110        """Drop this message, i.e. don't forward it to the other peer."""
111        self.dropped = True

Drop this message, i.e. don't forward it to the other peer.

def kill(self):
113    def kill(self):  # pragma: no cover
114        """A deprecated alias for `.drop()`."""
115        warnings.warn(
116            "WebSocketMessage.kill() is deprecated, use .drop() instead.",
117            DeprecationWarning,
118            stacklevel=2,
119        )
120        self.drop()

A deprecated alias for .drop().

text: str

The message content as text.

This attribute is only available if WebSocketMessage.is_text is True.

See also: WebSocketMessage.content

Inherited Members
mitmproxy.coretypes.serializable.Serializable
copy
class WebSocketData(mitmproxy.stateobject.StateObject):
148class WebSocketData(stateobject.StateObject):
149    """
150    A data container for everything related to a single WebSocket connection.
151    This is typically accessed as `mitmproxy.http.HTTPFlow.websocket`.
152    """
153
154    messages: list[WebSocketMessage]
155    """All `WebSocketMessage`s transferred over this connection."""
156
157    closed_by_client: Optional[bool] = None
158    """
159    `True` if the client closed the connection,
160    `False` if the server closed the connection,
161    `None` if the connection is active.
162    """
163    close_code: Optional[int] = None
164    """[Close Code](https://tools.ietf.org/html/rfc6455#section-7.1.5)"""
165    close_reason: Optional[str] = None
166    """[Close Reason](https://tools.ietf.org/html/rfc6455#section-7.1.6)"""
167
168    timestamp_end: Optional[float] = None
169    """*Timestamp:* WebSocket connection closed."""
170
171    _stateobject_attributes = dict(
172        messages=list[WebSocketMessage],
173        closed_by_client=bool,
174        close_code=int,
175        close_reason=str,
176        timestamp_end=float,
177    )
178
179    def __init__(self):
180        self.messages = []
181
182    def __repr__(self):
183        return f"<WebSocketData ({len(self.messages)} messages)>"
184
185    @classmethod
186    def from_state(cls, state):
187        d = WebSocketData()
188        d.set_state(state)
189        return d

A data container for everything related to a single WebSocket connection. This is typically accessed as mitmproxy.http.HTTPFlow.websocket.

WebSocketData()
179    def __init__(self):
180        self.messages = []

All WebSocketMessages transferred over this connection.

closed_by_client: Optional[bool] = None

True if the client closed the connection, False if the server closed the connection, None if the connection is active.

close_code: Optional[int] = None
close_reason: Optional[str] = None
timestamp_end: Optional[float] = None

Timestamp: WebSocket connection closed.

Inherited Members
mitmproxy.coretypes.serializable.Serializable
copy