Factor logger out of pathoc, use it in pathod as well.

This commit is contained in:
Aldo Cortesi 2015-05-30 17:43:01 +12:00
parent 4ed5043c67
commit a09f3e06c3
8 changed files with 213 additions and 211 deletions

57
libpathod/log.py Normal file
View File

@ -0,0 +1,57 @@
import netlib.utils
import netlib.tcp
import netlib.http
class Log:
def __init__(self, fp, hex, rfile, wfile):
self.lines = []
self.fp = fp
self.suppressed = False
self.hex = hex
self.rfile, self.wfile = rfile, wfile
def __enter__(self):
if self.wfile:
self.wfile.start_log()
if self.rfile:
self.rfile.start_log()
return self
def __exit__(self, exc_type, exc_value, traceback):
wlog = self.wfile.get_log() if self.wfile else None
rlog = self.rfile.get_log() if self.rfile else None
if self.suppressed or not self.fp:
return
if wlog:
self("Bytes written:")
self.dump(wlog, self.hex)
if rlog:
self("Bytes read:")
self.dump(rlog, self.hex)
if exc_type == netlib.tcp.NetLibTimeout:
self("Timeout")
elif exc_type in (
netlib.tcp.NetLibDisconnect,
netlib.http.HttpErrorConnClosed
):
self("Disconnected")
elif exc_type == netlib.http.HttpError:
self("HTTP Error: %s" % exc_value.message)
self.fp.write("\n".join(self.lines))
self.fp.write("\n")
self.fp.flush()
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)
def __call__(self, line):
self.lines.append(line)

View File

