More robust response handling.

This commit is contained in:
Aldo Cortesi 2012-07-21 20:50:41 +12:00
parent 86fe199988
commit 7a49cdfef3
3 changed files with 94 additions and 73 deletions

View File

@ -40,23 +40,23 @@ class Pathoc(tcp.TCPClient):
r = rparse.parse_request({}, i) r = rparse.parse_request({}, i)
req = r.serve(self.wfile) req = r.serve(self.wfile)
if reqdump: if reqdump:
print >> fp, ">>", req["method"], req["path"] print >> fp, "\n>>", req["method"], req["path"]
for a in req["actions"]: for a in req["actions"]:
print >> fp, "\t", print >> fp, "\t",
for x in a: for x in a:
print x, print >> fp, x,
print print >> fp
self.wfile.flush() self.wfile.flush()
resp = self.request(i) resp = http.read_response(self.rfile, r.method, None)
except rparse.ParseException, v: except rparse.ParseException, v:
print >> fp, "Error parsing request spec: %s"%v.msg print >> fp, "Error parsing request spec: %s"%v.msg
print >> fp, v.marked() print >> fp, v.marked()
return return
except http.HttpError, v: except http.HttpError, v:
print >> fp, v.msg print >> fp, "<<", v.msg
return return
except tcp.NetLibTimeout: except tcp.NetLibTimeout:
print >> fp, "Timeout" print >> fp, "<<", "Timeout"
else: else:
if respdump: if respdump:
print_full(fp, *resp) print_full(fp, *resp)

View File

@ -18,6 +18,82 @@ class PathodHandler(tcp.BaseHandler):
def handle_sni(self, connection): def handle_sni(self, connection):
self.sni = connection.get_servername() self.sni = connection.get_servername()
def handle_request(self):
"""
Returns True if handling should continue.
"""
line = self.rfile.readline()
if line == "\r\n" or line == "\n": # Possible leftover from previous message
line = self.rfile.readline()
if line == "":
return
parts = http.parse_init_http(line)
if not parts:
s = "Invalid first line: %s"%line.rstrip()
self.info(s)
self.server.add_log(
dict(
type = "error",
msg = s
)
)
return
method, path, httpversion = parts
headers = http.read_headers(self.rfile)
content = http.read_http_body_request(
self.rfile, self.wfile, headers, httpversion, None
)
crafted = None
for i in self.server.anchors:
if i[0].match(path):
crafted = i[1]
if not crafted and path.startswith(self.server.prefix):
spec = urllib.unquote(path)[len(self.server.prefix):]
try:
crafted = rparse.parse_response(self.server.request_settings, spec)
except rparse.ParseException, v:
crafted = rparse.InternalResponse(
800,
"Error parsing response spec: %s\n"%v.msg + v.marked()
)
request_log = dict(
path = path,
method = method,
headers = headers.lst,
sni = self.sni,
remote_address = self.client_address,
httpversion = httpversion,
)
if crafted:
response_log = crafted.serve(self.wfile)
if response_log["disconnect"]:
return
self.server.add_log(
dict(
type = "crafted",
request=request_log,
response=response_log
)
)
else:
cc = wsgi.ClientConn(self.client_address)
req = wsgi.Request(cc, "http", method, path, headers, content)
sn = self.connection.getsockname()
app = wsgi.WSGIAdaptor(
self.server.app,
sn[0],
self.server.port,
version.NAMEVERSION
)
app.serve(req, self.wfile)
self.debug("%s %s"%(method, path))
return True
def handle(self): def handle(self):
if self.server.ssloptions: if self.server.ssloptions:
try: try:
@ -34,79 +110,21 @@ class PathodHandler(tcp.BaseHandler):
) )
) )
self.info(s) self.info(s)
self.finish() return
while not self.finished: while not self.finished:
line = self.rfile.readline() try:
if line == "\r\n" or line == "\n": # Possible leftover from previous message if not self.handle_request():
line = self.rfile.readline() return
if line == "": except tcp.NetLibDisconnect:
return None self.info("Disconnect")
parts = http.parse_init_http(line)
if not parts:
s = "Invalid first line: %s"%line.rstrip()
self.info(s)
self.server.add_log( self.server.add_log(
dict( dict(
type = "error", type = "error",
msg = s msg = "Disconnect"
) )
) )
return None return
method, path, httpversion = parts
headers = http.read_headers(self.rfile)
content = http.read_http_body_request(
self.rfile, self.wfile, headers, httpversion, None
)
crafted = None
for i in self.server.anchors:
if i[0].match(path):
crafted = i[1]
if not crafted and path.startswith(self.server.prefix):
spec = urllib.unquote(path)[len(self.server.prefix):]
try:
crafted = rparse.parse_response(self.server.request_settings, spec)
except rparse.ParseException, v:
crafted = rparse.InternalResponse(
800,
"Error parsing response spec: %s\n"%v.msg + v.marked()
)
request_log = dict(
path = path,
method = method,
headers = headers.lst,
sni = self.sni,
remote_address = self.client_address,
httpversion = httpversion,
)
if crafted:
response_log = crafted.serve(self.wfile)
if response_log["disconnect"]:
self.finish()
self.server.add_log(
dict(
type = "crafted",
request=request_log,
response=response_log
)
)
else:
cc = wsgi.ClientConn(self.client_address)
req = wsgi.Request(cc, "http", method, path, headers, content)
sn = self.connection.getsockname()
app = wsgi.WSGIAdaptor(
self.server.app,
sn[0],
self.server.port,
version.NAMEVERSION
)
app.serve(req, self.wfile)
self.debug("%s %s"%(method, path))
class Pathod(tcp.TCPServer): class Pathod(tcp.TCPServer):

View File

@ -64,11 +64,13 @@ class _DaemonTests:
scheme = "https" if self.SSL else "http" scheme = "https" if self.SSL else "http"
return requests.get("%s://localhost:%s/p/%s"%(scheme, self.d.port, spec), verify=False) return requests.get("%s://localhost:%s/p/%s"%(scheme, self.d.port, spec), verify=False)
def pathoc(self, spec): def pathoc(self, spec, timeout=None):
c = pathoc.Pathoc("localhost", self.d.port) c = pathoc.Pathoc("localhost", self.d.port)
c.connect() c.connect()
if self.SSL: if self.SSL:
c.convert_to_ssl() c.convert_to_ssl()
if timeout:
c.settimeout(timeout)
return c.request(spec) return c.request(spec)
def test_preline(self): def test_preline(self):
@ -114,6 +116,7 @@ class _DaemonTests:
assert "foo" in l["msg"] assert "foo" in l["msg"]
class TestDaemon(_DaemonTests): class TestDaemon(_DaemonTests):
SSL = False SSL = False