application.ui_modules)
self.ui["modules"] = self.ui["_tt_modules"]
self.clear()
- # Check since connection is not available in WSGI
- if getattr(self.request, "connection", None):
- self.request.connection.set_close_callback(
- self.on_connection_close)
+ self.request.connection.set_close_callback(self.on_connection_close)
self.initialize(**kwargs)
def initialize(self):
if another flush occurs before the previous flush's callback
has been run, the previous callback will be discarded.
"""
- if self.application._wsgi:
- # WSGI applications cannot usefully support flush, so just make
- # it a no-op (and run the callback immediately).
- if callback is not None:
- callback()
- return
-
chunk = b"".join(self._write_buffer)
self._write_buffer = []
if not self._headers_written:
# are keepalive connections)
self.request.connection.set_close_callback(None)
- if not self.application._wsgi:
- self.flush(include_footers=True)
- self.request.finish()
- self._log()
+ self.flush(include_footers=True)
+ self.request.finish()
+ self._log()
self._finished = True
self.on_finish()
# Break up a reference cycle between this handler and the
from tornado.ioloop import IOLoop
@functools.wraps(method)
def wrapper(self, *args, **kwargs):
- if self.application._wsgi:
- raise Exception("@asynchronous is not supported for WSGI apps")
self._auto_finish = False
with stack_context.ExceptionStackContext(
self._stack_context_handle_exception):
"""
def __init__(self, handlers=None, default_host="", transforms=None,
- wsgi=False, **settings):
+ **settings):
if transforms is None:
self.transforms = []
if settings.get("gzip"):
'Template': TemplateModule,
}
self.ui_methods = {}
- self._wsgi = wsgi
self._load_ui_modules(settings.get("ui_modules", {}))
self._load_ui_methods(settings.get("ui_methods", {}))
if self.settings.get("static_path"):
self.settings.setdefault('serve_traceback', True)
# Automatically reload modified modules
- if self.settings.get('autoreload') and not wsgi:
+ if self.settings.get('autoreload'):
from tornado import autoreload
autoreload.start()
between Tornado and other Python web frameworks and servers. This module
provides WSGI support in two ways:
-* `WSGIApplication` is a version of `tornado.web.Application` that can run
- inside a WSGI server. This is useful for running a Tornado app on another
- HTTP server, such as Google App Engine. See the `WSGIApplication` class
+* `WSGIAdapter` converts a `tornado.web.Application` to the WSGI application
+ interface. This is useful for running a Tornado app on another
+ HTTP server, such as Google App Engine. See the `WSGIAdapter` class
documentation for limitations that apply.
* `WSGIContainer` lets you run other WSGI applications and frameworks on the
Tornado HTTP server. For example, with this class you can mix Django
from __future__ import absolute_import, division, print_function, with_statement
import sys
-import time
-import copy
import tornado
from tornado import escape
from tornado import httputil
from tornado.log import access_log
from tornado import web
-from tornado.escape import native_str, parse_qs_bytes
+from tornado.escape import native_str
from tornado.util import bytes_type, unicode_type
try:
except ImportError:
from cStringIO import StringIO as BytesIO # python 2
-try:
- import Cookie # py2
-except ImportError:
- import http.cookies as Cookie # py3
-
try:
import urllib.parse as urllib_parse # py3
except ImportError:
class WSGIApplication(web.Application):
"""A WSGI equivalent of `tornado.web.Application`.
- `WSGIApplication` is very similar to `tornado.web.Application`,
- except no asynchronous methods are supported (since WSGI does not
- support non-blocking requests properly). If you call
- ``self.flush()`` or other asynchronous methods in your request
- handlers running in a `WSGIApplication`, we throw an exception.
+ .. deprecated: 3.3::
+
+ Use a regular `.Application` and wrap it in `WSGIAdapter` instead.
+ """
+ def __init__(self, handlers=None, default_host="", **settings):
+ web.Application.__init__(self, handlers, default_host, transforms=[],
+ **settings)
+ self._adapter = WSGIAdapter(self)
+
+ def __call__(self, environ, start_response):
+ return self._adapter.__call__(environ, start_response)
+
+
+class _WSGIConnection(object):
+ def __init__(self, start_response):
+ self.start_response = start_response
+ self._write_buffer = []
+ self._finished = False
+
+ def set_close_callback(self, callback):
+ # WSGI has no facility for detecting a closed connection mid-request,
+ # so we can simply ignore the callback.
+ pass
+
+ def write_headers(self, start_line, headers):
+ self.start_response(
+ '%s %s' % (start_line.code, start_line.reason),
+ [(native_str(k), native_str(v)) for (k, v) in headers.get_all()])
+
+ def write(self, chunk, callback=None):
+ self._write_buffer.append(chunk)
+ if callback is not None:
+ callback()
+
+ def finish(self):
+ self._finished = True
+
+
+class WSGIAdapter(object):
+ """Converts a `tornado.web.Application` instance into a WSGI application.
Example usage::
self.write("Hello, world")
if __name__ == "__main__":
- application = tornado.wsgi.WSGIApplication([
+ application = tornado.web.Application([
(r"/", MainHandler),
])
- server = wsgiref.simple_server.make_server('', 8888, application)
+ wsgi_app = tornado.wsgi.WSGIAdapter(application)
+ server = wsgiref.simple_server.make_server('', 8888, wsgi_app)
server.serve_forever()
See the `appengine demo
for an example of using this module to run a Tornado app on Google
App Engine.
- WSGI applications use the same `.RequestHandler` class, but not
- ``@asynchronous`` methods or ``flush()``. This means that it is
- not possible to use `.AsyncHTTPClient`, or the `tornado.auth` or
- `tornado.websocket` modules.
+ In WSGI mode asynchronous methods are not supported. This means
+ that it is not possible to use `.AsyncHTTPClient`, or the
+ `tornado.auth` or `tornado.websocket` modules.
+
"""
- def __init__(self, handlers=None, default_host="", **settings):
- web.Application.__init__(self, handlers, default_host, transforms=[],
- wsgi=True, **settings)
+ def __init__(self, application):
+ if isinstance(application, WSGIApplication):
+ self.application = lambda request: web.Application.__call__(
+ application, request)
+ else:
+ self.application = application
def __call__(self, environ, start_response):
- handler = web.Application.__call__(self, HTTPRequest(environ))
- assert handler._finished
- reason = handler._reason
- status = str(handler._status_code) + " " + reason
- headers = list(handler._headers.get_all())
- if hasattr(handler, "_new_cookie"):
- for cookie in handler._new_cookie.values():
- headers.append(("Set-Cookie", cookie.OutputString(None)))
- start_response(status,
- [(native_str(k), native_str(v)) for (k, v) in headers])
- return handler._write_buffer
-
-
-class HTTPRequest(object):
- """Mimics `tornado.httputil.HTTPServerRequest` for WSGI applications."""
- def __init__(self, environ):
- """Parses the given WSGI environment to construct the request."""
- self.method = environ["REQUEST_METHOD"]
- self.path = urllib_parse.quote(from_wsgi_str(environ.get("SCRIPT_NAME", "")))
- self.path += urllib_parse.quote(from_wsgi_str(environ.get("PATH_INFO", "")))
- self.uri = self.path
- self.arguments = {}
- self.query_arguments = {}
- self.body_arguments = {}
- self.query = environ.get("QUERY_STRING", "")
- if self.query:
- self.uri += "?" + self.query
- self.arguments = parse_qs_bytes(native_str(self.query),
- keep_blank_values=True)
- self.query_arguments = copy.deepcopy(self.arguments)
- self.version = "HTTP/1.1"
- self.headers = httputil.HTTPHeaders()
+ method = environ["REQUEST_METHOD"]
+ uri = urllib_parse.quote(from_wsgi_str(environ.get("SCRIPT_NAME", "")))
+ uri += urllib_parse.quote(from_wsgi_str(environ.get("PATH_INFO", "")))
+ if environ.get("QUERY_STRING"):
+ uri += "?" + environ["QUERY_STRING"]
+ headers = httputil.HTTPHeaders()
if environ.get("CONTENT_TYPE"):
- self.headers["Content-Type"] = environ["CONTENT_TYPE"]
+ headers["Content-Type"] = environ["CONTENT_TYPE"]
if environ.get("CONTENT_LENGTH"):
- self.headers["Content-Length"] = environ["CONTENT_LENGTH"]
+ headers["Content-Length"] = environ["CONTENT_LENGTH"]
for key in environ:
if key.startswith("HTTP_"):
- self.headers[key[5:].replace("_", "-")] = environ[key]
- if self.headers.get("Content-Length"):
- self.body = environ["wsgi.input"].read(
- int(self.headers["Content-Length"]))
+ headers[key[5:].replace("_", "-")] = environ[key]
+ if headers.get("Content-Length"):
+ body = environ["wsgi.input"].read(
+ int(headers["Content-Length"]))
else:
- self.body = ""
- self.protocol = environ["wsgi.url_scheme"]
- self.remote_ip = environ.get("REMOTE_ADDR", "")
+ body = ""
+ protocol = environ["wsgi.url_scheme"]
+ remote_ip = environ.get("REMOTE_ADDR", "")
if environ.get("HTTP_HOST"):
- self.host = environ["HTTP_HOST"]
+ host = environ["HTTP_HOST"]
else:
- self.host = environ["SERVER_NAME"]
-
- # Parse request body
- self.files = {}
- httputil.parse_body_arguments(self.headers.get("Content-Type", ""),
- self.body, self.body_arguments,
- self.files, self.headers)
-
- for k, v in self.body_arguments.items():
- self.arguments.setdefault(k, []).extend(v)
-
- self._start_time = time.time()
- self._finish_time = None
-
- def supports_http_1_1(self):
- """Returns True if this request supports HTTP/1.1 semantics"""
- return self.version == "HTTP/1.1"
-
- @property
- def cookies(self):
- """A dictionary of Cookie.Morsel objects."""
- if not hasattr(self, "_cookies"):
- self._cookies = Cookie.SimpleCookie()
- if "Cookie" in self.headers:
- try:
- self._cookies.load(
- native_str(self.headers["Cookie"]))
- except Exception:
- self._cookies = None
- return self._cookies
-
- def full_url(self):
- """Reconstructs the full URL for this request."""
- return self.protocol + "://" + self.host + self.uri
-
- def request_time(self):
- """Returns the amount of time it took for this request to execute."""
- if self._finish_time is None:
- return time.time() - self._start_time
- else:
- return self._finish_time - self._start_time
+ host = environ["SERVER_NAME"]
+ connection = _WSGIConnection(start_response)
+ request = httputil.HTTPServerRequest(
+ method, uri, "HTTP/1.1",
+ headers=headers, body=body, remote_ip=remote_ip, protocol=protocol,
+ host=host, connection=connection)
+ request._parse_body()
+ self.application(request)
+ if not connection._finished:
+ raise Exception("request did not finish synchronously")
+ return connection._write_buffer
class WSGIContainer(object):
summary = request.method + " " + request.uri + " (" + \
request.remote_ip + ")"
log_method("%d %s %.2fms", status_code, summary, request_time)
+
+
+HTTPRequest = httputil.HTTPServerRequest