diff --git a/.gitignore b/.gitignore index 64280a20b..ac1655a1e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ MANIFEST *.swp /doc .coverage +netlib diff --git a/libpathod/app.py b/libpathod/app.py new file mode 100644 index 000000000..8ea19f4cf --- /dev/null +++ b/libpathod/app.py @@ -0,0 +1,97 @@ + +class APILog(tornado.web.RequestHandler): + def get(self): + self.write( + dict( + d = self.application.get_log() + ) + ) + + +class APILogClear(tornado.web.RequestHandler): + def post(self): + self.application.clear_log() + self.write("OK") + + +class APIShutdown(tornado.web.RequestHandler): + def post(self): + tornado.ioloop.IOLoop.instance().stop() + self.write("OK") + + +class APIInfo(tornado.web.RequestHandler): + def get(self): + self.write( + dict( + version = version.IVERSION + ) + ) + + +class _Page(tornado.web.RequestHandler): + def render(self, name, **kwargs): + tornado.web.RequestHandler.render(self, name + ".html", **kwargs) + + +class Index(_Page): + name = "index" + section = "main" + def get(self): + self.render(self.name, section=self.section, spec="") + + +class Preview(_Page): + name = "preview" + section = "main" + SANITY = 1024*1024 + def get(self): + spec = self.get_argument("spec", None) + args = dict( + spec = spec, + section = self.section, + syntaxerror = None, + error = None + ) + try: + r = rparse.parse(self.application.settings, spec) + except rparse.ParseException, v: + args["syntaxerror"] = str(v) + args["marked"] = v.marked() + return self.render(self.name, **args) + if r.length() > self.SANITY: + error = "Refusing to preview a response of %s bytes. This is for your own good."%r.length() + args["error"] = error + else: + d = utils.DummyRequest() + r.serve(d) + args["output"] = d.getvalue() + self.render(self.name, **args) + + +class Help(_Page): + name = "help" + section = "help" + def get(self): + self.render(self.name, section=self.section) + + +class Log(_Page): + name = "log" + section = "log" + def get(self): + self.render(self.name, section=self.section, log=self.application.log) + + +class OneLog(_Page): + name = "onelog" + section = "log" + def get(self, lid): + l = pprint.pformat(self.application.log_by_id(int(lid))) + self.render(self.name, section=self.section, alog=l, lid=lid) + + +class ClearLog(_Page): + def post(self): + self.application.clear_logs() + self.redirect("/log") diff --git a/libpathod/netlib.py b/libpathod/netlib.py deleted file mode 100644 index 08ccba091..000000000 --- a/libpathod/netlib.py +++ /dev/null @@ -1,182 +0,0 @@ -import select, socket, threading, traceback, sys -from OpenSSL import SSL - - -class NetLibError(Exception): pass - - -class FileLike: - def __init__(self, o): - self.o = o - - def __getattr__(self, attr): - return getattr(self.o, attr) - - def flush(self): - pass - - def read(self, length): - result = '' - while len(result) < length: - try: - data = self.o.read(length) - except SSL.ZeroReturnError: - break - if not data: - break - result += data - return result - - def write(self, v): - self.o.sendall(v) - - def readline(self, size = None): - result = '' - bytes_read = 0 - while True: - if size is not None and bytes_read >= size: - break - ch = self.read(1) - bytes_read += 1 - if not ch: - break - else: - result += ch - if ch == '\n': - break - return result - - -class TCPClient: - def __init__(self, ssl, host, port, clientcert): - self.ssl, self.host, self.port, self.clientcert = ssl, host, port, clientcert - self.connection, self.rfile, self.wfile = None, None, None - self.cert = None - self.connect() - - def connect(self): - try: - addr = socket.gethostbyname(self.host) - server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - if self.ssl: - context = SSL.Context(SSL.SSLv23_METHOD) - if self.clientcert: - context.use_certificate_file(self.clientcert) - server = SSL.Connection(context, server) - server.connect((addr, self.port)) - if self.ssl: - self.cert = server.get_peer_certificate() - self.rfile, self.wfile = FileLike(server), FileLike(server) - else: - self.rfile, self.wfile = server.makefile('rb'), server.makefile('wb') - except socket.error, err: - raise NetLibError('Error connecting to "%s": %s' % (self.host, err)) - self.connection = server - - -class BaseHandler: - rbufsize = -1 - wbufsize = 0 - def __init__(self, connection, client_address, server): - self.connection = connection - self.rfile = self.connection.makefile('rb', self.rbufsize) - self.wfile = self.connection.makefile('wb', self.wbufsize) - - self.client_address = client_address - self.server = server - self.handle() - self.finish() - - def convert_to_ssl(self, cert, key): - ctx = SSL.Context(SSL.SSLv23_METHOD) - ctx.use_privatekey_file(key) - ctx.use_certificate_file(cert) - self.connection = SSL.Connection(ctx, self.connection) - self.connection.set_accept_state() - self.rfile = FileLike(self.connection) - self.wfile = FileLike(self.connection) - - def finish(self): - try: - if not getattr(self.wfile, "closed", False): - self.wfile.flush() - self.connection.close() - self.wfile.close() - self.rfile.close() - except IOError: # pragma: no cover - pass - - def handle(self): # pragma: no cover - raise NotImplementedError - - -class TCPServer: - request_queue_size = 20 - def __init__(self, server_address): - self.server_address = server_address - self.__is_shut_down = threading.Event() - self.__shutdown_request = False - self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self.socket.bind(self.server_address) - self.server_address = self.socket.getsockname() - self.socket.listen(self.request_queue_size) - self.port = self.socket.getsockname()[1] - - def request_thread(self, request, client_address): - try: - self.handle_connection(request, client_address) - request.close() - except: - self.handle_error(request, client_address) - request.close() - - def serve_forever(self, poll_interval=0.5): - self.__is_shut_down.clear() - try: - while not self.__shutdown_request: - r, w, e = select.select([self.socket], [], [], poll_interval) - if self.socket in r: - try: - request, client_address = self.socket.accept() - except socket.error: - return - try: - t = threading.Thread( - target = self.request_thread, - args = (request, client_address) - ) - t.setDaemon(1) - t.start() - except: - self.handle_error(request, client_address) - request.close() - finally: - self.__shutdown_request = False - self.__is_shut_down.set() - - def shutdown(self): - self.__shutdown_request = True - self.__is_shut_down.wait() - self.handle_shutdown() - - def handle_error(self, request, client_address, fp=sys.stderr): - """ - Called when handle_connection raises an exception. - """ - print >> fp, '-'*40 - print >> fp, "Error processing of request from %s:%s"%client_address - print >> fp, traceback.format_exc() - print >> fp, '-'*40 - - def handle_connection(self, request, client_address): # pragma: no cover - """ - Called after client connection. - """ - raise NotImplementedError - - def handle_shutdown(self): - """ - Called after server shutdown. - """ - pass diff --git a/libpathod/pathod.py b/libpathod/pathod.py index f712582e8..c700b550f 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -1,14 +1,25 @@ -import netlib +from netlib import tcp, protocol, odict -class PathodHandler(netlib.BaseHandler): +class PathodHandler(tcp.BaseHandler): def handle(self): - print "Here" + line = self.rfile.readline() + if line == "\r\n" or line == "\n": # Possible leftover from previous message + line = self.rfile.readline() + if line == "": + return None + + method, path, httpversion = protocol.parse_init_http(line) + headers = odict.ODictCaseless(protocol.read_headers(self.rfile)) + content = protocol.read_http_body_request( + self.rfile, self.wfile, headers, httpversion, None + ) + print method, path, httpversion + #return flow.Request(client_conn, httpversion, host, port, "http", method, path, headers, content) -class PathodServer(netlib.TCPServer): +class Pathod(tcp.TCPServer): def __init__(self, addr): - netlib.TCPServer.__init__(self, addr) + tcp.TCPServer.__init__(self, addr) def handle_connection(self, request, client_address): PathodHandler(request, client_address, self) - diff --git a/libpathod/test.py b/libpathod/test.py index a66124a33..28fcd664f 100644 --- a/libpathod/test.py +++ b/libpathod/test.py @@ -7,7 +7,8 @@ IFACE = "127.0.0.1" class Daemon: def __init__(self, staticdir=None, anchors=(), ssl=None): - self.app = pathod.make_app(staticdir=staticdir, anchors=anchors) + #self.app = pathod.make_app(staticdir=staticdir, anchors=anchors) + self.app = None self.q = Queue.Queue() self.thread = PaThread(self.q, self.app, ssl) self.thread.start() @@ -19,7 +20,7 @@ class Daemon: return resp.json def shutdown(self): - requests.post("%s/api/shutdown"%self.urlbase, verify=False) + self.thread.server.shutdown() self.thread.join() @@ -37,6 +38,7 @@ class PaThread(threading.Thread): ) else: ssloptions = self.ssl - self.server, self.port = pathod.make_server(self.app, 0, IFACE, ssloptions) - self.q.put(self.port) - pathod.run(self.server) + self.server = pathod.Pathod((IFACE, 0)) + #self.server, self.port = pathod.make_server(self.app, 0, IFACE, ssloptions) + self.q.put(self.server.port) + self.server.serve_forever() diff --git a/test/test_test.py b/test/test_test.py index 53decd1ca..c3743a42e 100644 --- a/test/test_test.py +++ b/test/test_test.py @@ -46,4 +46,3 @@ class TestDaemon: def test_info(self): assert tuple(self.d.info()["version"]) == version.IVERSION -