Skip to content

Commit

Permalink
Add initial WSGI container support for running other frameworks on To…
Browse files Browse the repository at this point in the history
…rnado's HTTP server
  • Loading branch information
finiteloop committed Sep 13, 2009
1 parent 9c51d01 commit 8ca6160
Showing 1 changed file with 100 additions and 0 deletions.
100 changes: 100 additions & 0 deletions tornado/wsgi.py
Expand Up @@ -44,11 +44,18 @@ def get(self):
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
Expand Down Expand Up @@ -178,6 +185,99 @@ def _parse_mime_body(self, boundary):
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):
Expand Down

0 comments on commit 8ca6160

Please sign in to comment.