Permalink
Browse files

initial commit

  • Loading branch information...
0 parents commit bdb5d3ceb2734916683557d986dfd8112475e966 @wil committed Dec 24, 2009
Showing with 269 additions and 0 deletions.
  1. +22 −0 src/README.rst
  2. 0 src/gt/__init__.py
  3. BIN src/gt/__init__.pyc
  4. +247 −0 src/gt/monkey.py
  5. BIN src/gt/monkey.pyc
@@ -0,0 +1,22 @@
+gtornado = Tornado + gevent
+===========================
+
+:Author: Wil Tan
+:Version: $Revision:
+
+
+Introduction
+------------
+
+Tornado_ is a high performance web server and framework. It operates in a non-blocking fashion,
+utilizing Linux's epoll_ facility when available. It also comes bundled with several niceties
+such as authentication via OpenID, OAuth, secure cookies, templates, CSRF protection and UI modules.
+
+Unfortunately, some of its features ties the developer into its own asynchronous API implementation.
+
+This module is an experiment to monkey patch it just enough to make it run under gevent.
+One advantage of doing so is that one can use a coroutine-style and code in a blocking fashion
+while being able to use the tornado framework.
+
+.. _Tornado: http://www.tornadoweb.org/
+.. _epoll: http://www.kernel.org/doc/man-pages/online/pages/man4/epoll.4.html
No changes.
Binary file not shown.
@@ -0,0 +1,247 @@
+import time
+import cgi
+import gevent
+import gevent.hub
+import gevent.http
+
+def patch_tornado_ioloop():
+ _tornado_iol = __import__('tornado.ioloop', fromlist=['fromlist_has_to_be_non_empty'])
+ _IOLoop = _tornado_iol.IOLoop
+
+ class IOLoop:
+ def __init__(self):
+ self._handlers = {} # by fd
+ self._events = {} # by fd
+
+ def start(self):
+ gevent.hub.get_hub().switch()
+
+ def remove_handler(self, fd):
+ self._handlers.pop(fd, None)
+ ev = self._events.pop(fd, None)
+ ev.cancel()
+
+ def update_handler(self, fd, events):
+ handler = self._handlers.pop(fd, None)
+ ev = self._events[fd]
+ self.remove_handler(fd)
+ self.add_handler(fd, handler, events)
+
+ def add_handler(self, fd, handler, events):
+ type = 0
+ if events & _IOLoop.READ:
+ type = type | gevent.core.EV_READ
+ if events & _IOLoop.WRITE:
+ type = type | gevent.core.EV_WRITE
+ if events & _IOLoop.ERROR:
+ type = type | gevent.core.EV_SIGNAL
+
+ def callback(ev, type):
+ tornado_events = 0
+ if type & gevent.core.EV_READ:
+ tornado_events |= _IOLoop.READ
+ if type & gevent.core.EV_WRITE:
+ tornado_events |= _IOLoop.WRITE
+ if type & gevent.core.EV_SIGNAL:
+ tornado_events |= _IOLoop.ERROR
+ return handler(ev.fd, tornado_events)
+
+ self._events[fd] = gevent.core.event(type, fd, callback)
+ self._handlers[fd] = handler
+
+
+ def add_callback(self, callback):
+ print "adding callback"
+ gevent.spawn(callback)
+
+ def add_timeout(self, deadline, callback):
+ print "adding callback"
+ gevent.spawn_later(int(deadline - time.time()), callback)
+
+ @classmethod
+ def instance(cls):
+ print "new instance?"
+ if not hasattr(cls, "_instance"):
+ cls._instance = cls()
+ return cls._instance
+
+ #print "orig ioloop = ", dir(_tornado_iol)
+ _tornado_iol.IOLoop = IOLoop
+ #print "iol = ", id(_tornado_iol.IOLoop)
+
+
+
+def patch_tornado_httpserver():
+ from tornado.httpserver import HTTPRequest
+
+ def parse_t_http_output(buf):
+ headers, body = buf.split("\r\n\r\n", 1)
+ headers = headers.split("\r\n")
+ ver, code, msg = headers[0].split(" ", 2)
+ code = int(code)
+ chunked = False
+
+ headers_out = []
+ for h in headers[1:]:
+ k, v = h.split(":", 1)
+ if k == "Transfer-Encoding" and v == "chunked":
+ chunked = True
+ headers_out.append((k, v.lstrip()))
+
+ return code, msg, headers_out, body, chunked
+
+ def parse_post_body(req, body):
+ content_type = req.headers.get("Content-Type", "")
+ if req.method == "POST":
+ if content_type.startswith("application/x-www-form-urlencoded"):
+ arguments = cgi.parse_qs(req.body)
+ for name, values in arguments.iteritems():
+ values = [v for v in values if v]
+ if values:
+ req.arguments.setdefault(name, []).extend(values)
+ elif content_type.startswith("multipart/form-data"):
+ boundary = content_type[30:]
+ if boundary:
+ self._parse_mime_body(boundary, data)
+ # from HTTPConnection._parse_mime_body
+ if data.endswith("\r\n"):
+ footer_length = len(boundary) + 6
+ else:
+ footer_length = len(boundary) + 4
+ parts = data[:-footer_length].split("--" + boundary + "\r\n")
+ for part in parts:
+ if not part: continue
+ eoh = part.find("\r\n\r\n")
+ if eoh == -1:
+ logging.warning("multipart/form-data missing headers")
+ continue
+ headers = HTTPHeaders.parse(part[:eoh])
+ name_header = headers.get("Content-Disposition", "")
+ if not name_header.startswith("form-data;") or \
+ not part.endswith("\r\n"):
+ logging.warning("Invalid multipart/form-data")
+ continue
+ value = part[eoh + 4:-2]
+
+
+ name_values = {}
+ for name_part in name_header[10:].split(";"):
+ name, name_value = name_part.strip().split("=", 1)
+ name_values[name] = name_value.strip('"').decode("utf-8")
+ if not name_values.get("name"):
+ logging.warning("multipart/form-data value missing name")
+ continue
+ name = name_values["name"]
+ if name_values.get("filename"):
+ ctype = headers.get("Content-Type", "application/unknown")
+ req.files.setdefault(name, []).append(dict(
+ filename=name_values["filename"], body=value,
+ content_type=ctype))
+ else:
+ req.arguments.setdefault(name, []).append(value)
+
+
+ class FakeStream():
+ def __init__(self):
+ self._closed = False
+
+ def closed(self):
+ print "stream closed = ", self._closed
+ return self._closed
+
+
+ class FakeConnection():
+ def __init__(self, r):
+ self._r = r
+ self.xheaders = False
+ self.reply_started = False
+ self.stream = FakeStream()
+ #r.connection.set_closecb(self)
+
+ def _cb_connection_close(self, conn):
+ print "connection %r closed!!!!" % (conn,)
+ print "stream = %r" % self.stream
+ self.stream._closed = True
+ print "flagged stream as closed"
+
+ def write(self, chunk):
+ if not self.reply_started:
+ #print "starting reply..."
+ # need to parse the first line as RequestHandler actually writes the response line
+ code, msg, headers, body, chunked = parse_t_http_output(chunk)
+
+ for k, v in headers:
+ #print "header[%s] = %s" % (k, v)
+ self._r.add_output_header(k, v)
+
+ if chunked:
+ self._r.send_reply_start(code, msg)
+ self._r.send_reply_chunk(body)
+ else:
+ self._r.send_reply(code, msg, body)
+ self.reply_started = True
+ else:
+ print "writing %s" % chunk
+ self._r.send_reply_chunk(chunk)
+
+ def finish(self):
+ print "finishing..."
+ self._r.send_reply_end()
+ print "finished"
+
+
+ class GHttpServer:
+ def __init__(self, t_app):
+ def debug_http_cb(r):
+ print "http request = ", r
+ for m in dir(r):
+ o = eval("r." + m)
+ if type(o) in (str, list, int, tuple):
+ print "r.%s = %r" % (m, o)
+ r.add_output_header("X-Awesomeness", "100%")
+ r.send_reply(200, "OK", '<b>hello</b>')
+
+
+ def http_cb(r):
+ body = r.input_buffer.read()
+ treq = HTTPRequest(
+ r.typestr, # method
+ r.uri, # uri
+ headers=dict(r.get_input_headers()), # need to transform from list of tuples to dict
+ body=body,
+ remote_ip=r.remote_host,
+ protocol="http", # or https
+ host=None, # 127.0.0.1?
+ files=None, # ??
+ connection=FakeConnection(r))
+
+ parse_post_body(treq, body)
+
+ """
+ print "http request = ", r
+ for m in dir(r):
+ o = eval("r." + m)
+ if type(o) in (str, list, int, tuple):
+ print "r.%s = %r" % (m, o)
+ """
+ t_app(treq)
+
+ self._httpserver = gevent.http.HTTPServer(http_cb)
+
+ def listen(self, port):
+ self._httpserver.serve_forever(('0.0.0.0', port), backlog=128)
+
+ @classmethod
+ def instance(cls):
+ print "new instance?"
+ if not hasattr(cls, "_instance"):
+ cls._instance = cls()
+ return cls._instance
+
+ _httpserver = __import__('tornado.httpserver', fromlist=['fromlist_has_to_be_non_empty'])
+ _httpserver.HTTPServer = GHttpServer
+
+
+def patch_tornado():
+ patch_tornado_ioloop()
+ patch_tornado_httpserver()
Binary file not shown.

0 comments on commit bdb5d3c

Please sign in to comment.