mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-23 00:01:36 +00:00
Improve responsiveness of request and response viewing.
- Computing the view of a large body is expensive, so we introduce an LRU cache to hold the latest 20 results. - Use ListView more correctly, passing it individual urwid.Text snippets, rather than a single large one. This hugely improves render time.
This commit is contained in:
parent
563d4161f1
commit
fe1e2f16ff
@ -289,71 +289,31 @@ class ConnectionView(WWrap):
|
||||
)
|
||||
return f
|
||||
|
||||
def _view_normal(self, conn, txt):
|
||||
for i in conn.content.splitlines():
|
||||
txt.append(
|
||||
("text", i),
|
||||
)
|
||||
txt.append(
|
||||
("text", "\n"),
|
||||
)
|
||||
|
||||
def _view_binary(self, conn, txt):
|
||||
for offset, hex, s in utils.hexdump(conn.content):
|
||||
txt.extend([
|
||||
("offset", offset),
|
||||
" ",
|
||||
("text", hex),
|
||||
" ",
|
||||
("text", s),
|
||||
"\n"
|
||||
])
|
||||
|
||||
def _view_pretty(self, conn, txt):
|
||||
for i in utils.pretty_xmlish(conn.content):
|
||||
txt.append(
|
||||
("text", i),
|
||||
)
|
||||
txt.append(
|
||||
("text", "\n"),
|
||||
)
|
||||
|
||||
def _conn_text(self, conn):
|
||||
def _conn_text(self, conn, viewmode):
|
||||
if conn:
|
||||
txt = []
|
||||
txt.extend(
|
||||
format_keyvals(
|
||||
[(h+":", v) for (h, v) in sorted(conn.headers.itemPairs())],
|
||||
key = "header",
|
||||
val = "text"
|
||||
)
|
||||
)
|
||||
txt.append("\n\n")
|
||||
if conn.content:
|
||||
if self.state.view_body_mode == VIEW_BODY_BINARY:
|
||||
self._view_binary(conn, txt)
|
||||
elif self.state.view_body_mode == VIEW_BODY_INDENT:
|
||||
self.master.statusbar.update("Calculating pretty mode...")
|
||||
self._view_pretty(conn, txt)
|
||||
self.master.statusbar.update("")
|
||||
else:
|
||||
if utils.isBin(conn.content):
|
||||
self._view_binary(conn, txt)
|
||||
else:
|
||||
self._view_normal(conn, txt)
|
||||
return urwid.ListBox([urwid.Text(txt)])
|
||||
return self.master._cached_conn_text(conn.content, tuple(conn.headers.itemPairs()), viewmode)
|
||||
else:
|
||||
return urwid.ListBox([])
|
||||
|
||||
def view_request(self):
|
||||
self.state.view_flow_mode = VIEW_FLOW_REQUEST
|
||||
body = self._conn_text(self.flow.request)
|
||||
self.master.statusbar.update("Calculating view...")
|
||||
body = self._conn_text(
|
||||
self.flow.request,
|
||||
self.state.view_body_mode
|
||||
)
|
||||
self.w = self.wrap_body(VIEW_FLOW_REQUEST, body)
|
||||
self.master.statusbar.update("")
|
||||
|
||||
def view_response(self):
|
||||
self.state.view_flow_mode = VIEW_FLOW_RESPONSE
|
||||
body = self._conn_text(self.flow.response)
|
||||
self.master.statusbar.update("Calculating view...")
|
||||
body = self._conn_text(
|
||||
self.flow.response,
|
||||
self.state.view_body_mode
|
||||
)
|
||||
self.w = self.wrap_body(VIEW_FLOW_RESPONSE, body)
|
||||
self.master.statusbar.update("")
|
||||
|
||||
def refresh_connection(self, c=None):
|
||||
if c == self.flow:
|
||||
@ -729,11 +689,11 @@ class StatusBar(WWrap):
|
||||
),
|
||||
]), "statusbar")
|
||||
self.ib.set_w(status)
|
||||
self.master.drawscreen()
|
||||
|
||||
def update(self, text):
|
||||
self.helptext = text
|
||||
self.redraw()
|
||||
self.master.drawscreen()
|
||||
|
||||
def selectable(self):
|
||||
return True
|
||||
@ -896,6 +856,52 @@ class ConsoleMaster(flow.FlowMaster):
|
||||
if options.server_replay:
|
||||
self.server_playback_path(options.server_replay)
|
||||
|
||||
def _view_conn_normal(self, content, txt):
|
||||
for i in content.splitlines():
|
||||
txt.append(
|
||||
urwid.Text(("text", i))
|
||||
)
|
||||
|
||||
def _view_conn_binary(self, content, txt):
|
||||
for offset, hex, s in utils.hexdump(content):
|
||||
txt.append(urwid.Text([
|
||||
("offset", offset),
|
||||
" ",
|
||||
("text", hex),
|
||||
" ",
|
||||
("text", s),
|
||||
]))
|
||||
|
||||
def _view_conn_pretty(self, content, txt):
|
||||
for i in utils.pretty_xmlish(content):
|
||||
txt.append(
|
||||
urwid.Text(("text", i)),
|
||||
)
|
||||
|
||||
@utils.LRUCache(20)
|
||||
def _cached_conn_text(self, content, hdrItems, viewmode):
|
||||
hdr = []
|
||||
hdr.extend(
|
||||
format_keyvals(
|
||||
[(h+":", v) for (h, v) in sorted(hdrItems)],
|
||||
key = "header",
|
||||
val = "text"
|
||||
)
|
||||
)
|
||||
hdr.append("\n")
|
||||
|
||||
txt = [urwid.Text(hdr)]
|
||||
if content:
|
||||
if viewmode == VIEW_BODY_BINARY:
|
||||
self._view_conn_binary(content, txt)
|
||||
elif viewmode == VIEW_BODY_INDENT:
|
||||
self._view_conn_pretty(content, txt)
|
||||
else:
|
||||
if utils.isBin(content):
|
||||
self._view_conn_binary(content, txt)
|
||||
else:
|
||||
self._view_conn_normal(content, txt)
|
||||
return urwid.ListBox(txt)
|
||||
|
||||
def _readflow(self, path):
|
||||
path = os.path.expanduser(path)
|
||||
@ -1272,8 +1278,8 @@ class ConsoleMaster(flow.FlowMaster):
|
||||
slave.start()
|
||||
try:
|
||||
while not self._shutdown:
|
||||
size = self.drawscreen()
|
||||
self.statusbar.redraw()
|
||||
size = self.drawscreen()
|
||||
self.tick(q)
|
||||
self.ui.set_input_timeouts(max_wait=0.1)
|
||||
keys = self.ui.get_input()
|
||||
|
@ -303,6 +303,9 @@ class Response(controller.Msg):
|
||||
del i["expires"]
|
||||
return c.output(header="").strip()
|
||||
|
||||
def __hash__(self):
|
||||
return id(self)
|
||||
|
||||
def refresh(self, now=None):
|
||||
"""
|
||||
This fairly complex and heuristic function refreshes a server
|
||||
|
@ -12,7 +12,7 @@
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import re, os, subprocess, datetime, textwrap, errno, sys, time
|
||||
import re, os, subprocess, datetime, textwrap, errno, sys, time, functools
|
||||
|
||||
|
||||
def timestamp():
|
||||
@ -444,3 +444,41 @@ def dummy_cert(certdir, ca, commonname):
|
||||
)
|
||||
if ret: return None
|
||||
return certpath
|
||||
|
||||
|
||||
class LRUCache:
|
||||
"""
|
||||
A decorator that implements a self-expiring LRU cache for class
|
||||
methods (not functions!).
|
||||
|
||||
Cache data is tracked as attributes on the object itself. There is
|
||||
therefore a separate cache for each object instance.
|
||||
"""
|
||||
def __init__(self, size=100):
|
||||
self.size = size
|
||||
|
||||
def __call__(self, f):
|
||||
cacheName = "_cached_%s"%f.__name__
|
||||
cacheListName = "_cachelist_%s"%f.__name__
|
||||
size = self.size
|
||||
|
||||
@functools.wraps(f)
|
||||
def wrap(self, *args):
|
||||
if not hasattr(self, cacheName):
|
||||
setattr(self, cacheName, {})
|
||||
setattr(self, cacheListName, [])
|
||||
cache = getattr(self, cacheName)
|
||||
cacheList = getattr(self, cacheListName)
|
||||
if cache.has_key(args):
|
||||
cacheList.remove(args)
|
||||
cacheList.insert(0, args)
|
||||
return cache[args]
|
||||
else:
|
||||
ret = f(self, *args)
|
||||
cacheList.insert(0, args)
|
||||
cache[args] = ret
|
||||
if len(cacheList) > size:
|
||||
d = cacheList.pop()
|
||||
cache.pop(d)
|
||||
return ret
|
||||
return wrap
|
||||
|
@ -310,6 +310,36 @@ class udummy_cert(libpry.AutoTree):
|
||||
assert os.path.exists(os.path.join(d, "foo.com.pem"))
|
||||
|
||||
|
||||
class uLRUCache(libpry.AutoTree):
|
||||
def test_one(self):
|
||||
class Foo:
|
||||
ran = False
|
||||
@utils.LRUCache(2)
|
||||
def one(self, x):
|
||||
self.ran = True
|
||||
return x
|
||||
|
||||
f = Foo()
|
||||
assert f.one(1) == 1
|
||||
assert f.ran
|
||||
f.ran = False
|
||||
assert f.one(1) == 1
|
||||
assert not f.ran
|
||||
|
||||
f.ran = False
|
||||
assert f.one(1) == 1
|
||||
assert not f.ran
|
||||
assert f.one(2) == 2
|
||||
assert f.one(3) == 3
|
||||
assert f.ran
|
||||
|
||||
f.ran = False
|
||||
assert f.one(1) == 1
|
||||
assert f.ran
|
||||
|
||||
assert len(f._cached_one) == 2
|
||||
assert len(f._cachelist_one) == 2
|
||||
|
||||
|
||||
tests = [
|
||||
uformat_timestamp(),
|
||||
@ -324,4 +354,5 @@ tests = [
|
||||
upretty_xmlish(),
|
||||
udummy_ca(),
|
||||
udummy_cert(),
|
||||
uLRUCache(),
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user