add Flow.timestamp_created, which retains list order on replay (#5227)

This commit is contained in:
Maximilian Hils 2022-04-06 10:34:17 +02:00 committed by GitHub
parent 85e57a91e0
commit 6d67a405a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 40 additions and 16 deletions

View File

@ -2,6 +2,8 @@
## Unreleased: mitmproxy next ## Unreleased: mitmproxy next
* Replayed flows retain their current position in the flow list.
([#5227](https://github.com/mitmproxy/mitmproxy/issues/5227), @mhils)
* Console Performance Improvements * Console Performance Improvements
([#3427](https://github.com/mitmproxy/mitmproxy/issues/3427), @BkPHcgQL3V) ([#3427](https://github.com/mitmproxy/mitmproxy/issues/3427), @BkPHcgQL3V)
* Add flatpak support to the browser addon * Add flatpak support to the browser addon

View File

@ -74,7 +74,7 @@ class _OrderKey:
class OrderRequestStart(_OrderKey): class OrderRequestStart(_OrderKey):
def generate(self, f: mitmproxy.flow.Flow) -> float: def generate(self, f: mitmproxy.flow.Flow) -> float:
return f.timestamp_start return f.timestamp_created
class OrderRequestMethod(_OrderKey): class OrderRequestMethod(_OrderKey):

View File

@ -111,6 +111,13 @@ class Flow(stateobject.StateObject):
If `False`, the flow may have been already completed or loaded from disk. If `False`, the flow may have been already completed or loaded from disk.
""" """
timestamp_created: float
"""
The Unix timestamp of when this flow was created.
In contrast to `timestamp_start`, this value will not change when a flow is replayed.
"""
def __init__( def __init__(
self, self,
type: str, type: str,
@ -123,6 +130,7 @@ class Flow(stateobject.StateObject):
self.client_conn = client_conn self.client_conn = client_conn
self.server_conn = server_conn self.server_conn = server_conn
self.live = live self.live = live
self.timestamp_created = time.time()
self.intercepted: bool = False self.intercepted: bool = False
self._resume_event: typing.Optional[asyncio.Event] = None self._resume_event: typing.Optional[asyncio.Event] = None
@ -143,6 +151,7 @@ class Flow(stateobject.StateObject):
marked=str, marked=str,
metadata=typing.Dict[str, typing.Any], metadata=typing.Dict[str, typing.Any],
comment=str, comment=str,
timestamp_created=float,
) )
def get_state(self): def get_state(self):

View File

@ -330,6 +330,12 @@ def convert_14_15(data):
return data return data
def convert_15_16(data):
data["version"] = 16
data["timestamp_created"] = data.get("request", data["client_conn"])["timestamp_start"]
return data
def _convert_dict_keys(o: Any) -> Any: def _convert_dict_keys(o: Any) -> Any:
if isinstance(o, dict): if isinstance(o, dict):
return {strutils.always_str(k): _convert_dict_keys(v) for k, v in o.items()} return {strutils.always_str(k): _convert_dict_keys(v) for k, v in o.items()}
@ -392,6 +398,7 @@ converters = {
12: convert_12_13, 12: convert_12_13,
13: convert_13_14, 13: convert_13_14,
14: convert_14_15, 14: convert_14_15,
15: convert_15_16,
} }

View File

@ -24,6 +24,7 @@ def ttcpflow(client_conn=True, server_conn=True, messages=True, err=None) -> tcp
err = terr() err = terr()
f = tcp.TCPFlow(client_conn, server_conn) f = tcp.TCPFlow(client_conn, server_conn)
f.timestamp_created = client_conn.timestamp_start
f.messages = messages f.messages = messages
f.error = err f.error = err
f.live = True f.live = True
@ -115,6 +116,7 @@ def tflow(
assert ws is False or isinstance(ws, websocket.WebSocketData) assert ws is False or isinstance(ws, websocket.WebSocketData)
f = http.HTTPFlow(client_conn, server_conn) f = http.HTTPFlow(client_conn, server_conn)
f.timestamp_created = req.timestamp_start
f.request = req f.request = req
f.response = resp or None f.response = resp or None
f.error = err or None f.error = err or None

View File

@ -669,13 +669,13 @@ def format_flow(
for message in f.messages: for message in f.messages:
total_size += len(message.content) total_size += len(message.content)
if f.messages: if f.messages:
duration = f.messages[-1].timestamp - f.timestamp_start duration = f.messages[-1].timestamp - f.client_conn.timestamp_start
else: else:
duration = None duration = None
return format_tcp_flow( return format_tcp_flow(
render_mode=render_mode, render_mode=render_mode,
focused=focused, focused=focused,
timestamp_start=f.timestamp_start, timestamp_start=f.client_conn.timestamp_start,
marked=f.marked, marked=f.marked,
client_address=f.client_conn.peername, client_address=f.client_conn.peername,
server_address=f.server_conn.address, server_address=f.server_conn.address,

View File

@ -59,6 +59,7 @@ def flow_to_json(flow: mitmproxy.flow.Flow) -> dict:
"modified": flow.modified(), "modified": flow.modified(),
"marked": emoji.get(flow.marked, "🔴") if flow.marked else "", "marked": emoji.get(flow.marked, "🔴") if flow.marked else "",
"comment": flow.comment, "comment": flow.comment,
"timestamp_created": flow.timestamp_created,
} }
if flow.client_conn: if flow.client_conn:

View File

@ -7,7 +7,7 @@ MITMPROXY = "mitmproxy " + VERSION
# Serialization format version. This is displayed nowhere, it just needs to be incremented by one # Serialization format version. This is displayed nowhere, it just needs to be incremented by one
# for each change in the file format. # for each change in the file format.
FLOW_FORMAT_VERSION = 15 FLOW_FORMAT_VERSION = 16
def get_dev_version() -> str: def get_dev_version() -> str:

View File

@ -14,7 +14,7 @@ from mitmproxy.tools.console.common import render_marker, SYMBOL_MARK
def tft(*, method="get", start=0): def tft(*, method="get", start=0):
f = tflow.tflow() f = tflow.tflow()
f.request.method = method f.request.method = method
f.request.timestamp_start = start f.timestamp_created = start
return f return f
@ -31,7 +31,7 @@ def test_order_refresh():
with taddons.context() as tctx: with taddons.context() as tctx:
tctx.configure(v, view_order="time") tctx.configure(v, view_order="time")
v.add([tf]) v.add([tf])
tf.request.timestamp_start = 10 tf.timestamp_created = 10
assert not sargs assert not sargs
v.update([tf]) v.update([tf])
assert sargs assert sargs
@ -345,7 +345,7 @@ def test_order():
v.requestheaders(tft(method="put", start=2)) v.requestheaders(tft(method="put", start=2))
v.requestheaders(tft(method="get", start=3)) v.requestheaders(tft(method="get", start=3))
v.requestheaders(tft(method="put", start=4)) v.requestheaders(tft(method="put", start=4))
assert [i.request.timestamp_start for i in v] == [1, 2, 3, 4] assert [i.timestamp_created for i in v] == [1, 2, 3, 4]
v.set_order("method") v.set_order("method")
assert v.get_order() == "method" assert v.get_order() == "method"
@ -355,10 +355,10 @@ def test_order():
v.set_order("time") v.set_order("time")
assert v.get_order() == "time" assert v.get_order() == "time"
assert [i.request.timestamp_start for i in v] == [4, 3, 2, 1] assert [i.timestamp_created for i in v] == [4, 3, 2, 1]
v.set_reversed(False) v.set_reversed(False)
assert [i.request.timestamp_start for i in v] == [1, 2, 3, 4] assert [i.timestamp_created for i in v] == [1, 2, 3, 4]
with pytest.raises(exceptions.CommandError): with pytest.raises(exceptions.CommandError):
v.set_order("not_an_order") v.set_order("not_an_order")
@ -370,9 +370,9 @@ def test_reversed():
v.requestheaders(tft(start=3)) v.requestheaders(tft(start=3))
v.set_reversed(True) v.set_reversed(True)
assert v[0].request.timestamp_start == 3 assert v[0].timestamp_created == 3
assert v[-1].request.timestamp_start == 1 assert v[-1].timestamp_created == 1
assert v[2].request.timestamp_start == 1 assert v[2].timestamp_created == 1
with pytest.raises(IndexError): with pytest.raises(IndexError):
v[5] v[5]
with pytest.raises(IndexError): with pytest.raises(IndexError):
@ -485,21 +485,21 @@ def test_focus_follow():
v.add([tft(start=4)]) v.add([tft(start=4)])
assert v.focus.index == 0 assert v.focus.index == 0
assert v.focus.flow.request.timestamp_start == 4 assert v.focus.flow.timestamp_created == 4
v.add([tft(start=7)]) v.add([tft(start=7)])
assert v.focus.index == 2 assert v.focus.index == 2
assert v.focus.flow.request.timestamp_start == 7 assert v.focus.flow.timestamp_created == 7
mod = tft(method="put", start=6) mod = tft(method="put", start=6)
v.add([mod]) v.add([mod])
assert v.focus.index == 2 assert v.focus.index == 2
assert v.focus.flow.request.timestamp_start == 7 assert v.focus.flow.timestamp_created == 7
mod.request.method = "GET" mod.request.method = "GET"
v.update([mod]) v.update([mod])
assert v.focus.index == 2 assert v.focus.index == 2
assert v.focus.flow.request.timestamp_start == 6 assert v.focus.flow.timestamp_created == 6
def test_focus(): def test_focus():

View File

@ -145,6 +145,7 @@ export function THTTPFlow(): Required<HTTPFlow> {
"tls_established": true, "tls_established": true,
"tls_version": "TLSv1.2" "tls_version": "TLSv1.2"
}, },
"timestamp_created": 946681200,
"type": "http", "type": "http",
"websocket": { "websocket": {
"close_code": 1000, "close_code": 1000,
@ -221,6 +222,7 @@ export function TTCPFlow(): Required<TCPFlow> {
"tls_established": true, "tls_established": true,
"tls_version": "TLSv1.2" "tls_version": "TLSv1.2"
}, },
"timestamp_created": 946681200,
"type": "tcp" "type": "tcp"
} }
} }

View File

@ -9,6 +9,7 @@ interface _Flow {
modified: boolean modified: boolean
marked: string marked: string
comment: string comment: string
timestamp_created: number
client_conn: Client client_conn: Client
server_conn?: Server server_conn?: Server
error?: Error error?: Error