diff --git a/libmproxy/console.py b/libmproxy/console.py index f05b27e7f..b7e8f8796 100644 --- a/libmproxy/console.py +++ b/libmproxy/console.py @@ -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() diff --git a/libmproxy/proxy.py b/libmproxy/proxy.py index 5ec167d67..80040a035 100644 --- a/libmproxy/proxy.py +++ b/libmproxy/proxy.py @@ -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 diff --git a/libmproxy/utils.py b/libmproxy/utils.py index a64eca4e9..34c49e14c 100644 --- a/libmproxy/utils.py +++ b/libmproxy/utils.py @@ -12,7 +12,7 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -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 diff --git a/test/test_utils.py b/test/test_utils.py index c7d4e03f0..434d6b267 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -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(), ]