diff options
author | Aldo Cortesi <aldo@nullcube.com> | 2011-03-15 13:05:33 +1300 |
---|---|---|
committer | Aldo Cortesi <aldo@nullcube.com> | 2011-03-15 13:05:33 +1300 |
commit | fe1e2f16ff61ceaaca65f52590a5d5a4b132d790 (patch) | |
tree | 54c537fbc56cfe4ed1e1b87f927e4d0ea7adeb05 /libmproxy | |
parent | 563d4161f120083eac79bf67cdddf28f9e9134c2 (diff) | |
download | mitmproxy-fe1e2f16ff61ceaaca65f52590a5d5a4b132d790.tar.gz mitmproxy-fe1e2f16ff61ceaaca65f52590a5d5a4b132d790.tar.bz2 mitmproxy-fe1e2f16ff61ceaaca65f52590a5d5a4b132d790.zip |
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.
Diffstat (limited to 'libmproxy')
-rw-r--r-- | libmproxy/console.py | 118 | ||||
-rw-r--r-- | libmproxy/proxy.py | 3 | ||||
-rw-r--r-- | libmproxy/utils.py | 40 |
3 files changed, 104 insertions, 57 deletions
diff --git a/libmproxy/console.py b/libmproxy/console.py index f05b27e7..b7e8f879 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 5ec167d6..80040a03 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 a64eca4e..34c49e14 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 <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 |