Expand and rewrite the guide docs.

This commit is contained in:
Ben Darnell 2014-06-30 12:59:58 -04:00
parent 76b8ab66f2
commit 1b0cc391c7
8 changed files with 601 additions and 511 deletions

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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')

View File

@ -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) %}

View File

@ -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

View File

@ -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

View File

@ -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