mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-23 00:01:36 +00:00
Revamp pathoc log output with a context handler
This does two things - it gives us a central place to put log utilities, and it lets us group together related log lines.
This commit is contained in:
parent
f927701e74
commit
fea3d8e421
@ -16,6 +16,43 @@ import language
|
||||
import utils
|
||||
|
||||
|
||||
class Log:
|
||||
def __init__(self, fp):
|
||||
self.lines = []
|
||||
self.fp = fp
|
||||
self.suppressed = False
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
if self.suppressed or not self.fp:
|
||||
return
|
||||
if exc_type == tcp.NetLibTimeout:
|
||||
self("Timeout")
|
||||
elif exc_type == tcp.NetLibDisconnect:
|
||||
self("Disconnect")
|
||||
elif exc_type == http.HttpError:
|
||||
self("HTTP Error: %s"%exc_value.message)
|
||||
self.fp.write("\n".join(self.lines))
|
||||
self.fp.write("\n")
|
||||
self.fp.flush()
|
||||
|
||||
def __call__(self, line):
|
||||
self.lines.append(line)
|
||||
|
||||
def suppress(self):
|
||||
self.suppressed = True
|
||||
|
||||
def dump(self, data, hexdump):
|
||||
if hexdump:
|
||||
for line in netlib.utils.hexdump(data):
|
||||
self("\t%s %s %s"%line)
|
||||
else:
|
||||
for i in netlib.utils.cleanBin(data).split("\n"):
|
||||
self("\t%s"%i)
|
||||
|
||||
|
||||
class PathocError(Exception):
|
||||
pass
|
||||
|
||||
@ -79,9 +116,10 @@ class Response:
|
||||
|
||||
|
||||
class WebsocketFrameReader(threading.Thread):
|
||||
def __init__(self, rfile, callback, ws_read_limit):
|
||||
def __init__(self, rfile, showresp, callback, ws_read_limit):
|
||||
threading.Thread.__init__(self)
|
||||
self.ws_read_limit = ws_read_limit
|
||||
self.showresp = showresp
|
||||
self.rfile, self.callback = rfile, callback
|
||||
self.terminate = Queue.Queue()
|
||||
self.is_done = Queue.Queue()
|
||||
@ -97,7 +135,12 @@ class WebsocketFrameReader(threading.Thread):
|
||||
except Queue.Empty:
|
||||
pass
|
||||
for rfile in r:
|
||||
print websockets.Frame.from_file(self.rfile).human_readable()
|
||||
if self.showresp:
|
||||
rfile.start_log()
|
||||
self.callback(
|
||||
websockets.Frame.from_file(self.rfile),
|
||||
rfile.get_log()
|
||||
)
|
||||
if self.ws_read_limit is not None:
|
||||
self.ws_read_limit -= 1
|
||||
self.is_done.put(None)
|
||||
@ -126,7 +169,7 @@ class Pathoc(tcp.TCPClient):
|
||||
ignorecodes = (),
|
||||
ignoretimeout = False,
|
||||
showsummary = False,
|
||||
fp = sys.stderr
|
||||
fp = sys.stdout
|
||||
):
|
||||
"""
|
||||
spec: A request specification
|
||||
@ -163,6 +206,9 @@ class Pathoc(tcp.TCPClient):
|
||||
|
||||
self.ws_framereader = None
|
||||
|
||||
def log(self):
|
||||
return Log(self.fp)
|
||||
|
||||
def http_connect(self, connect_to):
|
||||
self.wfile.write(
|
||||
'CONNECT %s:%s HTTP/1.1\r\n'%tuple(connect_to) +
|
||||
@ -205,20 +251,11 @@ class Pathoc(tcp.TCPClient):
|
||||
if showssl:
|
||||
print >> fp, str(self.sslinfo)
|
||||
|
||||
def _show_summary(self, fp, resp):
|
||||
print >> fp, "<< %s %s: %s bytes"%(
|
||||
def _resp_summary(self, resp):
|
||||
return "<< %s %s: %s bytes"%(
|
||||
resp.status_code, utils.xrepr(resp.msg), len(resp.content)
|
||||
)
|
||||
|
||||
def _show(self, fp, header, data, hexdump):
|
||||
if hexdump:
|
||||
print >> fp, "%s (hex dump):"%header
|
||||
for line in netlib.utils.hexdump(data):
|
||||
print >> fp, "\t%s %s %s"%line
|
||||
else:
|
||||
print >> fp, "%s (unprintables escaped):"%header
|
||||
print >> fp, netlib.utils.cleanBin(data)
|
||||
|
||||
def stop(self):
|
||||
self.ws_framereader.terminate.put(None)
|
||||
|
||||
@ -232,44 +269,45 @@ class Pathoc(tcp.TCPClient):
|
||||
except Queue.Empty:
|
||||
pass
|
||||
|
||||
def websocket_get_frame(self, frame):
|
||||
def websocket_get_frame(self, frame, wirelog):
|
||||
"""
|
||||
Called when a frame is received from the server.
|
||||
"""
|
||||
pass
|
||||
with self.log() as log:
|
||||
log("<< %s"%frame.header.human_readable())
|
||||
if self.showresp:
|
||||
log.dump(
|
||||
wirelog,
|
||||
self.hexdump
|
||||
)
|
||||
|
||||
def websocket_send_frame(self, r):
|
||||
"""
|
||||
Sends a single websocket frame.
|
||||
"""
|
||||
req = None
|
||||
if isinstance(r, basestring):
|
||||
r = language.parse_requests(r)[0]
|
||||
if self.showreq:
|
||||
self.wfile.start_log()
|
||||
try:
|
||||
req = language.serve(r, self.wfile, self.settings)
|
||||
self.wfile.flush()
|
||||
except tcp.NetLibTimeout:
|
||||
if self.ignoretimeout:
|
||||
return None
|
||||
if self.showsummary:
|
||||
print >> self.fp, "<<", "Timeout"
|
||||
raise
|
||||
except tcp.NetLibDisconnect: # pragma: nocover
|
||||
if self.showsummary:
|
||||
print >> self.fp, "<<", "Disconnect"
|
||||
raise
|
||||
finally:
|
||||
if req:
|
||||
if self.explain:
|
||||
print >> self.fp, ">> Spec:", r.spec()
|
||||
if self.showreq:
|
||||
self._show(
|
||||
self.fp, ">> Websocket Frame",
|
||||
self.wfile.get_log(),
|
||||
self.hexdump
|
||||
)
|
||||
with self.log() as log:
|
||||
req = None
|
||||
if isinstance(r, basestring):
|
||||
r = language.parse_requests(r)[0]
|
||||
log(">> %s"%r)
|
||||
|
||||
if self.showreq:
|
||||
self.wfile.start_log()
|
||||
try:
|
||||
req = language.serve(r, self.wfile, self.settings)
|
||||
self.wfile.flush()
|
||||
except tcp.NetLibTimeout:
|
||||
if self.ignoretimeout:
|
||||
self.log("Timeout (ignored)")
|
||||
return None
|
||||
raise
|
||||
finally:
|
||||
if req:
|
||||
if self.showreq:
|
||||
log.dump(
|
||||
self.wfile.get_log(),
|
||||
self.hexdump
|
||||
)
|
||||
|
||||
def websocket_start(self, r, callback=None, limit=None):
|
||||
"""
|
||||
@ -280,16 +318,17 @@ class Pathoc(tcp.TCPClient):
|
||||
server frame.
|
||||
limit: Disconnect after receiving N server frames.
|
||||
"""
|
||||
resp = self.http(r)
|
||||
if resp.status_code == 101:
|
||||
if self.showsummary:
|
||||
print >> self.fp, "<< websocket connection established..."
|
||||
self.ws_framereader = WebsocketFrameReader(
|
||||
self.rfile,
|
||||
self.websocket_get_frame,
|
||||
self.ws_read_limit
|
||||
)
|
||||
self.ws_framereader.start()
|
||||
with self.log() as log:
|
||||
resp = self.http(r)
|
||||
if resp.status_code == 101:
|
||||
log("starting websocket connection...")
|
||||
self.ws_framereader = WebsocketFrameReader(
|
||||
self.rfile,
|
||||
self.showresp,
|
||||
callback,
|
||||
self.ws_read_limit
|
||||
)
|
||||
self.ws_framereader.start()
|
||||
return resp
|
||||
|
||||
def http(self, r):
|
||||
@ -302,64 +341,49 @@ class Pathoc(tcp.TCPClient):
|
||||
|
||||
May raise http.HTTPError, tcp.NetLibError
|
||||
"""
|
||||
if isinstance(r, basestring):
|
||||
r = language.parse_requests(r)[0]
|
||||
resp, req = None, None
|
||||
if self.showreq:
|
||||
self.wfile.start_log()
|
||||
if self.showresp:
|
||||
self.rfile.start_log()
|
||||
try:
|
||||
req = language.serve(r, self.wfile, self.settings)
|
||||
self.wfile.flush()
|
||||
resp = list(
|
||||
http.read_response(
|
||||
self.rfile,
|
||||
req["method"],
|
||||
None
|
||||
with self.log() as log:
|
||||
if isinstance(r, basestring):
|
||||
r = language.parse_requests(r)[0]
|
||||
log(">> %s"%r)
|
||||
resp, req = None, None
|
||||
if self.showreq:
|
||||
self.wfile.start_log()
|
||||
if self.showresp:
|
||||
self.rfile.start_log()
|
||||
try:
|
||||
req = language.serve(r, self.wfile, self.settings)
|
||||
self.wfile.flush()
|
||||
resp = list(
|
||||
http.read_response(
|
||||
self.rfile,
|
||||
req["method"],
|
||||
None
|
||||
)
|
||||
)
|
||||
)
|
||||
resp.append(self.sslinfo)
|
||||
resp = Response(*resp)
|
||||
except http.HttpError, v:
|
||||
if self.showsummary:
|
||||
print >> self.fp, "<< HTTP Error:", v.message
|
||||
raise
|
||||
except tcp.NetLibTimeout:
|
||||
if self.ignoretimeout:
|
||||
return None
|
||||
if self.showsummary:
|
||||
print >> self.fp, "<<", "Timeout"
|
||||
raise
|
||||
except tcp.NetLibDisconnect: # pragma: nocover
|
||||
if self.showsummary:
|
||||
print >> self.fp, "<<", "Disconnect"
|
||||
raise
|
||||
finally:
|
||||
if req:
|
||||
if resp and resp.status_code in self.ignorecodes:
|
||||
resp = None
|
||||
else:
|
||||
if self.explain:
|
||||
print >> self.fp, ">> Spec:", r.spec()
|
||||
|
||||
resp.append(self.sslinfo)
|
||||
resp = Response(*resp)
|
||||
except tcp.NetLibTimeout:
|
||||
if self.ignoretimeout:
|
||||
log("Timeout (ignored)")
|
||||
return None
|
||||
raise
|
||||
finally:
|
||||
if req:
|
||||
if self.showreq:
|
||||
self._show(
|
||||
self.fp, ">> Request",
|
||||
log.dump(
|
||||
self.wfile.get_log(),
|
||||
self.hexdump
|
||||
)
|
||||
|
||||
if self.showsummary and resp:
|
||||
self._show_summary(self.fp, resp)
|
||||
if resp:
|
||||
log(self._resp_summary(resp))
|
||||
if resp.status_code in self.ignorecodes:
|
||||
log.suppress()
|
||||
if self.showresp:
|
||||
self._show(
|
||||
self.fp,
|
||||
"<< Response",
|
||||
log.dump(
|
||||
self.rfile.get_log(),
|
||||
self.hexdump
|
||||
)
|
||||
return resp
|
||||
return resp
|
||||
|
||||
def request(self, r):
|
||||
"""
|
||||
|
@ -36,7 +36,8 @@ class _TestDaemon:
|
||||
def test_info(self):
|
||||
c = pathoc.Pathoc(
|
||||
("127.0.0.1", self.d.port),
|
||||
ssl = self.ssl
|
||||
ssl = self.ssl,
|
||||
fp = None
|
||||
)
|
||||
c.connect()
|
||||
resp = c.request("get:/api/info")
|
||||
@ -77,7 +78,7 @@ class _TestDaemon:
|
||||
r = r.freeze(language.Settings())
|
||||
try:
|
||||
c.request(r)
|
||||
except (http.HttpError, tcp.NetLibError), v:
|
||||
except (http.HttpError, tcp.NetLibError):
|
||||
pass
|
||||
return s.getvalue()
|
||||
|
||||
@ -93,7 +94,8 @@ class TestDaemonSSL(_TestDaemon):
|
||||
c = pathoc.Pathoc(
|
||||
("127.0.0.1", self.d.port),
|
||||
ssl = True,
|
||||
sni = "foobar.com"
|
||||
sni = "foobar.com",
|
||||
fp = None
|
||||
)
|
||||
c.connect()
|
||||
c.request("get:/p/200")
|
||||
@ -108,7 +110,8 @@ class TestDaemonSSL(_TestDaemon):
|
||||
c = pathoc.Pathoc(
|
||||
("127.0.0.1", self.d.port),
|
||||
ssl = True,
|
||||
clientcert = tutils.test_data.path("data/clientcert/client.pem")
|
||||
clientcert = tutils.test_data.path("data/clientcert/client.pem"),
|
||||
fp = None
|
||||
)
|
||||
c.connect()
|
||||
c.request("get:/p/200")
|
||||
@ -121,7 +124,7 @@ class TestDaemon(_TestDaemon):
|
||||
ssl = False
|
||||
|
||||
def test_ssl_error(self):
|
||||
c = pathoc.Pathoc(("127.0.0.1", self.d.port), ssl = True)
|
||||
c = pathoc.Pathoc(("127.0.0.1", self.d.port), ssl = True, fp=None)
|
||||
tutils.raises("ssl handshake", c.connect)
|
||||
|
||||
def test_showssl(self):
|
||||
@ -143,8 +146,10 @@ class TestDaemon(_TestDaemon):
|
||||
def test_showresp(self):
|
||||
reqs = ["get:/api/info:p0,0", "get:/api/info:p0,0"]
|
||||
assert self.tval(reqs).count("200") == 2
|
||||
assert self.tval(reqs, showresp=True).count("unprintables escaped") == 2
|
||||
assert self.tval(reqs, showresp=True, hexdump=True).count("hex dump") == 2
|
||||
assert self.tval(reqs, showresp=True).count("HTTP/1.1 200 OK") == 2
|
||||
assert self.tval(
|
||||
reqs, showresp=True, hexdump=True
|
||||
).count("0000000000") == 2
|
||||
|
||||
def test_showresp_httperr(self):
|
||||
v = self.tval(["get:'/p/200:d20'"], showresp=True, showsummary=True)
|
||||
@ -157,15 +162,17 @@ class TestDaemon(_TestDaemon):
|
||||
|
||||
def test_showreq(self):
|
||||
reqs = ["get:/api/info:p0,0", "get:/api/info:p0,0"]
|
||||
assert self.tval(reqs, showreq=True).count("unprintables escaped") == 2
|
||||
assert self.tval(reqs, showreq=True, hexdump=True).count("hex dump") == 2
|
||||
assert self.tval(reqs, showreq=True).count("GET /api") == 2
|
||||
assert self.tval(
|
||||
reqs, showreq=True, hexdump=True
|
||||
).count("0000000000") == 2
|
||||
|
||||
def test_conn_err(self):
|
||||
assert "Invalid server response" in self.tval(["get:'/p/200:d2'"])
|
||||
|
||||
def test_connect_fail(self):
|
||||
to = ("foobar", 80)
|
||||
c = pathoc.Pathoc(("127.0.0.1", self.d.port))
|
||||
c = pathoc.Pathoc(("127.0.0.1", self.d.port), fp=None)
|
||||
c.rfile, c.wfile = cStringIO.StringIO(), cStringIO.StringIO()
|
||||
tutils.raises("connect failed", c.http_connect, to)
|
||||
c.rfile = cStringIO.StringIO(
|
||||
|
@ -77,7 +77,8 @@ class DaemonTests(object):
|
||||
c = pathoc.Pathoc(
|
||||
("localhost", self.d.port),
|
||||
ssl=ssl,
|
||||
ws_read_limit=ws_read_limit
|
||||
ws_read_limit=ws_read_limit,
|
||||
fp = None
|
||||
)
|
||||
c.connect(connect_to)
|
||||
if timeout:
|
||||
|
Loading…
Reference in New Issue
Block a user