Expand and rewrite the guide docs.
This commit is contained in:
parent
76b8ab66f2
commit
1b0cc391c7
|
@ -1,5 +1,5 @@
|
|||
Asynchronous and non-Blocking
|
||||
-----------------------------
|
||||
Asynchronous and non-Blocking I/O
|
||||
---------------------------------
|
||||
|
||||
Real-time web features require a long-lived mostly-idle connection per
|
||||
user. In a traditional synchronous web server, this implies devoting
|
||||
|
|
|
@ -1,157 +1,68 @@
|
|||
Running and deploying
|
||||
=====================
|
||||
|
||||
Static files and aggressive file caching
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Since Tornado supplies its own HTTPServer, running and deploying it is
|
||||
a little different from other Python web frameworks. Instead of
|
||||
configuring a WSGI container to find your application, you write a
|
||||
``main()`` function that starts the server::
|
||||
|
||||
You can serve static files from Tornado by specifying the
|
||||
``static_path`` setting in your application:
|
||||
def main():
|
||||
app = make_app()
|
||||
app.listen(8888)
|
||||
IOLoop.current().start()
|
||||
|
||||
::
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
settings = {
|
||||
"static_path": os.path.join(os.path.dirname(__file__), "static"),
|
||||
"cookie_secret": "__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
|
||||
"login_url": "/login",
|
||||
"xsrf_cookies": True,
|
||||
}
|
||||
application = tornado.web.Application([
|
||||
(r"/", MainHandler),
|
||||
(r"/login", LoginHandler),
|
||||
(r"/(apple-touch-icon\.png)", tornado.web.StaticFileHandler,
|
||||
dict(path=settings['static_path'])),
|
||||
], **settings)
|
||||
Configure your operating system or process manager to run this program to
|
||||
start the server.
|
||||
|
||||
This setting will automatically make all requests that start with
|
||||
``/static/`` serve from that static directory, e.g.,
|
||||
`http://localhost:8888/static/foo.png <http://localhost:8888/static/foo.png>`_
|
||||
will serve the file ``foo.png`` from the specified static directory. We
|
||||
also automatically serve ``/robots.txt`` and ``/favicon.ico`` from the
|
||||
static directory (even though they don't start with the ``/static/``
|
||||
prefix).
|
||||
Processes and ports
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In the above settings, we have explicitly configured Tornado to serve
|
||||
``apple-touch-icon.png`` “from” the root with the ``StaticFileHandler``,
|
||||
though it is physically in the static file directory. (The capturing
|
||||
group in that regular expression is necessary to tell
|
||||
``StaticFileHandler`` the requested filename; capturing groups are
|
||||
passed to handlers as method arguments.) You could do the same thing to
|
||||
serve e.g. ``sitemap.xml`` from the site root. Of course, you can also
|
||||
avoid faking a root ``apple-touch-icon.png`` by using the appropriate
|
||||
``<link />`` tag in your HTML.
|
||||
Due to the Python GIL (Global Interpreter Lock), it is necessary to run
|
||||
multiple Python processes to take full advantage of multi-CPU machines.
|
||||
Typically it is best to run one process per CPU.
|
||||
|
||||
To improve performance, it is generally a good idea for browsers to
|
||||
cache static resources aggressively so browsers won't send unnecessary
|
||||
``If-Modified-Since`` or ``Etag`` requests that might block the
|
||||
rendering of the page. Tornado supports this out of the box with *static
|
||||
content versioning*.
|
||||
Tornado includes a built-in multi-process mode to start several
|
||||
processes at once. This requires a slight alteration to the standard
|
||||
main function::
|
||||
|
||||
To use this feature, use the ``static_url()`` method in your templates
|
||||
rather than typing the URL of the static file directly in your HTML:
|
||||
def main():
|
||||
app = make_app()
|
||||
server = tornado.httpserver.HTTPServer(app)
|
||||
server.bind(8888)
|
||||
server.start(0) # forks one process per cpu
|
||||
IOLoop.current().start()
|
||||
|
||||
::
|
||||
This is the easiest way to start multiple processes and have them all
|
||||
share the same port, although it has some limitations. First, each
|
||||
child process will have its own IOLoop, so it is important that
|
||||
nothing touch the global IOLoop instance (even indirectly) before the
|
||||
fork. Second, it is difficult to do zero-downtime updates in this model.
|
||||
Finally, since all the processes share the same port it is more difficult
|
||||
to monitor them individually.
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>FriendFeed - {{ _("Home") }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div><img src="{{ static_url("images/logo.png") }}"/></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
The ``static_url()`` function will translate that relative path to a URI
|
||||
that looks like ``/static/images/logo.png?v=aae54``. The ``v`` argument
|
||||
is a hash of the content in ``logo.png``, and its presence makes the
|
||||
Tornado server send cache headers to the user's browser that will make
|
||||
the browser cache the content indefinitely.
|
||||
|
||||
Since the ``v`` argument is based on the content of the file, if you
|
||||
update a file and restart your server, it will start sending a new ``v``
|
||||
value, so the user's browser will automatically fetch the new file. If
|
||||
the file's contents don't change, the browser will continue to use a
|
||||
locally cached copy without ever checking for updates on the server,
|
||||
significantly improving rendering performance.
|
||||
|
||||
In production, you probably want to serve static files from a more
|
||||
optimized static file server like `nginx <http://nginx.net/>`_. You can
|
||||
configure most any web server to support these caching semantics. Here
|
||||
is the nginx configuration we use at FriendFeed:
|
||||
|
||||
::
|
||||
|
||||
location /static/ {
|
||||
root /var/friendfeed/static;
|
||||
if ($query_string) {
|
||||
expires max;
|
||||
}
|
||||
}
|
||||
|
||||
.. _debug-mode:
|
||||
|
||||
Debug mode and automatic reloading
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you pass ``debug=True`` to the ``Application`` constructor, the app
|
||||
will be run in debug/development mode. In this mode, several features
|
||||
intended for convenience while developing will be enabled (each of which
|
||||
is also available as an individual flag; if both are specified the
|
||||
individual flag takes precedence):
|
||||
|
||||
* ``autoreload=True``: The app will watch for changes to its source
|
||||
files and reload itself when anything changes. This reduces the need
|
||||
to manually restart the server during development. However, certain
|
||||
failures (such as syntax errors at import time) can still take the
|
||||
server down in a way that debug mode cannot currently recover from.
|
||||
* ``compiled_template_cache=False``: Templates will not be cached.
|
||||
* ``static_hash_cache=False``: Static file hashes (used by the
|
||||
``static_url`` function) will not be cached
|
||||
* ``serve_traceback=True``: When an exception in a ``RequestHandler``
|
||||
is not caught, an error page including a stack trace will be
|
||||
generated.
|
||||
|
||||
Autoreload mode is not compatible with the multi-process mode of ``HTTPServer``.
|
||||
You must not give ``HTTPServer.start`` an argument other than 1 (or
|
||||
call `tornado.process.fork_processes`) if you are using autoreload mode.
|
||||
|
||||
The automatic reloading feature of debug mode is available as a
|
||||
standalone module in ``tornado.autoreload``. The two can be used in
|
||||
combination to provide extra robustness against syntax errors: set
|
||||
``autoreload=True`` within the app to detect changes while it is running,
|
||||
and start it with ``python -m tornado.autoreload myserver.py`` to catch
|
||||
any syntax errors or other errors at startup.
|
||||
|
||||
Reloading loses any Python interpreter command-line arguments (e.g. ``-u``)
|
||||
because it re-executes Python using ``sys.executable`` and ``sys.argv``.
|
||||
Additionally, modifying these variables will cause reloading to behave
|
||||
incorrectly.
|
||||
|
||||
On some platforms (including Windows and Mac OSX prior to 10.6), the
|
||||
process cannot be updated "in-place", so when a code change is
|
||||
detected the old server exits and a new one starts. This has been
|
||||
known to confuse some IDEs.
|
||||
For more sophisticated deployments, it is recommended to start the processes
|
||||
independently, and have each one listen on a different port.
|
||||
The "process groups" feature of `supervisord <http://www.supervisord.org>`_
|
||||
is one good way to arrange this. When each process uses a different port,
|
||||
an external load balancer such as HAProxy or nginx is usually needed
|
||||
to present a single address to outside visitors.
|
||||
|
||||
|
||||
Running Tornado in production
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
At FriendFeed, we use `nginx <http://nginx.net/>`_ as a load balancer
|
||||
and static file server. We run multiple instances of the Tornado web
|
||||
server on multiple frontend machines. We typically run one Tornado
|
||||
frontend per core on the machine (sometimes more depending on
|
||||
utilization).
|
||||
Running behind a load balancer
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When running behind a load balancer like nginx, it is recommended to
|
||||
pass ``xheaders=True`` to the ``HTTPServer`` constructor. This will tell
|
||||
pass ``xheaders=True`` to the `.HTTPServer` constructor. This will tell
|
||||
Tornado to use headers like ``X-Real-IP`` to get the user's IP address
|
||||
instead of attributing all traffic to the balancer's IP address.
|
||||
|
||||
This is a barebones nginx config file that is structurally similar to
|
||||
the one we use at FriendFeed. It assumes nginx and the Tornado servers
|
||||
are running on the same machine, and the four Tornado servers are
|
||||
running on ports 8000 - 8003:
|
||||
|
||||
::
|
||||
running on ports 8000 - 8003::
|
||||
|
||||
user nginx;
|
||||
worker_processes 1;
|
||||
|
@ -225,38 +136,159 @@ running on ports 8000 - 8003:
|
|||
}
|
||||
}
|
||||
|
||||
WSGI and Google AppEngine
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Static files and aggressive file caching
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Tornado comes with limited support for `WSGI <http://wsgi.org/>`_.
|
||||
However, since WSGI does not support non-blocking requests, you cannot
|
||||
use any of the asynchronous/non-blocking features of Tornado in your
|
||||
application if you choose to use WSGI instead of Tornado's HTTP server.
|
||||
Some of the features that are not available in WSGI applications:
|
||||
``@tornado.web.asynchronous``, the ``httpclient`` module, and the
|
||||
``auth`` module.
|
||||
You can serve static files from Tornado by specifying the
|
||||
``static_path`` setting in your application::
|
||||
|
||||
You can create a valid WSGI application from your Tornado request
|
||||
handlers by using ``WSGIApplication`` in the ``wsgi`` module instead of
|
||||
using ``tornado.web.Application``. Here is an example that uses the
|
||||
built-in WSGI ``CGIHandler`` to make a valid `Google
|
||||
AppEngine <http://code.google.com/appengine/>`_ application:
|
||||
settings = {
|
||||
"static_path": os.path.join(os.path.dirname(__file__), "static"),
|
||||
"cookie_secret": "__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
|
||||
"login_url": "/login",
|
||||
"xsrf_cookies": True,
|
||||
}
|
||||
application = tornado.web.Application([
|
||||
(r"/", MainHandler),
|
||||
(r"/login", LoginHandler),
|
||||
(r"/(apple-touch-icon\.png)", tornado.web.StaticFileHandler,
|
||||
dict(path=settings['static_path'])),
|
||||
], **settings)
|
||||
|
||||
::
|
||||
This setting will automatically make all requests that start with
|
||||
``/static/`` serve from that static directory, e.g.,
|
||||
``http://localhost:8888/static/foo.png`` will serve the file
|
||||
``foo.png`` from the specified static directory. We also automatically
|
||||
serve ``/robots.txt`` and ``/favicon.ico`` from the static directory
|
||||
(even though they don't start with the ``/static/`` prefix).
|
||||
|
||||
In the above settings, we have explicitly configured Tornado to serve
|
||||
``apple-touch-icon.png`` from the root with the `.StaticFileHandler`,
|
||||
though it is physically in the static file directory. (The capturing
|
||||
group in that regular expression is necessary to tell
|
||||
`.StaticFileHandler` the requested filename; recall that capturing
|
||||
groups are passed to handlers as method arguments.) You could do the
|
||||
same thing to serve e.g. ``sitemap.xml`` from the site root. Of
|
||||
course, you can also avoid faking a root ``apple-touch-icon.png`` by
|
||||
using the appropriate ``<link />`` tag in your HTML.
|
||||
|
||||
To improve performance, it is generally a good idea for browsers to
|
||||
cache static resources aggressively so browsers won't send unnecessary
|
||||
``If-Modified-Since`` or ``Etag`` requests that might block the
|
||||
rendering of the page. Tornado supports this out of the box with *static
|
||||
content versioning*.
|
||||
|
||||
To use this feature, use the `~.RequestHandler.static_url` method in
|
||||
your templates rather than typing the URL of the static file directly
|
||||
in your HTML::
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>FriendFeed - {{ _("Home") }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div><img src="{{ static_url("images/logo.png") }}"/></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
The ``static_url()`` function will translate that relative path to a URI
|
||||
that looks like ``/static/images/logo.png?v=aae54``. The ``v`` argument
|
||||
is a hash of the content in ``logo.png``, and its presence makes the
|
||||
Tornado server send cache headers to the user's browser that will make
|
||||
the browser cache the content indefinitely.
|
||||
|
||||
Since the ``v`` argument is based on the content of the file, if you
|
||||
update a file and restart your server, it will start sending a new ``v``
|
||||
value, so the user's browser will automatically fetch the new file. If
|
||||
the file's contents don't change, the browser will continue to use a
|
||||
locally cached copy without ever checking for updates on the server,
|
||||
significantly improving rendering performance.
|
||||
|
||||
In production, you probably want to serve static files from a more
|
||||
optimized static file server like `nginx <http://nginx.net/>`_. You
|
||||
can configure most any web server to recognize the version tags used
|
||||
by ``static_url()`` and set caching headers accordingly. Here is the
|
||||
relevant portion of the nginx configuration we use at FriendFeed::
|
||||
|
||||
location /static/ {
|
||||
root /var/friendfeed/static;
|
||||
if ($query_string) {
|
||||
expires max;
|
||||
}
|
||||
}
|
||||
|
||||
.. _debug-mode:
|
||||
|
||||
Debug mode and automatic reloading
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you pass ``debug=True`` to the ``Application`` constructor, the app
|
||||
will be run in debug/development mode. In this mode, several features
|
||||
intended for convenience while developing will be enabled (each of which
|
||||
is also available as an individual flag; if both are specified the
|
||||
individual flag takes precedence):
|
||||
|
||||
* ``autoreload=True``: The app will watch for changes to its source
|
||||
files and reload itself when anything changes. This reduces the need
|
||||
to manually restart the server during development. However, certain
|
||||
failures (such as syntax errors at import time) can still take the
|
||||
server down in a way that debug mode cannot currently recover from.
|
||||
* ``compiled_template_cache=False``: Templates will not be cached.
|
||||
* ``static_hash_cache=False``: Static file hashes (used by the
|
||||
``static_url`` function) will not be cached
|
||||
* ``serve_traceback=True``: When an exception in a `.RequestHandler`
|
||||
is not caught, an error page including a stack trace will be
|
||||
generated.
|
||||
|
||||
Autoreload mode is not compatible with the multi-process mode of `.HTTPServer`.
|
||||
You must not give `HTTPServer.start <.TCPServer.start>` an argument other than 1 (or
|
||||
call `tornado.process.fork_processes`) if you are using autoreload mode.
|
||||
|
||||
The automatic reloading feature of debug mode is available as a
|
||||
standalone module in `tornado.autoreload`. The two can be used in
|
||||
combination to provide extra robustness against syntax errors: set
|
||||
``autoreload=True`` within the app to detect changes while it is running,
|
||||
and start it with ``python -m tornado.autoreload myserver.py`` to catch
|
||||
any syntax errors or other errors at startup.
|
||||
|
||||
Reloading loses any Python interpreter command-line arguments (e.g. ``-u``)
|
||||
because it re-executes Python using `sys.executable` and `sys.argv`.
|
||||
Additionally, modifying these variables will cause reloading to behave
|
||||
incorrectly.
|
||||
|
||||
On some platforms (including Windows and Mac OSX prior to 10.6), the
|
||||
process cannot be updated "in-place", so when a code change is
|
||||
detected the old server exits and a new one starts. This has been
|
||||
known to confuse some IDEs.
|
||||
|
||||
|
||||
WSGI and Google App Engine
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Tornado is normally intended to be run on its own, without a WSGI
|
||||
container. However, in some environments (such as Google App Engine),
|
||||
only WSGI is allowed and applications cannot run their own servers.
|
||||
In this case Tornado supports a limited mode of operation that does
|
||||
not support asynchronous operation but allows a subset of Tornado's
|
||||
functionality in a WSGI-only environment. The features that are
|
||||
not allowed in WSGI mode include coroutines, the ``@asynchronous``
|
||||
decorator, `.AsyncHTTPClient`, the ``auth`` module, and WebSockets.
|
||||
|
||||
You can convert a Tornado `.Application` to a WSGI application
|
||||
with `tornado.wsgi.WSGIAdapter`. In this example, configure
|
||||
your WSGI container to find the ``application`` object::
|
||||
|
||||
import tornado.web
|
||||
import tornado.wsgi
|
||||
import wsgiref.handlers
|
||||
|
||||
class MainHandler(tornado.web.RequestHandler):
|
||||
def get(self):
|
||||
self.write("Hello, world")
|
||||
|
||||
if __name__ == "__main__":
|
||||
application = tornado.wsgi.WSGIApplication([
|
||||
(r"/", MainHandler),
|
||||
])
|
||||
wsgiref.handlers.CGIHandler().run(application)
|
||||
tornado_app = tornado.web.Application([
|
||||
(r"/", MainHandler),
|
||||
])
|
||||
application = tornado.wsgi.WSGIAdapter(tornado_app)
|
||||
|
||||
See the `appengine example application
|
||||
<https://github.com/tornadoweb/tornado/tree/stable/demos/appengine>`_ for a
|
||||
|
|
|
@ -5,9 +5,7 @@ Cookies and secure cookies
|
|||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can set cookies in the user's browser with the ``set_cookie``
|
||||
method:
|
||||
|
||||
::
|
||||
method::
|
||||
|
||||
class MainHandler(tornado.web.RequestHandler):
|
||||
def get(self):
|
||||
|
@ -17,15 +15,14 @@ method:
|
|||
else:
|
||||
self.write("Your cookie was set!")
|
||||
|
||||
Cookies are easily forged by malicious clients. If you need to set
|
||||
cookies to, e.g., save the user ID of the currently logged in user, you
|
||||
need to sign your cookies to prevent forgery. Tornado supports this out
|
||||
of the box with the ``set_secure_cookie`` and ``get_secure_cookie``
|
||||
methods. To use these methods, you need to specify a secret key named
|
||||
``cookie_secret`` when you create your application. You can pass in
|
||||
application settings as keyword arguments to your application:
|
||||
|
||||
::
|
||||
Cookies are not secure and can easily be modified by clients. If you
|
||||
need to set cookies to, e.g., identify the currently logged in user,
|
||||
you need to sign your cookies to prevent forgery. Tornado supports
|
||||
signed cookies with the `~.RequestHandler.set_secure_cookie` and
|
||||
`~.RequestHandler.get_secure_cookie` methods. To use these methods,
|
||||
you need to specify a secret key named ``cookie_secret`` when you
|
||||
create your application. You can pass in application settings as
|
||||
keyword arguments to your application::
|
||||
|
||||
application = tornado.web.Application([
|
||||
(r"/", MainHandler),
|
||||
|
@ -35,9 +32,7 @@ Signed cookies contain the encoded value of the cookie in addition to a
|
|||
timestamp and an `HMAC <http://en.wikipedia.org/wiki/HMAC>`_ signature.
|
||||
If the cookie is old or if the signature doesn't match,
|
||||
``get_secure_cookie`` will return ``None`` just as if the cookie isn't
|
||||
set. The secure version of the example above:
|
||||
|
||||
::
|
||||
set. The secure version of the example above::
|
||||
|
||||
class MainHandler(tornado.web.RequestHandler):
|
||||
def get(self):
|
||||
|
@ -47,20 +42,34 @@ set. The secure version of the example above:
|
|||
else:
|
||||
self.write("Your cookie was set!")
|
||||
|
||||
Tornado's secure cookies guarantee integrity but not confidentiality.
|
||||
That is, the cookie cannot be modified but its contents can be seen by the
|
||||
user. The ``cookie_secret`` is a symmetric key and must be kept secret --
|
||||
anyone who obtains the value of this key could produce their own signed
|
||||
cookies.
|
||||
|
||||
By default, Tornado's secure cookies expire after 30 days. To change this,
|
||||
use the ``expires_days`` keyword argument to ``set_secure_cookie`` *and* the
|
||||
``max_age_days`` argument to ``set_secure_cookie``. These two values are
|
||||
passed separately so that you may e.g. have a cookie that is valid for 30 days
|
||||
for most purposes, but for certain sensitive actions (such as changing billing
|
||||
information) you use a smaller ``max_age_days`` when reading the cookie.
|
||||
|
||||
.. _user-authentication:
|
||||
|
||||
User authentication
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The currently authenticated user is available in every request handler
|
||||
as ``self.current_user``, and in every template as ``current_user``. By
|
||||
default, ``current_user`` is ``None``.
|
||||
as `self.current_user <.RequestHandler.current_user>`, and in every
|
||||
template as ``current_user``. By default, ``current_user`` is
|
||||
``None``.
|
||||
|
||||
To implement user authentication in your application, you need to
|
||||
override the ``get_current_user()`` method in your request handlers to
|
||||
determine the current user based on, e.g., the value of a cookie. Here
|
||||
is an example that lets users log into the application simply by
|
||||
specifying a nickname, which is then saved in a cookie:
|
||||
|
||||
::
|
||||
specifying a nickname, which is then saved in a cookie::
|
||||
|
||||
class BaseHandler(tornado.web.RequestHandler):
|
||||
def get_current_user(self):
|
||||
|
@ -92,12 +101,10 @@ specifying a nickname, which is then saved in a cookie:
|
|||
|
||||
You can require that the user be logged in using the `Python
|
||||
decorator <http://www.python.org/dev/peps/pep-0318/>`_
|
||||
``tornado.web.authenticated``. If a request goes to a method with this
|
||||
`tornado.web.authenticated`. If a request goes to a method with this
|
||||
decorator, and the user is not logged in, they will be redirected to
|
||||
``login_url`` (another application setting). The example above could be
|
||||
rewritten:
|
||||
|
||||
::
|
||||
rewritten::
|
||||
|
||||
class MainHandler(BaseHandler):
|
||||
@tornado.web.authenticated
|
||||
|
@ -114,15 +121,47 @@ rewritten:
|
|||
(r"/login", LoginHandler),
|
||||
], **settings)
|
||||
|
||||
If you decorate ``post()`` methods with the ``authenticated`` decorator,
|
||||
and the user is not logged in, the server will send a ``403`` response.
|
||||
If you decorate ``post()`` methods with the ``authenticated``
|
||||
decorator, and the user is not logged in, the server will send a
|
||||
``403`` response. The ``@authenticated`` decorator is simply
|
||||
shorthand for ``if not self.current_user: self.redirect()`` and may
|
||||
not be appropriate for non-browser-based login schemes.
|
||||
|
||||
Tornado comes with built-in support for third-party authentication
|
||||
schemes like Google OAuth. See the `tornado.auth`
|
||||
for more details. Check out the `Tornado Blog example application <https://github.com/tornadoweb/tornado/tree/stable/demos/blog>`_ for a
|
||||
Check out the `Tornado Blog example application
|
||||
<https://github.com/tornadoweb/tornado/tree/stable/demos/blog>`_ for a
|
||||
complete example that uses authentication (and stores user data in a
|
||||
MySQL database).
|
||||
|
||||
Third party authentication
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The `tornado.auth` module implements the authentication and
|
||||
authorization protocols for a number of the most popular sites on the
|
||||
web, including Google/Gmail, Facebook, Twitter, and FriendFeed.
|
||||
The module includes methods to log users in via these sites and, where
|
||||
applicable, methods to authorize access to the service so you can, e.g.,
|
||||
download a user's address book or publish a Twitter message on their
|
||||
behalf.
|
||||
|
||||
Here is an example handler that uses Google for authentication, saving
|
||||
the Google credentials in a cookie for later access::
|
||||
|
||||
class GoogleHandler(tornado.web.RequestHandler, tornado.auth.GoogleMixin):
|
||||
@tornado.web.asynchronous
|
||||
def get(self):
|
||||
if self.get_argument("openid.mode", None):
|
||||
self.get_authenticated_user(self._on_auth)
|
||||
return
|
||||
self.authenticate_redirect()
|
||||
|
||||
def _on_auth(self, user):
|
||||
if not user:
|
||||
self.authenticate_redirect()
|
||||
return
|
||||
# Save the user with, e.g., set_secure_cookie()
|
||||
|
||||
See the `tornado.auth` module documentation for more details.
|
||||
|
||||
.. _xsrf:
|
||||
|
||||
Cross-site request forgery protection
|
||||
|
@ -142,9 +181,7 @@ value in the form submission do not match, then the request is likely
|
|||
forged.
|
||||
|
||||
Tornado comes with built-in XSRF protection. To include it in your site,
|
||||
include the application setting ``xsrf_cookies``:
|
||||
|
||||
::
|
||||
include the application setting ``xsrf_cookies``::
|
||||
|
||||
settings = {
|
||||
"cookie_secret": "__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
|
||||
|
@ -161,9 +198,7 @@ If ``xsrf_cookies`` is set, the Tornado web application will set the
|
|||
``DELETE`` requests that do not contain a correct ``_xsrf`` value. If
|
||||
you turn this setting on, you need to instrument all forms that submit
|
||||
via ``POST`` to contain this field. You can do this with the special
|
||||
function ``xsrf_form_html()``, available in all templates:
|
||||
|
||||
::
|
||||
`.UIModule` ``xsrf_form_html()``, available in all templates::
|
||||
|
||||
<form action="/new_message" method="post">
|
||||
{% module xsrf_form_html() %}
|
||||
|
@ -175,9 +210,7 @@ If you submit AJAX ``POST`` requests, you will also need to instrument
|
|||
your JavaScript to include the ``_xsrf`` value with each request. This
|
||||
is the `jQuery <http://jquery.com/>`_ function we use at FriendFeed for
|
||||
AJAX ``POST`` requests that automatically adds the ``_xsrf`` value to
|
||||
all requests:
|
||||
|
||||
::
|
||||
all requests::
|
||||
|
||||
function getCookie(name) {
|
||||
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
|
||||
|
@ -201,41 +234,9 @@ that does not use any regular forms you may need to access
|
|||
set the cookie as a side effect).
|
||||
|
||||
If you need to customize XSRF behavior on a per-handler basis, you can
|
||||
override ``RequestHandler.check_xsrf_cookie()``. For example, if you
|
||||
override `.RequestHandler.check_xsrf_cookie()`. For example, if you
|
||||
have an API whose authentication does not use cookies, you may want to
|
||||
disable XSRF protection by making ``check_xsrf_cookie()`` do nothing.
|
||||
However, if you support both cookie and non-cookie-based authentication,
|
||||
it is important that XSRF protection be used whenever the current
|
||||
request is authenticated with a cookie.
|
||||
|
||||
Third party authentication
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Tornado's ``auth`` module implements the authentication and
|
||||
authorization protocols for a number of the most popular sites on the
|
||||
web, including Google/Gmail, Facebook, Twitter, and FriendFeed.
|
||||
The module includes methods to log users in via these sites and, where
|
||||
applicable, methods to authorize access to the service so you can, e.g.,
|
||||
download a user's address book or publish a Twitter message on their
|
||||
behalf.
|
||||
|
||||
Here is an example handler that uses Google for authentication, saving
|
||||
the Google credentials in a cookie for later access:
|
||||
|
||||
::
|
||||
|
||||
class GoogleHandler(tornado.web.RequestHandler, tornado.auth.GoogleMixin):
|
||||
@tornado.web.asynchronous
|
||||
def get(self):
|
||||
if self.get_argument("openid.mode", None):
|
||||
self.get_authenticated_user(self._on_auth)
|
||||
return
|
||||
self.authenticate_redirect()
|
||||
|
||||
def _on_auth(self, user):
|
||||
if not user:
|
||||
self.authenticate_redirect()
|
||||
return
|
||||
# Save the user with, e.g., set_secure_cookie()
|
||||
|
||||
See the `tornado.auth` module documentation for more details.
|
||||
|
|
|
@ -3,234 +3,306 @@
|
|||
Structure of a Tornado web application
|
||||
======================================
|
||||
|
||||
Request handlers and request arguments
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
A Tornado web application generally consists of one or more
|
||||
`.RequestHandler` subclasses, an `.Application` object which
|
||||
routes incoming requests to handlers, and a ``main()`` function
|
||||
to start the server.
|
||||
|
||||
A Tornado web application maps URLs or URL patterns to subclasses of
|
||||
`tornado.web.RequestHandler`. Those classes define ``get()`` or
|
||||
``post()`` methods to handle HTTP ``GET`` or ``POST`` requests to that
|
||||
URL.
|
||||
A minimal "hello world" example looks something like this::
|
||||
|
||||
This code maps the root URL ``/`` to ``MainHandler`` and the URL pattern
|
||||
``/story/([0-9]+)`` to ``StoryHandler``. Regular expression groups are
|
||||
passed as arguments to the ``RequestHandler`` methods:
|
||||
from tornado.ioloop import IOLoop
|
||||
from tornado.web import RequestHandler, Application, url
|
||||
|
||||
class HelloHandler(RequestHandler):
|
||||
def get(self):
|
||||
self.write("Hello, world")
|
||||
|
||||
def make_app():
|
||||
return Application([
|
||||
url(r"/", HelloHandler),
|
||||
])
|
||||
|
||||
def main():
|
||||
app = make_app()
|
||||
app.listen(8888)
|
||||
IOLoop.current().start()
|
||||
|
||||
The ``Application`` object
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The `.Application` object is responsible for global configuration, including
|
||||
the routing table that maps requests to handlers.
|
||||
|
||||
The routing table is a list of `.URLSpec` objects (or tuples), each of
|
||||
which contains (at least) a regular expression and a handler class.
|
||||
Order matters; the first matching rule is used. If the regular
|
||||
expression contains capturing groups, these groups are the *path
|
||||
arguments* and will be passed to the handler's HTTP method. If a
|
||||
dictionary is passed as the third element of the `.URLSpec`, it
|
||||
supplies the *initialization arguments* which will be passed to
|
||||
`.RequestHandler.initialize`. Finally, the `.URLSpec` may have a
|
||||
name, which will allow it to be used with
|
||||
`.RequestHandler.reverse_url`.
|
||||
|
||||
For example, in this fragment the root URL ``/`` is mapped to
|
||||
``MainHandler`` and URLs of the form ``/story/`` followed by a number
|
||||
are mapped to ``StoryHandler``. That number is passed (as a string) to
|
||||
``StoryHandler.get``.
|
||||
|
||||
::
|
||||
|
||||
class MainHandler(tornado.web.RequestHandler):
|
||||
class MainHandler(RequestHandler):
|
||||
def get(self):
|
||||
self.write("You requested the main page")
|
||||
self.write('<a href="%s">link to story 1</a>' %
|
||||
self.reverse_url("story", "1"))
|
||||
|
||||
class StoryHandler(RequestHandler):
|
||||
def initialize(self, db):
|
||||
self.db = db
|
||||
|
||||
class StoryHandler(tornado.web.RequestHandler):
|
||||
def get(self, story_id):
|
||||
self.write("You requested the story " + story_id)
|
||||
self.write("this is story %s" % story_id)
|
||||
|
||||
application = tornado.web.Application([
|
||||
(r"/", MainHandler),
|
||||
(r"/story/([0-9]+)", StoryHandler),
|
||||
])
|
||||
app = Application([
|
||||
url(r"/", MainHandler),
|
||||
url(r"/story/([0-9]+)", StoryHandler, dict(db=db), name="story")
|
||||
])
|
||||
|
||||
You can get query string arguments and parse ``POST`` bodies with the
|
||||
``get_argument()`` method:
|
||||
The `.Application` constructor takes many keyword arguments that
|
||||
can be used to customize the behavior of the application and enable
|
||||
optional features; see `.Application.settings` for the complete list.
|
||||
|
||||
Subclassing ``RequestHandler``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Most of the work of a Tornado web application is done in subclasses
|
||||
of `.RequestHandler`. The main entry point for a handler subclass
|
||||
is a method named after the HTTP method being handled: ``get()``,
|
||||
``post()``, etc. Each handler may define one or more of these methods
|
||||
to handle different HTTP actions. As described above, these methods
|
||||
will be called with arguments corresponding to the capturing groups
|
||||
of the routing rule that matched.
|
||||
|
||||
Within a handler, call methods such as `.RequestHandler.render` or
|
||||
`.RequestHandler.write` to produce a response. ``render()`` loads a
|
||||
`.Template` by name and renders it with the given
|
||||
arguments. ``write()`` is used for non-template-based output; it
|
||||
accepts strings, bytes, and dictionaries (dicts will be encoded as
|
||||
JSON).
|
||||
|
||||
Many methods in `.RequestHandler` are designed to be overridden in
|
||||
subclasses and be used throughout the application. It is common
|
||||
to define a ``BaseHandler`` class that overrides methods such as
|
||||
`~.RequestHandler.write_error` and `~.RequestHandler.get_current_user`
|
||||
and then subclass your own ``BaseHandler`` instead of `.RequestHandler`
|
||||
for all your specific handlers.
|
||||
|
||||
Handling request input
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The request handler can access the object representing the current
|
||||
request with ``self.request``. See the class definition for
|
||||
`~tornado.httputil.HTTPServerRequest` for a complete list of
|
||||
attributes.
|
||||
|
||||
Request data in the formats used by HTML forms will be parsed for you
|
||||
and is made available in methods like `~.RequestHandler.get_query_argument`
|
||||
and `~.RequestHandler.get_body_argument`.
|
||||
|
||||
::
|
||||
|
||||
class MyFormHandler(tornado.web.RequestHandler):
|
||||
class MyFormHandler(RequestHandler):
|
||||
def get(self):
|
||||
self.write('<html><body><form action="/myform" method="post">'
|
||||
self.write('<html><body><form action="/myform" method="POST">'
|
||||
'<input type="text" name="message">'
|
||||
'<input type="submit" value="Submit">'
|
||||
'</form></body></html>')
|
||||
|
||||
def post(self):
|
||||
self.set_header("Content-Type", "text/plain")
|
||||
self.write("You wrote " + self.get_argument("message"))
|
||||
self.write("You wrote " + self.get_body_argument("message"))
|
||||
|
||||
Uploaded files are available in ``self.request.files``, which maps names
|
||||
(the name of the HTML ``<input type="file">`` element) to a list of
|
||||
files. Each file is a dictionary of the form
|
||||
``{"filename":..., "content_type":..., "body":...}``.
|
||||
Since the HTML form encoding is ambiguous as to whether an argument is
|
||||
a single value or a list with one element, `.RequestHandler` has
|
||||
distinct methods to allow the application to indicate whether or not
|
||||
it expects a list. For lists, use
|
||||
`~.RequestHandler.get_query_arguments` and
|
||||
`~.RequestHandler.get_body_arguments` instead of their singular
|
||||
counterparts.
|
||||
|
||||
If you want to send an error response to the client, e.g., 403
|
||||
Unauthorized, you can just raise a ``tornado.web.HTTPError`` exception:
|
||||
Files uploaded via a form are available in ``self.request.files``,
|
||||
which maps names (the name of the HTML ``<input type="file">``
|
||||
element) to a list of files. Each file is a dictionary of the form
|
||||
``{"filename":..., "content_type":..., "body":...}``. The ``files``
|
||||
object is only present if the files were uploaded with a form wrapper
|
||||
(i.e. a ``multipart/form-data`` Content-Type); if this format was not used
|
||||
the raw uploaded data is available in ``self.request.body``.
|
||||
By default uploaded files are fully buffered in memory; if you need to
|
||||
handle files that are too large to comfortable keep in memory see the
|
||||
`.stream_request_body` class decorator.
|
||||
|
||||
::
|
||||
Due to the quirks of the HTML form encoding (e.g. the ambiguity around
|
||||
singular versus plural arguments), Tornado does not attempt to unify
|
||||
form arguments with other types of input. In particular, we do not
|
||||
parse JSON request bodies. Applications that wish to use JSON instead
|
||||
of form-encoding may override `~.RequestHandler.prepare` to parse their
|
||||
requests::
|
||||
|
||||
if not self.user_is_logged_in():
|
||||
raise tornado.web.HTTPError(403)
|
||||
|
||||
The request handler can access the object representing the current
|
||||
request with ``self.request``. The ``HTTPRequest`` object includes a
|
||||
number of useful attributes, including:
|
||||
|
||||
- ``arguments`` - all of the ``GET`` and ``POST`` arguments
|
||||
- ``files`` - all of the uploaded files (via ``multipart/form-data``
|
||||
POST requests)
|
||||
- ``path`` - the request path (everything before the ``?``)
|
||||
- ``headers`` - the request headers
|
||||
|
||||
See the class definition for `tornado.httputil.HTTPServerRequest` for a
|
||||
complete list of attributes.
|
||||
def prepare(self):
|
||||
if self.request.headers["Content-Type"].startswith("application/json"):
|
||||
self.json_args = json.loads(self.request.body)
|
||||
else:
|
||||
self.json_args = None
|
||||
|
||||
Overriding RequestHandler methods
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In addition to ``get()``/``post()``/etc, certain other methods in
|
||||
``RequestHandler`` are designed to be overridden by subclasses when
|
||||
`.RequestHandler` are designed to be overridden by subclasses when
|
||||
necessary. On every request, the following sequence of calls takes
|
||||
place:
|
||||
|
||||
1. A new RequestHandler object is created on each request
|
||||
2. ``initialize()`` is called with keyword arguments from the
|
||||
``Application`` configuration. (the ``initialize`` method is new in
|
||||
Tornado 1.1; in older versions subclasses would override ``__init__``
|
||||
instead). ``initialize`` should typically just save the arguments
|
||||
passed into member variables; it may not produce any output or call
|
||||
methods like ``send_error``.
|
||||
3. ``prepare()`` is called. This is most useful in a base class shared
|
||||
by all of your handler subclasses, as ``prepare`` is called no matter
|
||||
which HTTP method is used. ``prepare`` may produce output; if it
|
||||
calls ``finish`` (or ``send_error``, etc), processing stops here.
|
||||
1. A new `.RequestHandler` object is created on each request
|
||||
2. `~.RequestHandler.initialize()` is called with the initalization
|
||||
arguments from the `.Application` configuration. ``initialize``
|
||||
should typically just save the arguments passed into member
|
||||
variables; it may not produce any output or call methods like
|
||||
`~.RequestHandler.send_error`.
|
||||
3. `~.RequestHandler.prepare()` is called. This is most useful in a
|
||||
base class shared by all of your handler subclasses, as ``prepare``
|
||||
is called no matter which HTTP method is used. ``prepare`` may
|
||||
produce output; if it calls `~.RequestHandler.finish` (or
|
||||
``redirect``, etc), processing stops here.
|
||||
4. One of the HTTP methods is called: ``get()``, ``post()``, ``put()``,
|
||||
etc. If the URL regular expression contains capturing groups, they
|
||||
are passed as arguments to this method.
|
||||
5. When the request is finished, ``on_finish()`` is called. For synchronous
|
||||
handlers this is immediately after ``get()`` (etc) return; for
|
||||
asynchronous handlers it is after the call to ``finish()``.
|
||||
5. When the request is finished, `~.RequestHandler.on_finish()` is
|
||||
called. For synchronous handlers this is immediately after
|
||||
``get()`` (etc) return; for asynchronous handlers it is after the
|
||||
call to `~.RequestHandler.finish()`.
|
||||
|
||||
Here is an example demonstrating the ``initialize()`` method:
|
||||
All methods designed to be overridden are noted as such in the
|
||||
`.RequestHandler` documentation. Some of the most commonly
|
||||
overridden methods include:
|
||||
|
||||
::
|
||||
|
||||
class ProfileHandler(RequestHandler):
|
||||
def initialize(self, database):
|
||||
self.database = database
|
||||
|
||||
def get(self, username):
|
||||
...
|
||||
|
||||
app = Application([
|
||||
(r'/user/(.*)', ProfileHandler, dict(database=database)),
|
||||
])
|
||||
|
||||
Other methods designed for overriding include:
|
||||
|
||||
- ``write_error(self, status_code, exc_info=None, **kwargs)`` -
|
||||
outputs HTML for use on error pages.
|
||||
- ``get_current_user(self)`` - see `User
|
||||
Authentication <#user-authentication>`_ below
|
||||
- ``get_user_locale(self)`` - returns ``locale`` object to use for the
|
||||
current user
|
||||
- ``get_login_url(self)`` - returns login url to be used by the
|
||||
``@authenticated`` decorator (default is in ``Application`` settings)
|
||||
- ``get_template_path(self)`` - returns location of template files
|
||||
(default is in ``Application`` settings)
|
||||
- ``set_default_headers(self)`` - may be used to set additional headers
|
||||
on the response (such as a custom ``Server`` header)
|
||||
- `~.RequestHandler.write_error` -
|
||||
outputs HTML for use on error pages.
|
||||
- `~.RequestHandler.on_connection_close` - called when the client
|
||||
disconnects; applications may choose to detect this case and halt
|
||||
further processing. Note that there is no guarantee that a closed
|
||||
connection can be detected promptly.
|
||||
- `~.RequestHandler.get_current_user` - see :ref:`user-authentication`
|
||||
- `~.RequestHandler.get_user_locale` - returns `.Locale` object to use
|
||||
for the current user
|
||||
- `~.RequestHandler.set_default_headers` - may be used to set
|
||||
additional headers on the response (such as a custom ``Server``
|
||||
header)
|
||||
|
||||
Error Handling
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
There are three ways to return an error from a `RequestHandler`:
|
||||
If a handler raises an exception, Tornado will call
|
||||
`.RequestHandler.write_error` to generate an error page.
|
||||
`tornado.web.HTTPError` can be used to generate a specified status
|
||||
code; all other exceptions return a 500 status.
|
||||
|
||||
1. Manually call `~tornado.web.RequestHandler.set_status` and output the
|
||||
response body normally.
|
||||
2. Call `~RequestHandler.send_error`. This discards
|
||||
any pending unflushed output and calls `~RequestHandler.write_error` to
|
||||
generate an error page.
|
||||
3. Raise an exception. `tornado.web.HTTPError` can be used to generate
|
||||
a specified status code; all other exceptions return a 500 status.
|
||||
The exception handler uses `~RequestHandler.send_error` and
|
||||
`~RequestHandler.write_error` to generate the error page.
|
||||
The default error page includes a stack trace in debug mode and a
|
||||
one-line description of the error (e.g. "500: Internal Server Error")
|
||||
otherwise. To produce a custom error page, override
|
||||
`RequestHandler.write_error` (probably in a base class shared by all
|
||||
your handlers). This method may produce output normally via
|
||||
methods such as `~RequestHandler.write` and `~RequestHandler.render`.
|
||||
If the error was caused by an exception, an ``exc_info`` triple will
|
||||
be passed as a keyword argument (note that this exception is not
|
||||
guaranteed to be the current exception in `sys.exc_info`, so
|
||||
``write_error`` must use e.g. `traceback.format_exception` instead of
|
||||
`traceback.format_exc`).
|
||||
|
||||
The default error page includes a stack trace in debug mode and a one-line
|
||||
description of the error (e.g. "500: Internal Server Error") otherwise.
|
||||
To produce a custom error page, override `RequestHandler.write_error`.
|
||||
This method may produce output normally via methods such as
|
||||
`~RequestHandler.write` and `~RequestHandler.render`. If the error was
|
||||
caused by an exception, an ``exc_info`` triple will be passed as a keyword
|
||||
argument (note that this exception is not guaranteed to be the current
|
||||
exception in ``sys.exc_info``, so ``write_error`` must use e.g.
|
||||
`traceback.format_exception` instead of `traceback.format_exc`).
|
||||
It is also possible to generate an error page from regular handler
|
||||
methods instead of ``write_error`` by calling
|
||||
`~.RequestHandler.set_status`, writing a response, and returning.
|
||||
The special exception `tornado.web.Finish` may be raised to terminate
|
||||
the handler without calling ``write_error`` in situations where simply
|
||||
returning is not convenient.
|
||||
|
||||
For 404 errors, use the ``default_handler_class`` `Application setting
|
||||
<.Application.settings>`. This handler should override
|
||||
`~.RequestHandler.prepare` instead of a more specific method like
|
||||
``get()`` so it works with any HTTP method. It should produce its
|
||||
error page as described above: either by raising a ``HTTPError(404)``
|
||||
and overriding ``write_error``, or calling ``self.set_status(404)``
|
||||
and producing the response directly in ``prepare()``.
|
||||
|
||||
Redirection
|
||||
~~~~~~~~~~~
|
||||
|
||||
There are two main ways you can redirect requests in Tornado:
|
||||
``self.redirect`` and with the ``RedirectHandler``.
|
||||
`.RequestHandler.redirect` and with the `.RedirectHandler`.
|
||||
|
||||
You can use ``self.redirect`` within a ``RequestHandler`` method (like
|
||||
``get``) to redirect users elsewhere. There is also an optional
|
||||
parameter ``permanent`` which you can use to indicate that the
|
||||
redirection is considered permanent.
|
||||
|
||||
This triggers a ``301 Moved Permanently`` HTTP status, which is useful
|
||||
for e.g. redirecting to a canonical URL for a page in an SEO-friendly
|
||||
You can use ``self.redirect()`` within a `.RequestHandler` method to
|
||||
redirect users elsewhere. There is also an optional parameter
|
||||
``permanent`` which you can use to indicate that the redirection is
|
||||
considered permanent. The default value of ``permanent`` is
|
||||
``False``, which generates a ``302 Found`` HTTP response code and is
|
||||
appropriate for things like redirecting users after successful
|
||||
``POST`` requests. If ``permanent`` is true, the ``301 Moved
|
||||
Permanently`` HTTP response code is used, which is useful for
|
||||
e.g. redirecting to a canonical URL for a page in an SEO-friendly
|
||||
manner.
|
||||
|
||||
The default value of ``permanent`` is ``False``, which is apt for things
|
||||
like redirecting users on successful POST requests.
|
||||
`.RedirectHandler` lets you configure redirects directly in your
|
||||
`.Application` routing table. For example, to configure a single
|
||||
static redirect::
|
||||
|
||||
::
|
||||
app = tornado.web.Application([
|
||||
url(r"/app", tornado.web.RedirectHandler,
|
||||
dict(url="http://itunes.apple.com/my-app-id")),
|
||||
])
|
||||
|
||||
self.redirect('/some-canonical-page', permanent=True)
|
||||
`.RedirectHandler` also supports regular expression substitutions.
|
||||
The following rule redirects all requests beginning with ``/pictures/``
|
||||
to the prefix ``/photos/`` instead::
|
||||
|
||||
``RedirectHandler`` is available for your use when you initialize
|
||||
``Application``.
|
||||
app = tornado.web.Application([
|
||||
url(r"/photos/(.*)", MyPhotoHandler),
|
||||
url(r"/pictures/(.*)", tornado.web.RedirectHandler,
|
||||
dict(url=r"/photos/\1")),
|
||||
])
|
||||
|
||||
For example, notice how we redirect to a longer download URL on this
|
||||
website:
|
||||
Unlike `.RequestHandler.redirect`, `.RedirectHandler` uses permanent
|
||||
redirects by default. This is because the routing table does not change
|
||||
at runtime and is presumed to be permanent, while redirects found in
|
||||
handlers are likely to be the result of other logic that may change.
|
||||
To send a temporary redirect with a `.RedirectHandler`, add
|
||||
``permanent=False`` to the `.RedirectHandler` initialization arguments.
|
||||
|
||||
::
|
||||
Asynchronous handlers
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
application = tornado.wsgi.WSGIApplication([
|
||||
(r"/([a-z]*)", ContentHandler),
|
||||
(r"/static/tornado-0.2.tar.gz", tornado.web.RedirectHandler,
|
||||
dict(url="https://github.com/downloads/facebook/tornado/tornado-0.2.tar.gz")),
|
||||
], **settings)
|
||||
Tornado handlers are synchronous by default: when the
|
||||
``get()``/``post()`` method returns, the request is considered
|
||||
finished and the response is sent. Since all other requests are
|
||||
blocked while one handler is running, any long-running handler should
|
||||
be made asynchronous so it can call its slow operations in a
|
||||
non-blocking way. This topic is covered in more detail in
|
||||
:doc:`async`; this section is about the particulars of
|
||||
asynchronous techniques in `.RequestHandler` subclasses.
|
||||
|
||||
The default ``RedirectHandler`` status code is
|
||||
``301 Moved Permanently``, but to use ``302 Found`` instead, set
|
||||
``permanent`` to ``False``.
|
||||
The simplest way to make a handler asynchronous is to use the
|
||||
`.coroutine` decorator. This allows you to perform non-blocking I/O
|
||||
with the ``yield`` keyword, and no response will be sent until the
|
||||
coroutine has returned. See :doc:`coroutines` for more details.
|
||||
|
||||
::
|
||||
In some cases, coroutines may be less convenient thatn a
|
||||
callback-oriented style, in which case the `.tornado.web.asynchronous`
|
||||
decorator can be used instead. When this decorator is used the response
|
||||
is not automatically sent; instead the request will be kept open until
|
||||
some callback calls `.RequestHandler.finish`. It is up to the application
|
||||
to ensure that this method is called, or else the user's browser will
|
||||
simply hang.
|
||||
|
||||
application = tornado.wsgi.WSGIApplication([
|
||||
(r"/foo", tornado.web.RedirectHandler, {"url":"/bar", "permanent":False}),
|
||||
], **settings)
|
||||
|
||||
Note that the default value of ``permanent`` is different in
|
||||
``self.redirect`` than in ``RedirectHandler``. This should make some
|
||||
sense if you consider that ``self.redirect`` is used in your methods and
|
||||
is probably invoked by logic involving environment, authentication, or
|
||||
form submission, but ``RedirectHandler`` patterns are going to fire 100%
|
||||
of the time they match the request URL.
|
||||
|
||||
Non-blocking, asynchronous requests
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When a request handler is executed, the request is automatically
|
||||
finished. Since Tornado uses a non-blocking I/O style, you can override
|
||||
this default behavior if you want a request to remain open after the
|
||||
main request handler method returns using the
|
||||
``tornado.web.asynchronous`` decorator.
|
||||
|
||||
When you use this decorator, it is your responsibility to call
|
||||
``self.finish()`` to finish the HTTP request, or the user's browser will
|
||||
simply hang:
|
||||
|
||||
::
|
||||
|
||||
class MainHandler(tornado.web.RequestHandler):
|
||||
@tornado.web.asynchronous
|
||||
def get(self):
|
||||
self.write("Hello, world")
|
||||
self.finish()
|
||||
|
||||
Here is a real example that makes a call to the FriendFeed API using
|
||||
Tornado's built-in asynchronous HTTP client:
|
||||
|
||||
::
|
||||
Here is an example that makes a call to the FriendFeed API using
|
||||
Tornado's built-in `.AsyncHTTPClient`::
|
||||
|
||||
class MainHandler(tornado.web.RequestHandler):
|
||||
@tornado.web.asynchronous
|
||||
|
@ -251,6 +323,17 @@ client eventually calls ``on_response()``, the request is still open,
|
|||
and the response is finally flushed to the client with the call to
|
||||
``self.finish()``.
|
||||
|
||||
For comparison, here is the same example using a coroutine::
|
||||
|
||||
class MainHandler(tornado.web.RequestHandler):
|
||||
@tornado.gen.coroutine
|
||||
def get(self):
|
||||
http = tornado.httpclient.AsyncHTTPClient()
|
||||
yield http.fetch("http://friendfeed-api.com/v2/feed/bret")
|
||||
json = tornado.escape.json_decode(response.body)
|
||||
self.write("Fetched " + str(len(json["entries"])) + " entries "
|
||||
"from the FriendFeed API")
|
||||
|
||||
For a more advanced asynchronous example, take a look at the `chat
|
||||
example application
|
||||
<https://github.com/tornadoweb/tornado/tree/stable/demos/chat>`_, which
|
||||
|
@ -259,25 +342,3 @@ implements an AJAX chat room using `long polling
|
|||
of long polling may want to override ``on_connection_close()`` to
|
||||
clean up after the client closes the connection (but see that method's
|
||||
docstring for caveats).
|
||||
|
||||
Asynchronous HTTP clients
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Tornado includes two non-blocking HTTP client implementations:
|
||||
``SimpleAsyncHTTPClient`` and ``CurlAsyncHTTPClient``. The simple client
|
||||
has no external dependencies because it is implemented directly on top
|
||||
of Tornado's ``IOLoop``. The Curl client requires that ``libcurl`` and
|
||||
``pycurl`` be installed (and a recent version of each is highly
|
||||
recommended to avoid bugs in older version's asynchronous interfaces),
|
||||
but is more likely to be compatible with sites that exercise little-used
|
||||
parts of the HTTP specification.
|
||||
|
||||
Each of these clients is available in its own module
|
||||
(``tornado.simple_httpclient`` and ``tornado.curl_httpclient``), as well
|
||||
as via a configurable alias in ``tornado.httpclient``.
|
||||
``SimpleAsyncHTTPClient`` is the default, but to use a different
|
||||
implementation call the ``AsyncHTTPClient.configure`` method at startup:
|
||||
|
||||
::
|
||||
|
||||
AsyncHTTPClient.configure('tornado.curl_httpclient.CurlAsyncHTTPClient')
|
||||
|
|
|
@ -1,19 +1,39 @@
|
|||
Templates and UI
|
||||
================
|
||||
|
||||
Tornado includes a simple, fast, and flexible templating language.
|
||||
This section describes that language as well as related issues
|
||||
such as internationalization.
|
||||
|
||||
Templates
|
||||
~~~~~~~~~
|
||||
Tornado can also be used with any other Python template language,
|
||||
although there is no provision for integrating these systems into
|
||||
`.RequestHandler.render`. Simply render the template to a string
|
||||
and pass it to `.RequestHandler.write`
|
||||
|
||||
You can use any template language supported by Python, but Tornado ships
|
||||
with its own templating language that is a lot faster and more flexible
|
||||
than many of the most popular templating systems out there. See the
|
||||
`tornado.template` module documentation for complete documentation.
|
||||
Configuring templates
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
By default, Tornado looks for template files in the same directory as
|
||||
the ``.py`` files that refer to them. To put your template files in a
|
||||
different directory, use the ``template_path`` `Application setting
|
||||
<.Application.settings>` (or override `.RequestHandler.get_template_path`
|
||||
if you have different template paths for different handlers).
|
||||
|
||||
To load templates from a non-filesystem location, subclass
|
||||
`tornado.template.BaseLoader` and pass an instance as the
|
||||
``template_loader`` application setting.
|
||||
|
||||
Compiled templates are cached by default; to turn off this caching
|
||||
and reload templates so changes to the underlying files are always
|
||||
visible, use the application settings ``compiled_template_cache=False``
|
||||
or ``debug=True``.
|
||||
|
||||
|
||||
Template syntax
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
A Tornado template is just HTML (or any other text-based format) with
|
||||
Python control sequences and expressions embedded within the markup:
|
||||
|
||||
::
|
||||
Python control sequences and expressions embedded within the markup::
|
||||
|
||||
<html>
|
||||
<head>
|
||||
|
@ -51,35 +71,39 @@ detail in the documentation for the `tornado.template`.
|
|||
|
||||
Expressions can be any Python expression, including function calls.
|
||||
Template code is executed in a namespace that includes the following
|
||||
objects and functions (Note that this list applies to templates rendered
|
||||
using ``RequestHandler.render`` and ``render_string``. If you're using
|
||||
the ``template`` module directly outside of a ``RequestHandler`` many of
|
||||
these entries are not present).
|
||||
objects and functions (Note that this list applies to templates
|
||||
rendered using `.RequestHandler.render` and
|
||||
`~.RequestHandler.render_string`. If you're using the
|
||||
`tornado.template` module directly outside of a `.RequestHandler` many
|
||||
of these entries are not present).
|
||||
|
||||
- ``escape``: alias for ``tornado.escape.xhtml_escape``
|
||||
- ``xhtml_escape``: alias for ``tornado.escape.xhtml_escape``
|
||||
- ``url_escape``: alias for ``tornado.escape.url_escape``
|
||||
- ``json_encode``: alias for ``tornado.escape.json_encode``
|
||||
- ``squeeze``: alias for ``tornado.escape.squeeze``
|
||||
- ``linkify``: alias for ``tornado.escape.linkify``
|
||||
- ``datetime``: the Python ``datetime`` module
|
||||
- ``handler``: the current ``RequestHandler`` object
|
||||
- ``request``: alias for ``handler.request``
|
||||
- ``current_user``: alias for ``handler.current_user``
|
||||
- ``locale``: alias for ``handler.locale``
|
||||
- ``_``: alias for ``handler.locale.translate``
|
||||
- ``static_url``: alias for ``handler.static_url``
|
||||
- ``xsrf_form_html``: alias for ``handler.xsrf_form_html``
|
||||
- ``reverse_url``: alias for ``Application.reverse_url``
|
||||
- All entries from the ``ui_methods`` and ``ui_modules``
|
||||
``Application`` settings
|
||||
- Any keyword arguments passed to ``render`` or ``render_string``
|
||||
- ``escape``: alias for `tornado.escape.xhtml_escape`
|
||||
- ``xhtml_escape``: alias for `tornado.escape.xhtml_escape`
|
||||
- ``url_escape``: alias for `tornado.escape.url_escape`
|
||||
- ``json_encode``: alias for `tornado.escape.json_encode`
|
||||
- ``squeeze``: alias for `tornado.escape.squeeze`
|
||||
- ``linkify``: alias for `tornado.escape.linkify`
|
||||
- ``datetime``: the Python `datetime` module
|
||||
- ``handler``: the current `.RequestHandler` object
|
||||
- ``request``: alias for `handler.request <.HTTPServerRequest>`
|
||||
- ``current_user``: alias for `handler.current_user
|
||||
<.RequestHandler.current_user>`
|
||||
- ``locale``: alias for `handler.locale <.Locale>`
|
||||
- ``_``: alias for `handler.locale.translate <.Locale.translate>`
|
||||
- ``static_url``: alias for `handler.static_url <.RequestHandler.static_url>`
|
||||
- ``xsrf_form_html``: alias for `handler.xsrf_form_html
|
||||
<.RequestHandler.xsrf_form_html>`
|
||||
- ``reverse_url``: alias for `.Application.reverse_url`
|
||||
- All entries from the ``ui_methods`` and ``ui_modules``
|
||||
``Application`` settings
|
||||
- Any keyword arguments passed to `~.RequestHandler.render` or
|
||||
`~.RequestHandler.render_string`
|
||||
|
||||
When you are building a real application, you are going to want to use
|
||||
all of the features of Tornado templates, especially template
|
||||
inheritance. Read all about those features in the `tornado.template`
|
||||
section (some features, including ``UIModules`` are implemented in the
|
||||
``web`` module)
|
||||
`tornado.web` module)
|
||||
|
||||
Under the hood, Tornado templates are translated directly to Python. The
|
||||
expressions you include in your template are copied verbatim into a
|
||||
|
@ -91,9 +115,9 @@ expressions, you will get random Python errors when you execute the
|
|||
template.
|
||||
|
||||
All template output is escaped by default, using the
|
||||
``tornado.escape.xhtml_escape`` function. This behavior can be changed
|
||||
globally by passing ``autoescape=None`` to the ``Application`` or
|
||||
``TemplateLoader`` constructors, for a template file with the
|
||||
`tornado.escape.xhtml_escape` function. This behavior can be changed
|
||||
globally by passing ``autoescape=None`` to the `.Application` or
|
||||
`.tornado.template.Loader` constructors, for a template file with the
|
||||
``{% autoescape None %}`` directive, or for a single expression by
|
||||
replacing ``{{ ... }}`` with ``{% raw ...%}``. Additionally, in each of
|
||||
these places the name of an alternative escaping function may be used
|
||||
|
@ -103,28 +127,24 @@ Note that while Tornado's automatic escaping is helpful in avoiding
|
|||
XSS vulnerabilities, it is not sufficient in all cases. Expressions
|
||||
that appear in certain locations, such as in Javascript or CSS, may need
|
||||
additional escaping. Additionally, either care must be taken to always
|
||||
use double quotes and ``xhtml_escape`` in HTML attributes that may contain
|
||||
use double quotes and `.xhtml_escape` in HTML attributes that may contain
|
||||
untrusted content, or a separate escaping function must be used for
|
||||
attributes (see e.g. http://wonko.com/post/html-escaping)
|
||||
|
||||
Localization
|
||||
~~~~~~~~~~~~
|
||||
Internationalization
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The locale of the current user (whether they are logged in or not) is
|
||||
always available as ``self.locale`` in the request handler and as
|
||||
``locale`` in templates. The name of the locale (e.g., ``en_US``) is
|
||||
available as ``locale.name``, and you can translate strings with the
|
||||
``locale.translate`` method. Templates also have the global function
|
||||
`.Locale.translate` method. Templates also have the global function
|
||||
call ``_()`` available for string translation. The translate function
|
||||
has two forms:
|
||||
|
||||
::
|
||||
has two forms::
|
||||
|
||||
_("Translate this string")
|
||||
|
||||
which translates the string directly based on the current locale, and
|
||||
|
||||
::
|
||||
which translates the string directly based on the current locale, and::
|
||||
|
||||
_("A person liked this", "%(num)d people liked this",
|
||||
len(people)) % {"num": len(people)}
|
||||
|
@ -138,9 +158,7 @@ The most common pattern for translations is to use Python named
|
|||
placeholders for variables (the ``%(num)d`` in the example above) since
|
||||
placeholders can move around on translation.
|
||||
|
||||
Here is a properly localized template:
|
||||
|
||||
::
|
||||
Here is a properly internationalized template::
|
||||
|
||||
<html>
|
||||
<head>
|
||||
|
@ -160,9 +178,7 @@ By default, we detect the user's locale using the ``Accept-Language``
|
|||
header sent by the user's browser. We choose ``en_US`` if we can't find
|
||||
an appropriate ``Accept-Language`` value. If you let user's set their
|
||||
locale as a preference, you can override this default locale selection
|
||||
by overriding ``get_user_locale`` in your request handler:
|
||||
|
||||
::
|
||||
by overriding `.RequestHandler.get_user_locale`::
|
||||
|
||||
class BaseHandler(tornado.web.RequestHandler):
|
||||
def get_current_user(self):
|
||||
|
@ -179,32 +195,20 @@ by overriding ``get_user_locale`` in your request handler:
|
|||
If ``get_user_locale`` returns ``None``, we fall back on the
|
||||
``Accept-Language`` header.
|
||||
|
||||
You can load all the translations for your application using the
|
||||
``tornado.locale.load_translations`` method. It takes in the name of the
|
||||
directory which should contain CSV files named after the locales whose
|
||||
translations they contain, e.g., ``es_GT.csv`` or ``fr_CA.csv``. The
|
||||
method loads all the translations from those CSV files and infers the
|
||||
list of supported locales based on the presence of each CSV file. You
|
||||
typically call this method once in the ``main()`` method of your server:
|
||||
|
||||
::
|
||||
|
||||
def main():
|
||||
tornado.locale.load_translations(
|
||||
os.path.join(os.path.dirname(__file__), "translations"))
|
||||
start_server()
|
||||
The `tornado.locale` module supports loading translations in two
|
||||
formats: the ``.mo`` format used by `gettext` and related tools, and a
|
||||
simple ``.csv`` format. An application will generally call either
|
||||
`tornado.locale.load_translations` or
|
||||
`tornado.locale.load_gettext_translations` once at startup; see those
|
||||
methods for more details on the supported formats..
|
||||
|
||||
You can get the list of supported locales in your application with
|
||||
``tornado.locale.get_supported_locales()``. The user's locale is chosen
|
||||
`tornado.locale.get_supported_locales()`. The user's locale is chosen
|
||||
to be the closest match based on the supported locales. For example, if
|
||||
the user's locale is ``es_GT``, and the ``es`` locale is supported,
|
||||
``self.locale`` will be ``es`` for that request. We fall back on
|
||||
``en_US`` if no close match can be found.
|
||||
|
||||
See the `tornado.locale`
|
||||
documentation for detailed information on the CSV format and other
|
||||
localization methods.
|
||||
|
||||
.. _ui-modules:
|
||||
|
||||
UI modules
|
||||
|
@ -212,15 +216,13 @@ UI modules
|
|||
|
||||
Tornado supports *UI modules* to make it easy to support standard,
|
||||
reusable UI widgets across your application. UI modules are like special
|
||||
functional calls to render components of your page, and they can come
|
||||
function calls to render components of your page, and they can come
|
||||
packaged with their own CSS and JavaScript.
|
||||
|
||||
For example, if you are implementing a blog, and you want to have blog
|
||||
entries appear on both the blog home page and on each blog entry page,
|
||||
you can make an ``Entry`` module to render them on both pages. First,
|
||||
create a Python module for your UI modules, e.g., ``uimodules.py``:
|
||||
|
||||
::
|
||||
create a Python module for your UI modules, e.g., ``uimodules.py``::
|
||||
|
||||
class Entry(tornado.web.UIModule):
|
||||
def render(self, entry, show_comments=False):
|
||||
|
@ -228,9 +230,9 @@ create a Python module for your UI modules, e.g., ``uimodules.py``:
|
|||
"module-entry.html", entry=entry, show_comments=show_comments)
|
||||
|
||||
Tell Tornado to use ``uimodules.py`` using the ``ui_modules`` setting in
|
||||
your application:
|
||||
your application::
|
||||
|
||||
::
|
||||
from . import uimodules
|
||||
|
||||
class HomeHandler(tornado.web.RequestHandler):
|
||||
def get(self):
|
||||
|
@ -251,27 +253,21 @@ your application:
|
|||
(r"/entry/([0-9]+)", EntryHandler),
|
||||
], **settings)
|
||||
|
||||
Within ``home.html``, you reference the ``Entry`` module rather than
|
||||
printing the HTML directly:
|
||||
|
||||
::
|
||||
Within a template, you can call a module with the ``{% module %}``
|
||||
statement. For example, you could call the ``Entry`` module from both
|
||||
``home.html``::
|
||||
|
||||
{% for entry in entries %}
|
||||
{% module Entry(entry) %}
|
||||
{% end %}
|
||||
|
||||
Within ``entry.html``, you reference the ``Entry`` module with the
|
||||
``show_comments`` argument to show the expanded form of the entry:
|
||||
|
||||
::
|
||||
and ``entry.html``::
|
||||
|
||||
{% module Entry(entry, show_comments=True) %}
|
||||
|
||||
Modules can include custom CSS and JavaScript functions by overriding
|
||||
the ``embedded_css``, ``embedded_javascript``, ``javascript_files``, or
|
||||
``css_files`` methods:
|
||||
|
||||
::
|
||||
``css_files`` methods::
|
||||
|
||||
class Entry(tornado.web.UIModule):
|
||||
def embedded_css(self):
|
||||
|
@ -288,16 +284,12 @@ tag at the end of the page.
|
|||
|
||||
When additional Python code is not required, a template file itself may
|
||||
be used as a module. For example, the preceding example could be
|
||||
rewritten to put the following in ``module-entry.html``:
|
||||
|
||||
::
|
||||
rewritten to put the following in ``module-entry.html``::
|
||||
|
||||
{{ set_resources(embedded_css=".entry { margin-bottom: 1em; }") }}
|
||||
<!-- more template html... -->
|
||||
|
||||
This revised template module would be invoked with
|
||||
|
||||
::
|
||||
This revised template module would be invoked with::
|
||||
|
||||
{% module Template("module-entry.html", show_comments=True) %}
|
||||
|
||||
|
|
|
@ -100,6 +100,7 @@
|
|||
.. automethod:: RequestHandler.check_xsrf_cookie
|
||||
.. automethod:: RequestHandler.compute_etag
|
||||
.. automethod:: RequestHandler.create_template_loader
|
||||
.. autoattribute:: RequestHandler.current_user
|
||||
.. automethod:: RequestHandler.get_browser_locale
|
||||
.. automethod:: RequestHandler.get_current_user
|
||||
.. automethod:: RequestHandler.get_login_url
|
||||
|
|
|
@ -33,6 +33,9 @@ information, see
|
|||
http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTCONNECTTIMEOUTMS
|
||||
and comments in curl_httpclient.py).
|
||||
|
||||
To select ``curl_httpclient``, call `AsyncHTTPClient.configure` at startup::
|
||||
|
||||
AsyncHTTPClient.configure("tornado.curl_httpclient.CurlAsyncHTTPClient")
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function, with_statement
|
||||
|
|
|
@ -35,8 +35,7 @@ Here is a simple "Hello, world" example app::
|
|||
application.listen(8888)
|
||||
tornado.ioloop.IOLoop.instance().start()
|
||||
|
||||
See the :doc:`Tornado overview <overview>` for more details and a good getting
|
||||
started guide.
|
||||
See the :doc:`guide` for additional information.
|
||||
|
||||
Thread-safety notes
|
||||
-------------------
|
||||
|
@ -48,6 +47,7 @@ not thread-safe. In particular, methods such as
|
|||
you use multiple threads it is important to use `.IOLoop.add_callback`
|
||||
to transfer control back to the main thread before finishing the
|
||||
request.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function, with_statement
|
||||
|
|
Loading…
Reference in New Issue