@ -10,68 +10,16 @@ import threading
import OpenSSL.crypto import OpenSSL.crypto
from netlib import tcp, http, certutils, websockets from netlib import tcp, http, certutils, websockets
import netlib.utils
import language.http import language.http
import language.websockets import language.websockets
import utils from . import utils, log
class PathocError(Exception): class PathocError(Exception):
pass pass
class Log:
def __init__(self, fp, hex, rfile, wfile):
self.lines = []
self.fp = fp
self.suppressed = False
self.hex = hex
self.rfile, self.wfile = rfile, wfile
def __enter__(self):
if self.wfile:
self.wfile.start_log()
if self.rfile:
self.rfile.start_log()
return self
def __exit__(self, exc_type, exc_value, traceback):
wlog = self.wfile.get_log() if self.wfile else None
rlog = self.rfile.get_log() if self.rfile else None
if self.suppressed or not self.fp:
return
if wlog:
self("Bytes written:")
self.dump(wlog, self.hex)
if rlog:
self("Bytes read:")
self.dump(rlog, self.hex)
if exc_type == tcp.NetLibTimeout:
self("Timeout")
elif exc_type in (tcp.NetLibDisconnect, http.HttpErrorConnClosed):
self("Disconnected")
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 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)
def __call__(self, line):
self.lines.append(line)
class SSLInfo: class SSLInfo:
def __init__(self, certchain, cipher): def __init__(self, certchain, cipher):
self.certchain, self.cipher = certchain, cipher self.certchain, self.cipher = certchain, cipher
@ -149,7 +97,7 @@ class WebsocketFrameReader(threading.Thread):
self.is_done = Queue.Queue() self.is_done = Queue.Queue()
def log(self, rfile): def log(self, rfile):
return Log( return log.Log(
self.logfp, self.logfp,
self.hexdump, self.hexdump,
rfile if self.showresp else None, rfile if self.showresp else None,
@ -242,7 +190,7 @@ class Pathoc(tcp.TCPClient):
self.ws_framereader = None self.ws_framereader = None
def log(self): def log(self):
return Log( return log.Log(
self.fp, self.fp,
self.hexdump, self.hexdump,
self.rfile if self.showresp else None, self.rfile if self.showresp else None,

View File

@ -7,7 +7,7 @@ import urllib
from netlib import tcp, http, wsgi, certutils, websockets from netlib import tcp, http, wsgi, certutils, websockets
import netlib.utils import netlib.utils
from . import version, app, language, utils from . import version, app, language, utils, log
import language.http import language.http
import language.actions import language.actions
@ -60,7 +60,8 @@ class PathodHandler(tcp.BaseHandler):
wbufsize = 0 wbufsize = 0
sni = None sni = None
def __init__(self, connection, address, server, settings): def __init__(self, connection, address, server, logfp, settings):
self.logfp = logfp
tcp.BaseHandler.__init__(self, connection, address, server) tcp.BaseHandler.__init__(self, connection, address, server)
self.settings = copy.copy(settings) self.settings = copy.copy(settings)
@ -106,11 +107,9 @@ class PathodHandler(tcp.BaseHandler):
again: True if request handling should continue. again: True if request handling should continue.
log: A dictionary, or None log: A dictionary, or None
""" """
if self.server.logreq: lr = self.rfile if self.server.logreq else None
self.rfile.start_log() lw = self.wfile if self.server.logresp else None
if self.server.logresp: with log.Log(self.logfp, self.server.hexdump, lr, lw) as lg:
self.wfile.start_log()
line = http.get_request_line(self.rfile) line = http.get_request_line(self.rfile)
if not line: if not line:
# Normal termination # Normal termination
@ -140,7 +139,7 @@ class PathodHandler(tcp.BaseHandler):
) )
except tcp.NetLibError as v: except tcp.NetLibError as v:
s = str(v) s = str(v)
self.info(s) lg(s)
self.addlog(dict(type="error", msg=s)) self.addlog(dict(type="error", msg=s))
return False return False
return True return True
@ -150,14 +149,14 @@ class PathodHandler(tcp.BaseHandler):
method, path, httpversion = m.v method, path, httpversion = m.v
else: else:
s = "Invalid first line: %s" % repr(line) s = "Invalid first line: %s" % repr(line)
self.info(s) lg(s)
self.addlog(dict(type="error", msg=s)) self.addlog(dict(type="error", msg=s))
return False return False
headers = http.read_headers(self.rfile) headers = http.read_headers(self.rfile)
if headers is None: if headers is None:
s = "Invalid headers" s = "Invalid headers"
self.info(s) lg(s)
self.addlog(dict(type="error", msg=s)) self.addlog(dict(type="error", msg=s))
return False return False
@ -195,13 +194,13 @@ class PathodHandler(tcp.BaseHandler):
) )
except http.HttpError as s: except http.HttpError as s:
s = str(s) s = str(s)
self.info(s) lg(s)
self.addlog(dict(type="error", msg=s)) self.addlog(dict(type="error", msg=s))
return False return False
for i in self.server.anchors: for i in self.server.anchors:
if i[0].match(path): if i[0].match(path):
self.info("crafting anchor: %s" % path) lg("crafting anchor: %s" % path)
again, retlog["response"] = self.serve_crafted(i[1]) again, retlog["response"] = self.serve_crafted(i[1])
self.addlog(retlog) self.addlog(retlog)
return again return again
@ -214,11 +213,11 @@ class PathodHandler(tcp.BaseHandler):
self.settings.websocket_key = key self.settings.websocket_key = key
if key and not spec: if key and not spec:
spec = "ws" spec = "ws"
self.info("crafting spec: %s" % spec) lg("crafting spec: %s" % spec)
try: try:
crafted = language.parse_response(spec) crafted = language.parse_response(spec)
except language.ParseException as v: except language.ParseException as v:
self.info("Parse error: %s" % v.msg) lg("Parse error: %s" % v.msg)
crafted = language.http.make_error_response( crafted = language.http.make_error_response(
"Parse Error", "Parse Error",
"Error parsing response spec: %s\n" % v.msg + v.marked() "Error parsing response spec: %s\n" % v.msg + v.marked()
@ -235,7 +234,7 @@ class PathodHandler(tcp.BaseHandler):
)) ))
return False return False
else: else:
self.info("app: %s %s" % (method, path)) lg("app: %s %s" % (method, path))
req = wsgi.Request("http", method, path, headers, content) req = wsgi.Request("http", method, path, headers, content)
flow = wsgi.Flow(self.address, req) flow = wsgi.Flow(self.address, req)
sn = self.connection.getsockname() sn = self.connection.getsockname()
@ -248,28 +247,15 @@ class PathodHandler(tcp.BaseHandler):
a.serve(flow, self.wfile) a.serve(flow, self.wfile)
return True return True
def _log_bytes(self, header, data, hexdump):
s = []
if hexdump:
s.append("%s (hex dump):" % header)
for line in netlib.utils.hexdump(data):
s.append("\t%s %s %s" % line)
else:
s.append("%s (unprintables escaped):" % header)
s.append(netlib.utils.cleanBin(data))
self.info("\n".join(s))
def addlog(self, log): def addlog(self, log):
# FIXME: The bytes in the log should not be escaped. We do this at the # FIXME: The bytes in the log should not be escaped. We do this at the
# moment because JSON encoding can't handle binary data, and I don't # moment because JSON encoding can't handle binary data, and I don't
# want to base64 everything. # want to base64 everything.
if self.server.logreq: if self.server.logreq:
bytes = self.rfile.get_log().encode("string_escape") bytes = self.rfile.get_log().encode("string_escape")
self._log_bytes("Request", bytes, self.server.hexdump)
log["request_bytes"] = bytes log["request_bytes"] = bytes
if self.server.logresp: if self.server.logresp:
bytes = self.wfile.get_log().encode("string_escape") bytes = self.wfile.get_log().encode("string_escape")
self._log_bytes("Response", bytes, self.server.hexdump)
log["response_bytes"] = bytes log["response_bytes"] = bytes
self.server.add_log(log) self.server.add_log(log)
@ -324,6 +310,7 @@ class Pathod(tcp.TCPServer):
explain=False, explain=False,
hexdump=False, hexdump=False,
webdebug=False, webdebug=False,
logfp=sys.stdout,
): ):
""" """
addr: (address, port) tuple. If port is 0, a free port will be addr: (address, port) tuple. If port is 0, a free port will be
@ -350,6 +337,7 @@ class Pathod(tcp.TCPServer):
self.timeout, self.logreq = timeout, logreq self.timeout, self.logreq = timeout, logreq
self.logresp, self.hexdump = logresp, hexdump self.logresp, self.hexdump = logresp, hexdump
self.explain = explain self.explain = explain
self.logfp = logfp
self.app = app.make_app(noapi, webdebug) self.app = app.make_app(noapi, webdebug)
self.app.config["pathod"] = self self.app.config["pathod"] = self
@ -378,7 +366,13 @@ class Pathod(tcp.TCPServer):
return None, req return None, req
def handle_client_connection(self, request, client_address): def handle_client_connection(self, request, client_address):
h = PathodHandler(request, client_address, self, self.settings) h = PathodHandler(
request,
client_address,
self,
self.logfp,
self.settings
)
try: try:
h.handle() h.handle()
h.finish() h.finish()

