aboutsummaryrefslogtreecommitdiffstats
path: root/libmproxy
diff options
context:
space:
mode:
authorAldo Cortesi <aldo@nullcube.com>2011-03-15 13:05:33 +1300
committerAldo Cortesi <aldo@nullcube.com>2011-03-15 13:05:33 +1300
commitfe1e2f16ff61ceaaca65f52590a5d5a4b132d790 (patch)
tree54c537fbc56cfe4ed1e1b87f927e4d0ea7adeb05 /libmproxy
parent563d4161f120083eac79bf67cdddf28f9e9134c2 (diff)
downloadmitmproxy-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.py118
-rw-r--r--libmproxy/proxy.py3
-rw-r--r--libmproxy/utils.py40
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