From: Bret Taylor Date: Sun, 13 Sep 2009 18:19:57 +0000 (-0700) Subject: Add initial WSGI container support for running other frameworks on Tornado's HTTP... X-Git-Tag: v1.0.0~121 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8ca616088cfb26ff19fcc6f359d654fef905b8da;p=thirdparty%2Ftornado.git Add initial WSGI container support for running other frameworks on Tornado's HTTP server --- diff --git a/tornado/wsgi.py b/tornado/wsgi.py index 15af00f78..9b61e8f2a 100644 --- a/tornado/wsgi.py +++ b/tornado/wsgi.py @@ -44,11 +44,18 @@ a Tornado app on Google AppEngine. Since no asynchronous methods are available for WSGI applications, the httpclient and auth modules are both not available for WSGI applications. + +We also export WSGIContainer, which lets you run other WSGI-compatible +frameworks on the Tornado HTTP server and I/O loop. See WSGIContainer for +details and documentation. """ import cgi +import cStringIO +import escape import httplib import logging +import sys import time import urllib import web @@ -178,6 +185,99 @@ class HTTPRequest(object): self.arguments.setdefault(name, []).append(value) +class WSGIContainer(object): + """Makes a WSGI-compatible function runnable on Tornado's HTTP server. + + Wrap a WSGI function in a WSGIContainer and pass it to HTTPServer to + run it. For example: + + def simple_app(environ, start_response): + status = "200 OK" + response_headers = [("Content-type", "text/plain")] + start_response(status, response_headers) + return ["Hello world!\n"] + + container = tornado.wsgi.WSGIContainer(simple_app) + http_server = tornado.httpserver.HTTPServer(container) + http_server.listen(8888) + tornado.ioloop.IOLoop.instance().start() + + This class is intended to let other frameworks (Django, web.py, etc) + run on the Tornado HTTP server and I/O loop. It has not yet been + thoroughly tested in production. + """ + def __init__(self, wsgi_application): + self.wsgi_application = wsgi_application + + def __call__(self, request): + data = {} + def start_response(status, response_headers): + data["status"] = status + data["headers"] = HTTPHeaders(response_headers) + body = "".join(self.wsgi_application( + self._environ(request), start_response)) + if not data: raise Exception("WSGI app did not call start_response") + + status_code = int(data["status"].split()[0]) + headers = data["headers"] + body = escape.utf8(body) + headers["Content-Length"] = str(len(body)) + headers.setdefault("Content-Type", "text/html; charset=UTF-8") + headers.setdefault("Server", "TornadoServer/0.1") + + parts = ["HTTP/1.1 " + data["status"] + "\r\n"] + for key, value in headers.iteritems(): + parts.append(escape.utf8(key) + ": " + escape.utf8(value) + "\r\n") + parts.append("\r\n") + parts.append(body) + request.write("".join(parts)) + request.finish() + self._log(status_code, request) + + def _environ(self, request): + hostport = request.host.split(":") + if len(hostport) == 2: + host = hostport[0] + port = int(hostport[1]) + else: + host = request.host + port = 443 if request.protocol == "https" else 80 + environ = { + "REQUEST_METHOD": request.method, + "SCRIPT_NAME": "", + "PATH_INFO": request.path, + "QUERY_STRING": request.query, + "SERVER_NAME": host, + "SERVER_PORT": port, + "wsgi.version": (1, 0), + "wsgi.url_scheme": request.protocol, + "wsgi.input": cStringIO.StringIO(request.body), + "wsgi.errors": sys.stderr, + "wsgi.multithread": False, + "wsgi.multiprocess": True, + "wsgi.run_once": False, + } + if "Content-Type" in request.headers: + environ["CONTENT_TYPE"] = request.headers["Content-Type"] + if "Content-Length" in request.headers: + environ["CONTENT_LENGTH"] = request.headers["Content-Length"] + for key, value in request.headers.iteritems(): + environ["HTTP_" + key.replace("-", "_").upper()] = value + return environ + + def _log(self, status_code, request): + if status_code < 400: + log_method = logging.info + elif status_code < 500: + log_method = logging.warning + else: + log_method = logging.error + request_time = 1000.0 * request.request_time() + summary = request.method + " " + request.uri + " (" + \ + request.remote_ip + ")" + log_method("%d %s %.2fms", status_code, summary, request_time) + + class HTTPHeaders(dict): """A dictionary that maintains Http-Header-Case for all keys.""" def __setitem__(self, name, value):