View File

@ -83,6 +83,7 @@ class _PaThread(threading.Thread):
self.server = pathod.Pathod( self.server = pathod.Pathod(
(self.iface, 0), (self.iface, 0),
ssl = self.ssl, ssl = self.ssl,
logfp = None,
**self.daemonargs **self.daemonargs
) )
self.name = "PathodThread (%s:%s)" % ( self.name = "PathodThread (%s:%s)" % (

View File

@ -1,3 +1,4 @@
import cStringIO
from libpathod import pathod, version from libpathod import pathod, version
from netlib import tcp, http from netlib import tcp, http
import tutils import tutils
@ -5,7 +6,8 @@ import tutils
class TestPathod(object): class TestPathod(object):
def test_logging(self): def test_logging(self):
p = pathod.Pathod(("127.0.0.1", 0)) s = cStringIO.StringIO()
p = pathod.Pathod(("127.0.0.1", 0), logfp=s)
assert len(p.get_log()) == 0 assert len(p.get_log()) == 0
id = p.add_log(dict(s="foo")) id = p.add_log(dict(s="foo"))
assert p.log_by_id(id) assert p.log_by_id(id)

View File

@ -20,7 +20,7 @@ def test_parse_size():
def test_parse_anchor_spec(): def test_parse_anchor_spec():
assert utils.parse_anchor_spec("foo=200") == ("foo", "200") assert utils.parse_anchor_spec("foo=200") == ("foo", "200")
assert utils.parse_anchor_spec("foo") == None assert utils.parse_anchor_spec("foo") is None
def test_data_path(): def test_data_path():