Merge pull request #2728 from Kriechi/websocket++

websocket: docs++ and kill messages
This commit is contained in:
Thomas Kriechbaumer 2017-12-29 10:55:29 +01:00 committed by GitHub
commit 43c74ff1ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 68 additions and 15 deletions

View File

@ -10,6 +10,9 @@ API
- `mitmproxy.http.HTTPRequest <#mitmproxy.http.HTTPRequest>`_
- `mitmproxy.http.HTTPResponse <#mitmproxy.http.HTTPResponse>`_
- `mitmproxy.http.HTTPFlow <#mitmproxy.http.HTTPFlow>`_
- WebSocket
- `mitmproxy.websocket.WebSocketFlow <#mitmproxy.websocket.WebSocketFlow>`_
- `mitmproxy.websocket.WebSocketMessage <#mitmproxy.websocket.WebSocketMessage>`_
- Logging
- `mitmproxy.log.Log <#mitmproxy.controller.Log>`_
- `mitmproxy.log.LogEntry <#mitmproxy.controller.LogEntry>`_
@ -33,6 +36,15 @@ HTTP
.. autoclass:: mitmproxy.http.HTTPFlow
:inherited-members:
WebSocket
---------
.. autoclass:: mitmproxy.websocket.WebSocketFlow
:inherited-members:
.. autoclass:: mitmproxy.websocket.WebSocketMessage
:inherited-members:
Logging
--------

View File

@ -187,8 +187,8 @@ are issued, only new WebSocket messages are called.
- Called when a WebSocket message is received from the client or server. The
sender and receiver are identifiable. The most recent message will be
``flow.messages[-1]``. The message is user-modifiable. Currently there are
two types of messages, corresponding to the BINARY and TEXT frame types.
``flow.messages[-1]``. The message is user-modifiable and is killable.
A message is either of TEXT or BINARY type.
*flow*
A ``models.WebSocketFlow`` object.

View File

@ -109,7 +109,7 @@ class WebSocketLayer(base.Layer):
self.flow.messages.append(websocket_message)
self.channel.ask("websocket_message", self.flow)
if not self.flow.stream:
if not self.flow.stream and not websocket_message.killed:
def get_chunk(payload):
if len(payload) == length:
# message has the same length, we can reuse the same sizes
@ -129,14 +129,9 @@ class WebSocketLayer(base.Layer):
self.connections[other_conn].send_data(chunk, final)
other_conn.send(self.connections[other_conn].bytes_to_send())
else:
if self.flow.stream:
self.connections[other_conn].send_data(event.data, event.message_finished)
other_conn.send(self.connections[other_conn].bytes_to_send())
elif self.flow.stream:
self.connections[other_conn].send_data(event.data, event.message_finished)
other_conn.send(self.connections[other_conn].bytes_to_send())
return True
def _handle_ping_received(self, event, source_conn, other_conn, is_server):

View File

@ -10,23 +10,33 @@ from mitmproxy.utils import strutils, human
class WebSocketMessage(serializable.Serializable):
"""
A WebSocket message sent from one endpoint to the other.
"""
def __init__(
self, type: int, from_client: bool, content: bytes, timestamp: Optional[int]=None
self, type: int, from_client: bool, content: bytes, timestamp: Optional[int]=None, killed: bool=False
) -> None:
self.type = wsproto.frame_protocol.Opcode(type) # type: ignore
"""indicates either TEXT or BINARY (from wsproto.frame_protocol.Opcode)."""
self.from_client = from_client
"""True if this messages was sent by the client."""
self.content = content
"""A byte-string representing the content of this message."""
self.timestamp = timestamp or int(time.time()) # type: int
"""Timestamp of when this message was received or created."""
self.killed = killed
"""True if this messages was killed and should not be sent to the other endpoint."""
@classmethod
def from_state(cls, state):
return cls(*state)
def get_state(self):
return int(self.type), self.from_client, self.content, self.timestamp
return int(self.type), self.from_client, self.content, self.timestamp, self.killed
def set_state(self, state):
self.type, self.from_client, self.content, self.timestamp = state
self.type, self.from_client, self.content, self.timestamp, self.killed = state
self.type = wsproto.frame_protocol.Opcode(self.type) # replace enum with bare int
def __repr__(self):
@ -35,20 +45,37 @@ class WebSocketMessage(serializable.Serializable):
else:
return "binary message: {}".format(strutils.bytes_to_escaped_str(self.content))
def kill(self):
"""
Kill this message.
It will not be sent to the other endpoint. This has no effect in streaming mode.
"""
self.killed = True
class WebSocketFlow(flow.Flow):
"""
A WebsocketFlow is a simplified representation of a Websocket session.
A WebsocketFlow is a simplified representation of a Websocket connection.
"""
def __init__(self, client_conn, server_conn, handshake_flow, live=None):
super().__init__("websocket", client_conn, server_conn, live)
self.messages = [] # type: List[WebSocketMessage]
"""A list containing all WebSocketMessage's."""
self.close_sender = 'client'
"""'client' if the client initiated connection closing."""
self.close_code = wsproto.frame_protocol.CloseReason.NORMAL_CLOSURE
"""WebSocket close code."""
self.close_message = '(message missing)'
"""WebSocket close message."""
self.close_reason = 'unknown status code'
"""WebSocket close reason."""
self.stream = False
"""True of this connection is streaming directly to the other endpoint."""
self.handshake_flow = handshake_flow
"""The HTTP flow containing the initial WebSocket handshake."""
if handshake_flow:
self.client_key = websockets.get_client_key(handshake_flow.request.headers)
@ -65,8 +92,6 @@ class WebSocketFlow(flow.Flow):
self.server_protocol = ''
self.server_extensions = ''
self.handshake_flow = handshake_flow
_stateobject_attributes = flow.Flow._stateobject_attributes.copy()
# mypy doesn't support update with kwargs
_stateobject_attributes.update(dict(

View File

@ -3,6 +3,7 @@ import pytest
from mitmproxy.io import tnetstring
from mitmproxy import flowfilter
from mitmproxy.exceptions import Kill, ControlException
from mitmproxy.test import tflow
@ -42,6 +43,20 @@ class TestWebSocketFlow:
assert f.error.get_state() == f2.error.get_state()
assert f.error is not f2.error
def test_kill(self):
f = tflow.twebsocketflow()
with pytest.raises(ControlException):
f.intercept()
f.resume()
f.kill()
f = tflow.twebsocketflow()
f.intercept()
assert f.killable
f.kill()
assert not f.killable
assert f.reply.value == Kill
def test_match(self):
f = tflow.twebsocketflow()
assert not flowfilter.match("~b nonexistent", f)
@ -71,3 +86,9 @@ class TestWebSocketFlow:
d = tflow.twebsocketflow().handshake_flow.get_state()
tnetstring.dump(d, b)
assert b.getvalue()
def test_message_kill(self):
f = tflow.twebsocketflow()
assert not f.messages[-1].killed
f.messages[-1].kill()
assert f.messages[-1].killed