mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-29 11:03:13 +00:00
Sketch out addons.View
The first iteration of a replacement for addons.State
This commit is contained in:
parent
9be34baa40
commit
69bacee1d8
124
mitmproxy/addons/view.py
Normal file
124
mitmproxy/addons/view.py
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
"""
|
||||||
|
The View:
|
||||||
|
|
||||||
|
- Keeps track of a store of flows
|
||||||
|
- Maintains a filtered, ordered view onto that list of flows
|
||||||
|
- Exposes various operations on flows in the store - notably intercept and
|
||||||
|
resume
|
||||||
|
- Exposes a number of signals so the view can be monitored
|
||||||
|
"""
|
||||||
|
import collections
|
||||||
|
import typing
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
import blinker
|
||||||
|
import sortedcontainers
|
||||||
|
|
||||||
|
from mitmproxy import flow
|
||||||
|
from mitmproxy import flowfilter
|
||||||
|
|
||||||
|
|
||||||
|
def key_request_start(f: flow.Flow) -> datetime.datetime:
|
||||||
|
return f.request.timestamp_start or 0
|
||||||
|
|
||||||
|
|
||||||
|
def key_request_method(f: flow.Flow) -> str:
|
||||||
|
return f.request.method
|
||||||
|
|
||||||
|
|
||||||
|
matchall = flowfilter.parse(".")
|
||||||
|
|
||||||
|
|
||||||
|
class View(collections.Sequence):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self._store = {}
|
||||||
|
self.filter = matchall
|
||||||
|
self.order_key = key_request_start
|
||||||
|
self.order_reverse = False
|
||||||
|
self._view = sortedcontainers.SortedListWithKey(
|
||||||
|
key = self.order_key
|
||||||
|
)
|
||||||
|
|
||||||
|
# These signals broadcast events that affect the view. That is, an
|
||||||
|
# update to a flow in the store but not in the view does not trigger a
|
||||||
|
# signal.
|
||||||
|
self.sig_update = blinker.Signal()
|
||||||
|
self.sig_add = blinker.Signal()
|
||||||
|
self.sig_remove = blinker.Signal()
|
||||||
|
# Signals that the view should be refreshed completely
|
||||||
|
self.sig_refresh = blinker.Signal()
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._view)
|
||||||
|
|
||||||
|
def __getitem__(self, offset) -> flow.Flow:
|
||||||
|
if self.order_reverse:
|
||||||
|
offset = -offset - 1
|
||||||
|
return self._view[offset]
|
||||||
|
|
||||||
|
def set_order(self, order_key: typing.Callable):
|
||||||
|
"""
|
||||||
|
Sets the current view order.
|
||||||
|
"""
|
||||||
|
self.order_key = order_key
|
||||||
|
newview = sortedcontainers.SortedListWithKey(key=order_key)
|
||||||
|
newview.update(self._view)
|
||||||
|
self._view = newview
|
||||||
|
|
||||||
|
def set_filter(self, flt: typing.Optional[flowfilter.TFilter]):
|
||||||
|
"""
|
||||||
|
Sets the current view filter.
|
||||||
|
"""
|
||||||
|
self.filter = flt or matchall
|
||||||
|
self._view.clear()
|
||||||
|
for i in self._store.values():
|
||||||
|
if self.filter(i):
|
||||||
|
self._view.add(i)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
"""
|
||||||
|
Clears both the state and view.
|
||||||
|
"""
|
||||||
|
self._state.clear()
|
||||||
|
self._view.clear()
|
||||||
|
|
||||||
|
def add(self, f: flow.Flow):
|
||||||
|
"""
|
||||||
|
Adds a flow to the state. If the flow already exists, it is
|
||||||
|
ignored.
|
||||||
|
"""
|
||||||
|
if f.id not in self._store:
|
||||||
|
self._store[f.id] = f
|
||||||
|
if self.filter(f):
|
||||||
|
self._view.add(f)
|
||||||
|
|
||||||
|
def update(self, f: flow.Flow):
|
||||||
|
"""
|
||||||
|
Updates a flow. If the flow is not in the state, it's ignored.
|
||||||
|
"""
|
||||||
|
if f.id in self._store:
|
||||||
|
if self.filter(f):
|
||||||
|
if f not in self._view:
|
||||||
|
self._view.add(f)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self._view.remove(f)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Event handlers
|
||||||
|
def request(self, f):
|
||||||
|
self.add(f)
|
||||||
|
|
||||||
|
def intercept(self, f):
|
||||||
|
self.update(f)
|
||||||
|
|
||||||
|
def resume(self, f):
|
||||||
|
self.update(f)
|
||||||
|
|
||||||
|
def error(self, f):
|
||||||
|
self.update(f)
|
||||||
|
|
||||||
|
def response(self, f):
|
||||||
|
self.update(f)
|
1
setup.py
1
setup.py
@ -83,6 +83,7 @@ setup(
|
|||||||
"urwid>=1.3.1, <1.4",
|
"urwid>=1.3.1, <1.4",
|
||||||
"watchdog>=0.8.3, <0.9",
|
"watchdog>=0.8.3, <0.9",
|
||||||
"brotlipy>=0.5.1, <0.7",
|
"brotlipy>=0.5.1, <0.7",
|
||||||
|
"sortedcontainers>=1.5.4, <1.6",
|
||||||
],
|
],
|
||||||
extras_require={
|
extras_require={
|
||||||
':sys_platform == "win32"': [
|
':sys_platform == "win32"': [
|
||||||
|
92
test/mitmproxy/addons/test_view.py
Normal file
92
test/mitmproxy/addons/test_view.py
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
from mitmproxy.addons import view
|
||||||
|
from mitmproxy import flowfilter
|
||||||
|
from .. import tutils
|
||||||
|
|
||||||
|
|
||||||
|
def test_simple():
|
||||||
|
v = view.View()
|
||||||
|
f = tutils.tflow()
|
||||||
|
f.request.timestamp_start = 1
|
||||||
|
v.request(f)
|
||||||
|
assert list(v) == [f]
|
||||||
|
v.request(f)
|
||||||
|
assert list(v) == [f]
|
||||||
|
assert len(v._store) == 1
|
||||||
|
|
||||||
|
f2 = tutils.tflow()
|
||||||
|
f2.request.timestamp_start = 3
|
||||||
|
v.request(f2)
|
||||||
|
assert list(v) == [f, f2]
|
||||||
|
v.request(f2)
|
||||||
|
assert list(v) == [f, f2]
|
||||||
|
assert len(v._store) == 2
|
||||||
|
|
||||||
|
f3 = tutils.tflow()
|
||||||
|
f3.request.timestamp_start = 2
|
||||||
|
v.request(f3)
|
||||||
|
assert list(v) == [f, f3, f2]
|
||||||
|
v.request(f3)
|
||||||
|
assert list(v) == [f, f3, f2]
|
||||||
|
assert len(v._store) == 3
|
||||||
|
|
||||||
|
|
||||||
|
def tft(*, method="get", start=0):
|
||||||
|
f = tutils.tflow()
|
||||||
|
f.request.method = method
|
||||||
|
f.request.timestamp_start = start
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter():
|
||||||
|
v = view.View()
|
||||||
|
f = flowfilter.parse("~m get")
|
||||||
|
v.request(tft(method="get"))
|
||||||
|
v.request(tft(method="put"))
|
||||||
|
v.request(tft(method="get"))
|
||||||
|
v.request(tft(method="put"))
|
||||||
|
assert(len(v)) == 4
|
||||||
|
v.set_filter(f)
|
||||||
|
assert [i.request.method for i in v] == ["GET", "GET"]
|
||||||
|
assert len(v._store) == 4
|
||||||
|
v.set_filter(None)
|
||||||
|
|
||||||
|
|
||||||
|
def test_order():
|
||||||
|
v = view.View()
|
||||||
|
v.request(tft(method="get", start=1))
|
||||||
|
v.request(tft(method="put", start=2))
|
||||||
|
v.request(tft(method="get", start=3))
|
||||||
|
v.request(tft(method="put", start=4))
|
||||||
|
assert [i.request.timestamp_start for i in v] == [1, 2, 3, 4]
|
||||||
|
|
||||||
|
v.set_order(view.key_request_method)
|
||||||
|
assert [i.request.method for i in v] == ["GET", "GET", "PUT", "PUT"]
|
||||||
|
v.order_reverse = True
|
||||||
|
assert [i.request.method for i in v] == ["PUT", "PUT", "GET", "GET"]
|
||||||
|
|
||||||
|
v.set_order(view.key_request_start)
|
||||||
|
assert [i.request.timestamp_start for i in v] == [4, 3, 2, 1]
|
||||||
|
|
||||||
|
v.order_reverse = False
|
||||||
|
assert [i.request.timestamp_start for i in v] == [1, 2, 3, 4]
|
||||||
|
|
||||||
|
|
||||||
|
def test_update():
|
||||||
|
v = view.View()
|
||||||
|
flt = flowfilter.parse("~m get")
|
||||||
|
v.set_filter(flt)
|
||||||
|
|
||||||
|
f = tft(method="get")
|
||||||
|
v.request(f)
|
||||||
|
assert f in v
|
||||||
|
|
||||||
|
f.request.method = "put"
|
||||||
|
v.update(f)
|
||||||
|
assert f not in v
|
||||||
|
|
||||||
|
f.request.method = "get"
|
||||||
|
v.update(f)
|
||||||
|
assert f in v
|
||||||
|
|
||||||
|
v.update(f)
|
||||||
|
assert f in v
|
Loading…
Reference in New Issue
Block a user