~soren/nova/iptables-security-groups

« back to all changes in this revision

Viewing changes to vendor/tornado/website/templates/documentation.txt

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
Overview
 
2
--------
 
3
[FriendFeed](http://friendfeed.com/)'s web server is a relatively simple,
 
4
non-blocking web server written in Python. The FriendFeed application is
 
5
written using a web framework that looks a bit like
 
6
[web.py](http://webpy.org/) or Google's
 
7
[webapp](http://code.google.com/appengine/docs/python/tools/webapp/),
 
8
but with additional tools and optimizations to take advantage of the
 
9
non-blocking web server and tools.
 
10
 
 
11
[Tornado](http://github.com/facebook/tornado) is an open source
 
12
version of this web server and some of the tools we use most often at
 
13
FriendFeed. The framework is distinct from most mainstream web server
 
14
frameworks (and certainly most Python frameworks) because it is
 
15
non-blocking and reasonably fast. Because it is non-blocking
 
16
and uses [epoll](http://www.kernel.org/doc/man-pages/online/pages/man4/epoll.4.html), it can handle 1000s of simultaneous standing connections,
 
17
which means the framework is ideal for real-time web services. We built the
 
18
web server specifically to handle FriendFeed's real-time features —
 
19
every active user of FriendFeed maintains an open connection to the
 
20
FriendFeed servers. (For more information on scaling servers to support
 
21
thousands of clients, see
 
22
[The C10K problem](http://www.kegel.com/c10k.html).)
 
23
 
 
24
Here is the canonical "Hello, world" example app:
 
25
 
 
26
    import tornado.httpserver
 
27
    import tornado.ioloop
 
28
    import tornado.web
 
29
 
 
30
    class MainHandler(tornado.web.RequestHandler):
 
31
        def get(self):
 
32
            self.write("Hello, world")
 
33
 
 
34
    application = tornado.web.Application([
 
35
        (r"/", MainHandler),
 
36
    ])
 
37
 
 
38
    if __name__ == "__main__":
 
39
        http_server = tornado.httpserver.HTTPServer(application)
 
40
        http_server.listen(8888)
 
41
        tornado.ioloop.IOLoop.instance().start()
 
42
 
 
43
See [Tornado walkthrough](#tornado-walkthrough) below for a detailed
 
44
walkthrough of the `tornado.web` package.
 
45
 
 
46
We attempted to clean up the code base to reduce interdependencies between
 
47
modules, so you should (theoretically) be able to use any of the modules
 
48
independently in your project without using the whole package.
 
49
 
 
50
 
 
51
Download
 
52
--------
 
53
Download the most recent version of Tornado from GitHub:
 
54
 
 
55
> [tornado-0.2.tar.gz](/static/tornado-0.2.tar.gz)
 
56
 
 
57
You can also [browse the source](http://github.com/facebook/tornado) on GitHub. To install Tornado:
 
58
 
 
59
    tar xvzf tornado-0.2.tar.gz
 
60
    cd tornado-0.2
 
61
    python setup.py build
 
62
    sudo python setup.py install
 
63
 
 
64
After installation, you should be able to run any of the demos in the `demos`
 
65
directory included with the Tornado package.
 
66
 
 
67
    ./demos/helloworld/helloworld.py
 
68
 
 
69
### Prerequisites
 
70
 
 
71
Tornado has been tested on Python 2.5 and 2.6. To use all of the features of Tornado, you need to have [PycURL](http://pycurl.sourceforge.net/) and a JSON library like [simplejson](http://pypi.python.org/pypi/simplejson/) installed. Complete installation instructions for Mac OS X and Ubuntu are included below for convenience.
 
72
 
 
73
**Mac OS X 10.5/10.6**
 
74
 
 
75
    sudo easy_install setuptools pycurl==7.16.2.1 simplejson
 
76
 
 
77
**Ubuntu Linux**
 
78
 
 
79
    sudo apt-get install python-dev python-pycurl python-simplejson
 
80
 
 
81
 
 
82
Module index
 
83
------------
 
84
The most important module is [`web`](http://github.com/facebook/tornado/blob/master/tornado/web.py), which is the web framework
 
85
that includes most of the meat of the Tornado package. The other modules
 
86
are tools that make `web` more useful. See
 
87
[Tornado walkthrough](#tornado-walkthrough) below for a detailed
 
88
walkthrough of the `web` package.
 
89
 
 
90
### Main modules
 
91
  * [`web`](http://github.com/facebook/tornado/blob/master/tornado/web.py) - The web framework on which FriendFeed is built. `web` incorporates most of the important features of Tornado
 
92
  * [`escape`](http://github.com/facebook/tornado/blob/master/tornado/escape.py) - XHTML, JSON, and URL encoding/decoding methods
 
93
  * [`database`](http://github.com/facebook/tornado/blob/master/tornado/database.py) - A simple wrapper around `MySQLdb` to make MySQL easier to use
 
94
  * [`template`](http://github.com/facebook/tornado/blob/master/tornado/template.py) - A Python-based web templating language
 
95
  * [`httpclient`](http://github.com/facebook/tornado/blob/master/tornado/httpclient.py) - A non-blocking HTTP client designed to work with `web` and `httpserver`
 
96
  * [`auth`](http://github.com/facebook/tornado/blob/master/tornado/auth.py) - Implementation of third party authentication and authorization schemes (Google OpenID/OAuth, Facebook Platform, Yahoo BBAuth, FriendFeed OpenID/OAuth, Twitter OAuth)
 
97
  * [`locale`](http://github.com/facebook/tornado/blob/master/tornado/locale.py) - Localization/translation support
 
98
  * [`options`](http://github.com/facebook/tornado/blob/master/tornado/options.py) - Command line and config file parsing, optimized for server environments
 
99
 
 
100
### Low-level modules
 
101
  * [`httpserver`](http://github.com/facebook/tornado/blob/master/tornado/httpserver.py) - A very simple HTTP server built on which `web` is built
 
102
  * [`iostream`](http://github.com/facebook/tornado/blob/master/tornado/iostream.py) - A simple wrapper around non-blocking sockets to aide common reading and writing patterns
 
103
  * [`ioloop`](http://github.com/facebook/tornado/blob/master/tornado/ioloop.py) - Core I/O loop
 
104
 
 
105
### Random modules
 
106
  * [`s3server`](http://github.com/facebook/tornado/blob/master/tornado/s3server.py) - A web server that implements most of the [Amazon S3](http://aws.amazon.com/s3/) interface, backed by local file storage
 
107
 
 
108
 
 
109
Tornado walkthrough
 
110
-------------------
 
111
 
 
112
### Request handlers and request arguments
 
113
 
 
114
A Tornado web application maps URLs or URL patterns to subclasses of
 
115
`tornado.web.RequestHandler`. Those classes define `get()` or `post()`
 
116
methods to handle HTTP `GET` or `POST` requests to that URL.
 
117
 
 
118
This code maps the root URL `/` to `MainHandler` and the URL pattern
 
119
`/story/([0-9]+)` to `StoryHandler`. Regular expression groups are passed
 
120
as arguments to the `RequestHandler` methods:
 
121
 
 
122
    class MainHandler(tornado.web.RequestHandler):
 
123
        def get(self):
 
124
            self.write("You requested the main page")
 
125
 
 
126
    class StoryHandler(tornado.web.RequestHandler):
 
127
        def get(self, story_id):
 
128
            self.write("You requested the story " + story_id)
 
129
 
 
130
    application = tornado.web.Application([
 
131
        (r"/", MainHandler),
 
132
        (r"/story/([0-9]+)", StoryHandler),
 
133
    ])
 
134
 
 
135
You can get query string arguments and parse `POST` bodies with the
 
136
`get_argument()` method:
 
137
 
 
138
    class MainHandler(tornado.web.RequestHandler):
 
139
        def get(self):
 
140
            self.write('<html><body><form action="/" method="post">'
 
141
                       '<input type="text" name="message">'
 
142
                       '<input type="submit" value="Submit">'
 
143
                       '</form></body></html>')
 
144
 
 
145
        def post(self):
 
146
            self.set_header("Content-Type", "text/plain")
 
147
            self.write("You wrote " + self.get_argument("message"))
 
148
 
 
149
If you want to send an error response to the client, e.g., 403 Unauthorized,
 
150
you can just raise a `tornado.web.HTTPError` exception:
 
151
 
 
152
    if not self.user_is_logged_in():
 
153
        raise tornado.web.HTTPError(403)
 
154
 
 
155
The request handler can access the object representing the current request
 
156
with `self.request`. The `HTTPRequest` object includes a number of useful
 
157
attribute, including:
 
158
 
 
159
 * `arguments` - all of the `GET` and `POST` arguments
 
160
 * `files` - all of the uploaded files (via `multipart/form-data` POST requests)
 
161
 * `path` - the request path (everything before the `?`)
 
162
 * `headers` - the request headers
 
163
 
 
164
See the class definition for `HTTPRequest` in `httpserver` for a complete list
 
165
of attributes.
 
166
 
 
167
 
 
168
### Templates
 
169
 
 
170
You can use any template language supported by Python, but Tornado ships
 
171
with its own templating language that is a lot faster and more flexible
 
172
than many of the most popular templating systems out there. See the
 
173
[`template`](http://github.com/facebook/tornado/blob/master/tornado/template.py) module documentation for complete documentation.
 
174
 
 
175
A Tornado template is just HTML (or any other text-based format) with
 
176
Python control sequences and expressions embedded within the markup:
 
177
 
 
178
    <html>
 
179
       <head>
 
180
          <title>{{ title }}</title>
 
181
       </head>
 
182
       <body>
 
183
         <ul>
 
184
           {% for item in items %}
 
185
             <li>{{ escape(item) }}</li>
 
186
           {% end %}
 
187
         </ul>
 
188
       </body>
 
189
     </html>
 
190
 
 
191
If you saved this template as "template.html" and put it in the same
 
192
directory as your Python file, you could render this template with:
 
193
 
 
194
    class MainHandler(tornado.web.RequestHandler):
 
195
        def get(self):
 
196
            items = ["Item 1", "Item 2", "Item 3"]
 
197
            self.render("template.html", title="My title", items=items)
 
198
 
 
199
Tornado templates support *control statements* and *expressions*. Control
 
200
statements are surronded by `{%` and `%}`, e.g., `{% if len(items) > 2 %}`.
 
201
Expressions are surrounded by `{{` and `}}`, e.g., `{{ items[0] }}`.
 
202
 
 
203
Control statements more or less map exactly to Python statements. We support
 
204
`if`, `for`, `while`, and `try`, all of which are terminated with `{% end %}`.
 
205
We also support *template inheritance* using the `extends` and `block`
 
206
statements, which are described in detail in the documentation for the
 
207
[`template` module](http://github.com/facebook/tornado/blob/master/tornado/template.py).
 
208
 
 
209
Expressions can be any Python expression, including function calls. We
 
210
support the functions `escape`, `url_escape`, and `json_encode` by default,
 
211
and you can pass other functions into the template simply by passing them
 
212
as keyword arguments to the template render function:
 
213
 
 
214
    class MainHandler(tornado.web.RequestHandler):
 
215
        def get(self):
 
216
            self.render("template.html", add=self.add)
 
217
 
 
218
        def add(self, x, y):
 
219
            return x + y
 
220
 
 
221
When you are building a real application, you are going to want to use
 
222
all of the features of Tornado templates, especially template inheritance.
 
223
Read all about those features in the [`template` module](http://github.com/facebook/tornado/blob/master/tornado/template.py)
 
224
section.
 
225
 
 
226
Under the hood, Tornado templates are translated directly to Python.
 
227
The expressions you include in your template are copied verbatim into
 
228
a Python function representing your template. We don't try to prevent
 
229
anything in the template language; we created it explicitly to provide
 
230
the flexibility that other, stricter templating systems prevent.
 
231
Consequently, if you write random stuff inside of your template expressions,
 
232
you will get random Python errors when you execute the template.
 
233
 
 
234
 
 
235
### Cookies and secure cookies
 
236
 
 
237
You can set cookies in the user's browser with the `set_cookie` method:
 
238
 
 
239
    class MainHandler(tornado.web.RequestHandler):
 
240
        def get(self):
 
241
            if not self.get_cookie("mycookie"):
 
242
                self.set_cookie("mycookie", "myvalue")
 
243
                self.write("Your cookie was not set yet!")
 
244
            else:
 
245
                self.write("Your cookie was set!")
 
246
 
 
247
Cookies are easily forged by malicious clients. If you need to set cookies
 
248
to, e.g., save the user ID of the currently logged in user, you need to
 
249
sign your cookies to prevent forgery. Tornado supports this out of the
 
250
box with the `set_secure_cookie` and `get_secure_cookie` methods. To use
 
251
these methods, you need to specify a secret key named `cookie_secret` when
 
252
you create your application. You can pass in application settings as keyword
 
253
arguments to your application:
 
254
 
 
255
    application = tornado.web.Application([
 
256
        (r"/", MainHandler),
 
257
    ], cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=")
 
258
 
 
259
Signed cookies contain the encoded value of the cookie in addition to a
 
260
timestamp and an [HMAC](http://en.wikipedia.org/wiki/HMAC) signature. If the
 
261
cookie is old or if the signature doesn't match, `get_secure_cookie` will
 
262
return `None` just as if the cookie isn't set. The secure version of the
 
263
example above:
 
264
 
 
265
    class MainHandler(tornado.web.RequestHandler):
 
266
        def get(self):
 
267
            if not self.get_secure_cookie("mycookie"):
 
268
                self.set_secure_cookie("mycookie", "myvalue")
 
269
                self.write("Your cookie was not set yet!")
 
270
            else:
 
271
                self.write("Your cookie was set!")
 
272
 
 
273
 
 
274
### User authentication
 
275
 
 
276
The currently authenticated user is available in every request handler
 
277
as `self.current_user`, and in every template as `current_user`. By
 
278
default, `current_user` is `None`.
 
279
 
 
280
To implement user authentication in your application, you need to
 
281
override the `get_current_user()` method in your request handlers to
 
282
determine the current user based on, e.g., the value of a cookie.
 
283
Here is an example that lets users log into the application simply
 
284
by specifying a nickname, which is then saved in a cookie:
 
285
 
 
286
    class BaseHandler(tornado.web.RequestHandler):
 
287
        def get_current_user(self):
 
288
            return self.get_secure_cookie("user")
 
289
 
 
290
    class MainHandler(BaseHandler):
 
291
        def get(self):
 
292
            if not self.current_user:
 
293
                self.redirect("/login")
 
294
                return
 
295
            name = tornado.escape.xhtml_escape(self.current_user)
 
296
            self.write("Hello, " + name)
 
297
 
 
298
    class LoginHandler(BaseHandler):
 
299
        def get(self):
 
300
            self.write('<html><body><form action="/login" method="post">'
 
301
                       'Name: <input type="text" name="name">'
 
302
                       '<input type="submit" value="Sign in">'
 
303
                       '</form></body></html>')
 
304
 
 
305
        def post(self):
 
306
            self.set_secure_cookie("user", self.get_argument("name"))
 
307
            self.redirect("/")
 
308
 
 
309
    application = tornado.web.Application([
 
310
        (r"/", MainHandler),
 
311
        (r"/login", LoginHandler),
 
312
    ], cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=")
 
313
 
 
314
You can require that the user be logged in using the
 
315
[Python decorator](http://www.python.org/dev/peps/pep-0318/)
 
316
`tornado.web.authenticated`. If a request goes to a method with this
 
317
decorator, and the user is not logged in, they will be redirected to
 
318
`login_url` (another application setting). The example above could
 
319
be rewritten:
 
320
 
 
321
    class MainHandler(BaseHandler):
 
322
        @tornado.web.authenticated
 
323
        def get(self):
 
324
            name = tornado.escape.xhtml_escape(self.current_user)
 
325
            self.write("Hello, " + name)
 
326
 
 
327
    settings = {
 
328
        "cookie_secret": "61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
 
329
        "login_url": "/login",
 
330
    }
 
331
    application = tornado.web.Application([
 
332
        (r"/", MainHandler),
 
333
        (r"/login", LoginHandler),
 
334
    ], **settings)
 
335
 
 
336
If you decorate `post()` methods with the `authenticated` decorator, and
 
337
the user is not logged in, the server will send a `403` response.
 
338
 
 
339
Tornado comes with built-in support for third-party authentication schemes
 
340
like Google OAuth. See the [`auth` module](http://github.com/facebook/tornado/blob/master/tornado/auth.py) for more details. Check
 
341
out the Tornado Blog example application for a complete example that
 
342
uses authentication (and stores user data in a MySQL database).
 
343
 
 
344
 
 
345
### Cross-site request forgery protection
 
346
 
 
347
[Cross-site request forgery](http://en.wikipedia.org/wiki/Cross-site_request_forgery), or XSRF, is a common problem for personalized web applications. See the
 
348
[Wikipedia article](http://en.wikipedia.org/wiki/Cross-site_request_forgery)
 
349
for more information on how XSRF works.
 
350
 
 
351
The generally accepted solution to prevent XSRF is to cookie every user
 
352
with an unpredictable value and include that value as an additional
 
353
argument with every form submission on your site. If the cookie and the
 
354
value in the form submission do not match, then the request is likely
 
355
forged.
 
356
 
 
357
Tornado comes with built-in XSRF protection. To include it in your site,
 
358
include the application setting `xsrf_cookies`:
 
359
 
 
360
    settings = {
 
361
        "cookie_secret": "61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
 
362
        "login_url": "/login",
 
363
        "xsrf_cookies": True,
 
364
    }
 
365
    application = tornado.web.Application([
 
366
        (r"/", MainHandler),
 
367
        (r"/login", LoginHandler),
 
368
    ], **settings)
 
369
 
 
370
If `xsrf_cookies` is set, the Tornado web application will set the `_xsrf`
 
371
cookie for all users and reject all `POST` requests hat do not contain a
 
372
correct `_xsrf` value. If you turn this setting on, you need to instrument
 
373
all forms that submit via `POST` to contain this field. You can do this with
 
374
the special function `xsrf_form_html()`, available in all templates:
 
375
 
 
376
    <form action="/login" method="post">
 
377
      {{ xsrf_form_html() }}
 
378
      <div>Username: <input type="text" name="username"/></div>
 
379
      <div>Password: <input type="password" name="password"/></div>
 
380
      <div><input type="submit" value="Sign in"/></div>
 
381
    </form>
 
382
 
 
383
If you submit AJAX `POST` requests, you will also need to instrument your
 
384
JavaScript to include the `_xsrf` value with each request. This is the
 
385
[jQuery](http://jquery.com/) function we use at FriendFeed for AJAX `POST`
 
386
requests that automatically adds the `_xsrf` value to all requests:
 
387
 
 
388
    function getCookie(name) {
 
389
        var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
 
390
        return r ? r[1] : undefined;
 
391
    }
 
392
 
 
393
    jQuery.postJSON = function(url, args, callback) {
 
394
        args._xsrf = getCookie("_xsrf");
 
395
        $.ajax({url: url, data: $.param(args), dataType: "text", type: "POST",
 
396
                success: function(response) {
 
397
            callback(eval("(" + response + ")"));
 
398
        }});
 
399
    };
 
400
 
 
401
 
 
402
### Static files and aggressive file caching
 
403
 
 
404
You can serve static files from Tornado by specifying the `static_path`
 
405
setting in your application:
 
406
 
 
407
    settings = {
 
408
        "static_path": os.path.join(os.path.dirname(__file__), "static"),
 
409
        "cookie_secret": "61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
 
410
        "login_url": "/login",
 
411
        "xsrf_cookies": True,
 
412
    }
 
413
    application = tornado.web.Application([
 
414
        (r"/", MainHandler),
 
415
        (r"/login", LoginHandler),
 
416
    ], **settings)
 
417
 
 
418
This setting will automatically make all requests that start with `/static/`
 
419
serve from that static directory, e.g., [http://localhost:8888/static/foo.png](http://localhost:8888/static/foo.png)
 
420
will serve the file `foo.png` from the specified static directory. We
 
421
also automatically serve `/robots.txt` and `/favicon.ico` from the static
 
422
directory (even though they don't start with the `/static/` prefix).
 
423
 
 
424
To improve performance, it is generally a good idea for browsers to
 
425
cache static resources aggressively so browsers won't send unnecessary
 
426
`If-Modified-Since` or `Etag` requests that might block the rendering of
 
427
the page. Tornado supports this out of the box with *static content
 
428
versioning*.
 
429
 
 
430
To use this feature, use the `static_url()` method in your templates rather
 
431
than typing the URL of the static file directly in your HTML:
 
432
 
 
433
    <html>
 
434
       <head>
 
435
          <title>FriendFeed - {{ _("Home") }}</title>
 
436
       </head>
 
437
       <body>
 
438
         <div><img src="{{ static_url("images/logo.png") }}"/></div>
 
439
       </body>
 
440
     </html>
 
441
 
 
442
The `static_url()` function will translate that relative path to a URI
 
443
that looks like `/static/images/logo.png?v=aae54`. The `v` argument is
 
444
a hash of the content in `logo.png`, and its presence makes the Tornado
 
445
server send cache headers to the user's browser that will make the browser
 
446
cache the content indefinitely.
 
447
 
 
448
Since the `v` argument is based on the content of the file, if you update
 
449
a file and restart your server, it will start sending a new `v` value,
 
450
so the user's browser will automatically fetch the new file. If the file's
 
451
contents don't change, the browser will continue to use a locally cached
 
452
copy without ever checking for updates on the server, significantly
 
453
improving rendering performance.
 
454
 
 
455
In production, you probably want to serve static files from a more
 
456
optimized static file server like [nginx](http://nginx.net/). You can
 
457
configure most any web server to support these caching semantics. Here
 
458
is the nginx configuration we use at FriendFeed:
 
459
 
 
460
    location /static/ {
 
461
        root /var/friendfeed/static;
 
462
        if ($query_string) {
 
463
            expires max;
 
464
        }
 
465
     }
 
466
 
 
467
 
 
468
### Localization
 
469
 
 
470
The locale of the current user (whether they are logged in or not) is
 
471
always available as `self.locale` in the request handler and as `locale`
 
472
in templates. The name of the locale (e.g., `en_US`) is available as
 
473
`locale.name`, and you can translate strings with the `locale.translate`
 
474
method. Templates also have the global function call `_()` available
 
475
for string translation. The translate function has two forms:
 
476
 
 
477
    _("Translate this string")
 
478
 
 
479
which translates the string directly based on the current locale, and
 
480
 
 
481
    _("A person liked this", "%(num)d people liked this", len(people)) % {"num": len(people)}
 
482
 
 
483
which translates a string that can be singular or plural based on the value
 
484
of the third argument. In the example above, a translation of the first
 
485
string will be returned if `len(people)` is `1`, or a translation of the
 
486
second string will be returned otherwise.
 
487
 
 
488
The most common pattern for translations is to use Python named placeholders
 
489
for variables (the `%(num)d` in the example above) since placeholders can
 
490
move around on translation.
 
491
 
 
492
Here is a properly localized template:
 
493
 
 
494
    <html>
 
495
       <head>
 
496
          <title>FriendFeed - {{ _("Sign in") }}</title>
 
497
       </head>
 
498
       <body>
 
499
         <form action="{{ request.path }}" method="post">
 
500
           <div>{{ _("Username") }} <input type="text" name="username"/></div>
 
501
           <div>{{ _("Password") }} <input type="password" name="password"/></div>
 
502
           <div><input type="submit" value="{{ _("Sign in") }}"/></div>
 
503
           {{ xsrf_form_html() }}
 
504
         </form>
 
505
       </body>
 
506
     </html>
 
507
 
 
508
By default, we detect the user's locale using the `Accept-Language` header
 
509
sent by the user's browser. We choose `en_US` if we can't find an appropriate
 
510
`Accept-Language` value. If you let user's set their locale as a preference,
 
511
you can override this default locale selection by overriding `get_user_locale`
 
512
in your request handler:
 
513
 
 
514
    class BaseHandler(tornado.web.RequestHandler):
 
515
        def get_current_user(self):
 
516
            user_id = self.get_secure_cookie("user")
 
517
            if not user_id: return None
 
518
            return self.backend.get_user_by_id(user_id)
 
519
 
 
520
        def get_user_locale(self):
 
521
            if "locale" not in self.current_user.prefs:
 
522
                # Use the Accept-Language header
 
523
                return None
 
524
            return self.current_user.prefs["locale"]
 
525
 
 
526
If `get_user_locale` returns `None`, we fall back on the `Accept-Language`
 
527
header.
 
528
 
 
529
You can load all the translations for your application using the
 
530
`tornado.locale.load_translations` method. It takes in the name of the
 
531
directory which should contain CSV files named after the locales whose
 
532
translations they contain, e.g., `es_GT.csv` or `fr_CA.csv`. The method
 
533
loads all the translations from those CSV files and infers the list of
 
534
supported locales based on the presence of each CSV file. You typically
 
535
call this method once in the `main()` method of your server:
 
536
 
 
537
    def main():
 
538
        tornado.locale.load_translations(
 
539
            os.path.join(os.path.dirname(__file__), "translations"))
 
540
        start_server()
 
541
 
 
542
You can get the list of supported locales in your application with
 
543
`tornado.locale.get_supported_locales()`. The user's locale is chosen to
 
544
be the closest match based on the supported locales. For example, if the
 
545
user's locale is `es_GT`, and the `es` locale is supported, `self.locale`
 
546
will be `es` for that request. We fall back on `en_US` if no close match
 
547
can be found.
 
548
 
 
549
See the [`locale` module](http://github.com/facebook/tornado/blob/master/tornado/locale.py) documentation for detailed information
 
550
on the CSV format and other localization methods.
 
551
 
 
552
 
 
553
### UI modules
 
554
 
 
555
Tornado supports *UI modules* to make it easy to support standard, reusable
 
556
UI widgets across your application. UI modules are like special functional
 
557
calls to render components of your page, and they can come packaged with
 
558
their own CSS and JavaScript.
 
559
 
 
560
For example, if you are implementing a blog, and you want to have
 
561
blog entries appear on both the blog home page and on each blog entry page,
 
562
you can make an `Entry` module to render them on both pages. First, create
 
563
a Python module for your UI modules, e.g., `uimodules.py`:
 
564
 
 
565
    class Entry(tornado.web.UIModule):
 
566
        def render(self, entry, show_comments=False):
 
567
            return self.render_string(
 
568
                "module-entry.html", show_comments=show_comments)
 
569
 
 
570
Tell Tornado to use `uimodules.py` using the `ui_modules` setting in your
 
571
application:
 
572
 
 
573
    class HomeHandler(tornado.web.RequestHandler):
 
574
        def get(self):
 
575
            entries = self.db.query("SELECT * FROM entries ORDER BY date DESC")
 
576
            self.render("home.html", entries=entries)
 
577
 
 
578
    class EntryHandler(tornado.web.RequestHandler):
 
579
        def get(self, entry_id):
 
580
            entry = self.db.get("SELECT * FROM entries WHERE id = %s", entry_id)
 
581
            if not entry: raise tornado.web.HTTPError(404)
 
582
            self.render("entry.html", entry=entry)
 
583
 
 
584
    settings = {
 
585
        "ui_modules": uimodules,
 
586
    }
 
587
    application = tornado.web.Application([
 
588
        (r"/", HomeHandler),
 
589
        (r"/entry/([0-9]+)", EntryHandler),
 
590
    ], **settings)
 
591
 
 
592
Within `home.html`, you reference the `Entry` module rather than printing
 
593
the HTML directly:
 
594
 
 
595
    {% for entry in entries %}
 
596
      {{ modules.Entry(entry) }}
 
597
    {% end %}
 
598
 
 
599
Within `entry.html`, you reference the `Entry` module with the
 
600
`show_comments` argument to show the expanded form of the entry:
 
601
 
 
602
    {{ modules.Entry(entry, show_comments=True) }}
 
603
 
 
604
Modules can include custom CSS and JavaScript functions by overriding
 
605
the `embedded_css`, `embedded_javascript`, `javascript_files`, or
 
606
`css_files` methods:
 
607
 
 
608
    class Entry(tornado.web.UIModule):
 
609
        def embedded_css(self):
 
610
            return ".entry { margin-bottom: 1em; }"
 
611
 
 
612
        def render(self, entry, show_comments=False):
 
613
            return self.render_string(
 
614
                "module-entry.html", show_comments=show_comments)
 
615
 
 
616
Module CSS and JavaScript will be included once no matter how many times
 
617
a module is used on a page. CSS is always included in the `<head>` of the
 
618
page, and JavaScript is always included just before the `</body>` tag
 
619
at the end of the page.
 
620
 
 
621
 
 
622
### Non-blocking, asynchronous requests
 
623
 
 
624
When a request handler is executed, the request is automatically finished.
 
625
Since Tornado uses a non-blocking I/O style, you can override this default
 
626
behavior if you want a request to remain open after the main request handler
 
627
method returns using the `tornado.web.asynchronous` decorator.
 
628
 
 
629
When you use this decorator, it is your responsibility to call
 
630
`self.finish()` to finish the HTTP request, or the user's browser
 
631
will simply hang:
 
632
 
 
633
    class MainHandler(tornado.web.RequestHandler):
 
634
        @tornado.web.asynchronous
 
635
        def get(self):
 
636
            self.write("Hello, world")
 
637
            self.finish()
 
638
 
 
639
Here is a real example that makes a call to the FriendFeed API using
 
640
Tornado's built-in asynchronous HTTP client:
 
641
 
 
642
    class MainHandler(tornado.web.RequestHandler):
 
643
        @tornado.web.asynchronous
 
644
        def get(self):
 
645
            http = tornado.httpclient.AsyncHTTPClient()
 
646
            http.fetch("http://friendfeed-api.com/v2/feed/bret",
 
647
                       callback=self.async_callback(self.on_response))
 
648
 
 
649
        def on_response(self, response):
 
650
            if response.error: raise tornado.web.HTTPError(500)
 
651
            json = tornado.escape.json_decode(response.body)
 
652
            self.write("Fetched " + str(len(json["entries"])) + " entries "
 
653
                       "from the FriendFeed API")
 
654
            self.finish()
 
655
 
 
656
When `get()` returns, the request has not finished. When the HTTP client
 
657
eventually calls `on_response()`, the request is still open, and the response
 
658
is finally flushed to the client with the call to `self.finish()`.
 
659
 
 
660
If you make calls to asynchronous library functions that require a callback
 
661
(like the HTTP `fetch` function above), you should always wrap your
 
662
callbacks with `self.async_callback`. This simple wrapper ensures that if
 
663
your callback function raises an exception or has a programming error,
 
664
a proper HTTP error response will be sent to the browser, and the connection
 
665
will be properly closed.
 
666
 
 
667
For a more advanced asynchronous example, take a look at the `chat` example
 
668
application, which implements an AJAX chat room using
 
669
[long polling](http://en.wikipedia.org/wiki/Push_technology#Long_polling).
 
670
 
 
671
 
 
672
### Third party authentication
 
673
 
 
674
Tornado's `auth` module implements the authentication and authorization
 
675
protocols for a number of the most popular sites on the web, including
 
676
Google/Gmail, Facebook, Twitter, Yahoo, and FriendFeed. The module includes
 
677
methods to log users in via these sites and, where applicable, methods to
 
678
authorize access to the service so you can, e.g., download a user's address
 
679
book or publish a Twitter message on their behalf.
 
680
 
 
681
Here is an example handler that uses Google for authentication, saving
 
682
the Google credentials in a cookie for later access:
 
683
 
 
684
    class GoogleHandler(tornado.web.RequestHandler, tornado.auth.GoogleMixin):
 
685
        @tornado.web.asynchronous
 
686
        def get(self):
 
687
            if self.get_argument("openid.mode", None):
 
688
                self.get_authenticated_user(self.async_callback(self._on_auth))
 
689
                return
 
690
            self.authenticate_redirect()
 
691
    
 
692
        def _on_auth(self, user):
 
693
            if not user:
 
694
                self.authenticate_redirect()
 
695
                return
 
696
            # Save the user with, e.g., set_secure_cookie()
 
697
 
 
698
See the `auth` module documentation for more details.
 
699
 
 
700
 
 
701
Performance
 
702
-----------
 
703
Web application performance is generally bound by architecture, not frontend
 
704
performance. That said, Tornado is pretty fast relative to most popular
 
705
Python web frameworks.
 
706
 
 
707
We ran a few remedial load tests on a simple "Hello, world" application 
 
708
in each of the most popular Python web frameworks
 
709
([Django](http://www.djangoproject.com/), [web.py](http://webpy.org/), and
 
710
[CherryPy](http://www.cherrypy.org/)) to get the baseline performance of
 
711
each relative to Tornado. We used Apache/mod_wsgi for Django and web.py
 
712
and ran CherryPy as a standalone server, which was our impression of how
 
713
each framework is typically run in production environments. We ran 4
 
714
single-threaded Tornado frontends behind an [nginx](http://nginx.net/)
 
715
reverse proxy, which is how we recommend running Tornado in production
 
716
(our load test machine had four cores, and we recommend 1 frontend per
 
717
core).
 
718
 
 
719
We load tested each with Apache Benchmark (`ab`) on the a separate machine
 
720
with the command
 
721
 
 
722
    ab -n 100000 -c 25 http://10.0.1.x/
 
723
 
 
724
The results (requests per second) on a 2.4GHz AMD Opteron processor with
 
725
4 cores:
 
726
 
 
727
<div style="text-align:center;margin-top:2em;margin-bottom:2em"><img src="http://chart.apis.google.com/chart?chxt=y&chd=t%3A100%2C40%2C27%2C25%2C9&chco=609bcc&chm=t+8213%2C000000%2C0%2C0%2C11%7Ct+3353%2C000000%2C0%2C1%2C11%7Ct+2223%2C000000%2C0%2C2%2C11%7Ct+2066%2C000000%2C0%2C3%2C11%7Ct+785%2C000000%2C0%2C4%2C11&chs=600x175&cht=bhs&chtt=Web+server+requests%2Fsec+%28AMD+Opteron%2C+2.4GHz%2C+4+cores%29&chxl=0%3A%7CCherryPy+%28standalone%29%7Cweb.py+%28Apache%2Fmod_wsgi%29%7CDjango+%28Apache%2Fmod_wsgi%29%7CTornado+%281+single-threaded+frontend%29%7CTornado+%28nginx%3B+4+frontends%29%7C"/></div>
 
728
 
 
729
In our tests, Tornado consistently had 4X the throughput of the next fastest
 
730
framework, and even a single standalone Tornado frontend got 33% more
 
731
throughput even though it only used one of the four cores.
 
732
 
 
733
Not very scientific, but at a high level, it should give you a sense that we
 
734
have cared about performance as we built Tornado, and it shouldn't add too
 
735
much latency to your apps relative to most Python web development frameworks.
 
736
 
 
737
 
 
738
Running Tornado in production
 
739
-----------------------------
 
740
At FriendFeed, we use [nginx](http://nginx.net/) as a load balancer
 
741
and static file server. We run multiple instances of the Tornado web
 
742
server on multiple frontend machines. We typically run one Tornado frontend
 
743
per core on the machine (sometimes more depending on utilization).
 
744
 
 
745
This is a barebones nginx config file that is structurally similar to the
 
746
one we use at FriendFeed. It assumes nginx and the Tornado servers
 
747
are running on the same machine, and the four Tornado servers
 
748
are running on ports 8000 - 8003:
 
749
 
 
750
    user nginx;
 
751
    worker_processes 1;
 
752
 
 
753
    error_log /var/log/nginx/error.log;
 
754
    pid /var/run/nginx.pid;
 
755
 
 
756
    events {
 
757
        worker_connections 1024;
 
758
        use epoll;
 
759
    }
 
760
 
 
761
    http {
 
762
        # Enumerate all the Tornado servers here
 
763
        upstream frontends {
 
764
            server 127.0.0.1:8000;
 
765
            server 127.0.0.1:8001;
 
766
            server 127.0.0.1:8002;
 
767
            server 127.0.0.1:8003;
 
768
        }
 
769
 
 
770
        include /etc/nginx/mime.types;
 
771
        default_type application/octet-stream;
 
772
 
 
773
        access_log /var/log/nginx/access.log;
 
774
 
 
775
        keepalive_timeout 65;
 
776
        proxy_read_timeout 200;
 
777
        sendfile on;
 
778
        tcp_nopush on;
 
779
        tcp_nodelay on;
 
780
        gzip on;
 
781
        gzip_min_length 1000;
 
782
        gzip_proxied any;              
 
783
        gzip_types text/plain text/html text/css text/xml
 
784
                   application/x-javascript application/xml
 
785
                   application/atom+xml text/javascript;
 
786
 
 
787
        # Only retry if there was a communication error, not a timeout
 
788
        # on the Tornado server (to avoid propagating "queries of death"
 
789
        # to all frontends)
 
790
        proxy_next_upstream error;
 
791
 
 
792
        server {
 
793
            listen 80;
 
794
 
 
795
            # Allow file uploads
 
796
            client_max_body_size 50M;
 
797
 
 
798
            location ^~ /static/ {
 
799
                root /var/www;
 
800
                if ($query_string) {
 
801
                    expires max;
 
802
                }
 
803
            }
 
804
            location = /favicon.ico {
 
805
                rewrite (.*) /static/favicon.ico;
 
806
            }
 
807
            location = /robots.txt {
 
808
                rewrite (.*) /static/robots.txt;
 
809
            }
 
810
 
 
811
            location / {
 
812
                proxy_pass_header Server;
 
813
                proxy_set_header Host $http_host;
 
814
                proxy_redirect false;
 
815
                proxy_set_header X-Real-IP $remote_addr;
 
816
                proxy_set_header X-Scheme $scheme;
 
817
                proxy_pass http://frontends;
 
818
            }
 
819
        }
 
820
    }
 
821
 
 
822
 
 
823
WSGI and Google AppEngine
 
824
-------------------------
 
825
Tornado comes with limited support for [WSGI](http://wsgi.org/). However,
 
826
since WSGI does not support non-blocking requests, you cannot use any
 
827
of the asynchronous/non-blocking features of Tornado in your application
 
828
if you choose to use WSGI instead of Tornado's HTTP server. Some of the
 
829
features that are not available in WSGI applications:
 
830
`@tornado.web.asynchronous`, the `httpclient` module, and the `auth` module.
 
831
 
 
832
You can create a valid WSGI application from your Tornado request handlers
 
833
by using `WSGIApplication` in the `wsgi` module instead of using
 
834
`tornado.web.Application`. Here is an example that uses the built-in WSGI
 
835
`CGIHandler` to make a valid
 
836
[Google AppEngine](http://code.google.com/appengine/) application:
 
837
 
 
838
    import tornado.web
 
839
    import tornado.wsgi
 
840
    import wsgiref.handlers
 
841
 
 
842
    class MainHandler(tornado.web.RequestHandler):
 
843
        def get(self):
 
844
            self.write("Hello, world")
 
845
 
 
846
    if __name__ == "__main__":
 
847
        application = tornado.wsgi.WSGIApplication([
 
848
            (r"/", MainHandler),
 
849
        ])
 
850
        wsgiref.handlers.CGIHandler().run(application)
 
851
 
 
852
See the `appengine` example application for a full-featured AppEngine
 
853
app built on Tornado.
 
854
 
 
855
 
 
856
Caveats and support
 
857
-------------------
 
858
Tornado was refactored from the [FriendFeed](http://friendfeed.com/)
 
859
code base to reduce dependencies. This refactoring may have introduced
 
860
bugs.  Likewise, because the FriendFeed servers have always run
 
861
[behind nginx](#running-tornado-in-production), Tornado has not been
 
862
extensively tested with HTTP/1.1 clients beyond Firefox. Tornado
 
863
currently does not attempt to handle multi-line headers and some types
 
864
of malformed input.
 
865
 
 
866
You can discuss Tornado and report bugs on [the Tornado developer mailing list](http://groups.google.com/group/python-tornado).