Commits

Wang Dingwei committed 0c0bd04

adds tornado.rst

Comments (0)

Files changed (1)

+.. Tornado文档翻译
+
+========================
+python_Tornado 文档翻译
+========================
+
+.. 宏定义区
+.. |date| date::
+.. 宏定义区结束
+
+:作者: 邹业盛
+:联系: yeshengzou # # gmail.com
+:日期: |date|
+:标签: python,tornado
+
+:abstract:
+
+    对 http://www.tornadoweb.org/documentation 上的 Tornado documentation 的翻译。
+
+.. contents:: 内容索引 Table of Contents
+.. section-numbering::
+
+
+概述
+=======
+
+.. contents:: :local:
+
+FriendFeed_ 使用了一款使用 Python 编写的,相对简单的非阻塞式 Web 服务器。其\
+应用程序使用的 Web 框架看起来有些像 web.py_ 或者 Google 的 webapp_ ,不过为了\
+能有效利用非阻塞式服务器环境,这个 Web 框架还包含了一些相关的有用工具和优化。
+
+Tornado_ 就是我们在 FriendFeed 的 Web 服务器及其常用工具的开源版本。Tornado
+和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非\
+阻塞式服务器,而且速度相当快。得利于其非阻塞的方式和对 epoll_ 的运用,Tornado 
+每秒可以处理数以千计的连接,因此 Tornado 是实时 Web 服务的一个理想框架。\
+我们开发这个 Web 服务器的主要目的就是为了处理 FriendFeed 的实时功能——在
+FriendFeed 的应用里每一个活动用户都会保持着一个服务器连接。(关于如何扩容服务\
+器,以处理数以千计的客户端的连接的问题,请参阅 `The C10K problem`_ )
+
+以下是经典的 “Hello, world” 示例:
+
+.. code:: python
+
+    import tornado.ioloop
+    import tornado.web
+
+    class MainHandler(tornado.web.RequestHandler):
+        def get(self):
+            self.write("Hello, world")
+
+    application = tornado.web.Application([
+        (r"/", MainHandler),
+    ])
+
+    if __name__ == "__main__":
+        application.listen(8888)
+        tornado.ioloop.IOLoop.instance().start()
+
+查看后面的 `Tornado 攻略`_\以了解更多关于 ``tornado.web`` 包的细节。
+
+我们清理了 Tornado 的基础代码,减少了各模块之间的相互依存关系,所以理论上讲,\
+你可以在自己的项目中独立地使用任何模块,而不需要使用整个包。
+
+
+下载和安装
+============
+
+.. contents:: :local:
+
+**自动安装:** Tornado 已经列入 PyPI_\,因此可以通过 ``pip`` 或者 
+``easy_install`` 来安装。如果你没有安装 ``libcurl`` 的话,你需要将其单独安装。\
+请参见下面的安装依赖一节。注意一点,使用 ``pip`` 或 ``easy_install`` 安装的
+Tornado 并没有包含源代码中的那些 demo 应用。
+
+**手动安装:** 下载 tornado-1.2.1.tar.gz_
+
+    .. code:: shell
+    
+        tar xvzf tornado-1.2.1.tar.gz
+        cd tornado-1.2.1
+        python setup.py build
+        sudo python setup.py install
+
+Tornado 的代码 `托管在 GitHub上`_\。对于 Python 2.6 以上的版本,因为标准库中\
+已经包括了对 ``epoll`` 的支持,所以你可以不用 ``setup.py`` 编译安装,只要简单\
+地将 tornado 的目录添加到 ``PYTHONPATH`` 就可以使用了。
+
+
+安装依赖
+----------
+
+Tornado 在 Pyhton 2.5, 2.6, 2.7 当中都经过了测试。要使用 Tornado 的所有功能,\
+你需要安装 PycURL_ (7.18.2 或更高版本),以及 simplejson_ (仅适用于Python 2.5,\
+2.6 以后的版本标准库当中已经包含了对 JSON 的支持)。 Mac OS X 及 Ubuntu 中的\
+完整简便安装方法如下:
+
+**Mac OS X 10.6 (Python 2.6+)**
+
+.. code:: shell
+
+    sudo easy_install setuptools pycurl
+
+**Ubuntu Linux (Python 2.6+)**
+
+.. code:: shell
+
+    sudo apt-get install python-pycurl
+
+**Ubuntu Linux (Python 2.5)**
+
+.. code:: shell
+
+    sudo apt-get install python-dev python-pycurl python-simplejson
+
+
+模块索引
+===========
+
+.. contents:: :local:
+
+最重要的一个模块是 ``web``\,它就是包含了 Tornado 的大部分主要功能的 Web 框架\
+。其它的模块都是工具性质的,以便让 ``web`` 模块更加有用。后面的 `Tornado 攻略`_
+一节详细讲解了 ``web`` 模块的使用方法。
+
+
+主要模块
+------------
+
+* ``web`` - FriendFeed 使用的基础 Web 框架,包含了 Tornado 的大多数重要的功能
+* ``escape`` - XHTML, JSON, URL 的编码/解码方法
+* ``database`` - 对 ``MySQLdb`` 的简单封装
+* ``template`` - 基于 Python 的 web 模板系统
+* ``httpclient`` - 非阻塞式 HTTP 客户端,和 ``web`` 及 ``httpserver`` 协同工作
+* ``auth`` - 第三方认证的实现(包括 Google OpenID/OAuth、Facebook Platform、\ 
+  Yahoo BBAuth、FriendFeed OpenID/OAuth、Twitter OAuth)
+* ``locale`` - 针对本地化和翻译的支持
+* ``options`` - 命令行和配置文件解析工具,针对服务器环境做了优化
+
+底层模块
+------------
+
+* ``httpserver`` - 服务于 ``web`` 模块的一个非常简单的 HTTP 服务器的实现
+* ``iostream`` - 对非阻塞式的 socket 的简单封装,以方便常用读写操作
+* ``ioloop`` - 核心的 I/O 循环
+
+随机模块
+-----------
+
+* ``s3server`` - 一个实现了 `Amazon S3`_ 大部分接口的 Web 服务器,该服务器依存\
+  于本地的文件存储
+
+
+Tornado 攻略
+==============
+
+.. contents:: :local:
+
+
+请求处理和请求参数
+---------------------
+
+Tornado 的 Web 应用会将一组 URL 映射到 ``tornado.web.RequestHandler`` 的子类上\
+去。在其子类中定义了 ``get()`` 和 ``post()`` 这些方法,用以处理不同的 HTTP 请\
+求。
+
+下面的代码将根目录 ``/`` 映射到 ``MainHandler``\,将一个 URL 模式 
+``/story/([0-9]+)`` 映射到 ``StoryHandler``\。正则表达式匹配的分组会作为参数引\
+入 ``RequestHandler`` 的相应方法中:
+
+.. code:: python
+
+    class MainHandler(tornado.web.RequestHandler):
+    def get(self):
+        self.write("You requested the main page")
+
+    class StoryHandler(tornado.web.RequestHandler):
+        def get(self, story_id):
+            self.write("You requested the story " + story_id)
+
+    application = tornado.web.Application([
+        (r"/", MainHandler),
+        (r"/story/([0-9]+)", StoryHandler),
+    ])
+
+
+你可以使用 ``get_argument()`` 方法获取到发送过来的参数:
+
+.. code:: python
+
+    class MainHandler(tornado.web.RequestHandler):
+        def get(self):
+            self.write('<html><body><form action="/" 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.request.files`` 当中,通过 *name* 对应到一个文件列表,\
+每一个文件的格式是 ``{"filename":..., "content_type":..., "body":...}``\。
+
+如果你想要返回一个错误信息给客户端,例如“403 unauthorized”,只需要抛出一个
+``tornado.web.HTTPError`` 异常:
+
+.. code:: python
+
+    if not self.user_is_logged_in():
+        raise tornado.web.HTTPError(403)
+
+请求的处理器对象可以访问到请求的原始对象,通过 ``self.request`` 。这是一个
+``HTTPRequest`` 对象,它包括的一些常用属性有:
+
+* ``argument`` - 所有的 ``GET`` 或 ``POST`` 的参数
+* ``files`` - 通过 ``multipart/form-data`` 以 ``POST`` 方式上传的所有文件
+* ``path`` - 请求的路径( ``?`` 前面的部分)
+* ``headers`` - 请求的头部信息
+
+完整的属性请查看源码 ``httpserver`` 中 ``HTTPRequest`` 定义部分。
+
+
+RequestHandler中的主要方法
+--------------------------------------
+
+除了 ``get()`` 和 ``post()`` 以外,在 ``RequestHandler`` 中的其它方法被设计用\
+于需要时在子类中重写。对于一个请求的处理,是按如下顺序进行的:
+
+1. 每一个请求会创建一个新的 ``RequestHandler`` 对象。
+2. ``initialize()`` 方法被调用,同时 ``Application`` 中的关键字参数会被传入。\
+   ( ``initialize()`` 方法是 Tornado 1.1 中新添加的,老版本中用的是
+   ``__init__`` )。 ``initialize`` 方法一般只是把传入的参数存到成员变量中,而\
+   不会产生一些输出或者调用像 ``send_error`` 之类的方法。
+3. ``prepare()`` 被调用。这个方法通常在一个基类中被定义,然后在子类中重用。不\
+   管 HTTP 访问使用的是哪种方法 ``GET`` 或者 ``POST`` ,这个方法都会被调用。\
+   ``prepare`` 会产生输出信息,如果它调用了 ``finish`` 或者 ``send_error`` 这\
+   些方法,那么整个处理流程就此结束。
+4. 对于 HTTP 方法的相关方法被调用,像 ``get()`` , ``post()`` , ``put()`` 这\
+   些。如果 URL 的正则表达式模式中有分组匹配,那么相关匹配会作为参数传入方法。
+
+这是一个示范 ``initialize()`` 方法的例子:
+
+.. code:: python
+
+    class ProfileHandler(RequestHandler):
+        def initialize(self, database):
+            self.database = database
+
+        def get(self, username):
+            ...
+
+    app = Application([
+        (r'/user/(.*)', ProfileHandler, dict(database=database)),
+        ])
+
+其它设计用来被复写的方法有:
+
+* ``get_error_html(self, status_code, exception-None, **kargs)`` - 返回 HTML
+  字符串作为错误页。 
+* ``get_current_user(self)`` - 查看下面的\ `用户认证`_\一节。
+* ``get_user_locale(self)`` - 返回当前用户的 ``locale`` 对象。
+* ``get_login_url(self)`` - 返回一个登陆地址,在 ``@authenticated`` 装饰器中\
+  会用到(默认是 ``Application`` 中的设置)。
+* ``get_template_path(self)`` - 返回模板文件的路径(默认是 ``Application`` 中\
+  的设置)。
+
+
+模板
+-------
+
+你可以使用 Python 支持的任何一种模板语言。但是相较于其它模板而言,Tornado 自己\
+的模板系统速度更快,并且也更灵活。具体的可以查看 ``template`` 模块的源码。
+
+一个 Tornado 模板,就是一个包含了 Python 控制结构和一些表达式的 HTML 文件(或\
+者其它什么的文本文件),像这样:
+
+.. code:: html
+
+    <html>
+       <head>
+          <title>{{ title }}</title>
+       </head>
+       <body>
+         <ul>
+           {% for item in items %}
+             <li>{{ escape(item) }}</li>
+           {% end %}
+         </ul>
+       </body>
+     </html>
+
+如果你把上面的代码命名为 ``template.html`` 存到当前目录的话,你可以使用下面的\
+方法渲染它:
+
+.. code:: python
+
+    class MainHandler(tornado.web.RequestHandler):
+        def get(self):
+            items = ["Item 1", "Item 2", "Item 3"]
+            self.render("template.html", title="My title", items=items)
+
+Tornado 的模板支持控制结构和表达式。控制结构是使用 ``{% %}`` 包起来的,比如
+``{% if len(items) > 2 %}`` 。表达式是使用 ``{{ }}`` 包起来的,比如 
+``{{ items[0] }}`` 。
+
+控制结构很像 Python 自己的那一套,现在支持 ``if, for, while, try`` ,使用时都\
+需要用 ``{% %}`` 包起来。同时,也支持使用 ``extends`` 实现模板的继承,还有
+``block`` 的块结构。更多细节请查看 ``template`` 模块的源码。
+
+表达式可以是任意的 Python 表达式。包括函数调用。模板中的相关代码,会在一个单独\
+的名字空间中被执行,这个名字空间包括了以下的一些对象和方法。(注意,下面列表中\
+的对象或方法在使用 ``RequestHandler.render`` 或者 ``render_string`` 时才存在的\
+,如果你在 ``RequestHandler`` 外面直接使用 ``template`` 模块,则它们中的大部分\
+是没有的)。
+
+* ``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``
+
+在实际项目中,通常你希望使用到 Tornado 模板的全部功能,特别是继承。具体的细节\
+参阅 ``template`` 模块的源码。(一些功能,像 ``UIModules`` 是在 ``web`` 模块中\
+实现的)。
+
+在实现上, Tornado 的模板会直接转成 Python 代码。表达式会直接复制过去。 Tornado
+没有对模板作任何的限制,没有什么沙盒等保护机制。所以,当你写在模板中的语句有什\
+么错误时,你会直接得到这个错误。
+
+Cookie 和安全 Cookie
+-----------------------
+
+你可以使用 ``set_cookie`` 方法在用户的浏览中设置 cookie:
+
+.. code:: python
+
+    class MainHandler(tornado.web.RequestHandler):
+        def get(self):
+            if not self.get_cookie("mycookie"):
+                self.set_cookie("mycookie", "myvalue")
+                self.write("Your cookie was not set yet!")
+            else:
+                self.write("Your cookie was set!")
+    
+Cookie 本身很容易被恶意的客户端伪造。如果你想在 cookie 中保存当前登陆用户的 id
+之类的信息,那么最好对 cookie 作签名以防止伪造。为此,Tornado 单独提供了 
+``set_secure_cookie`` 和 ``get_secure_cookie`` 方法。要使用这些方法,你需要在\
+创建应用时提供一个密钥,叫 ``cookie_secret``\。你可以把它作为一个关键词参数传\
+入应用的设置中:
+
+.. code:: python
+
+    application = tornado.web.Application([
+        (r"/", MainHandler),
+    ], cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=")
+
+Cookie 的签名,包括有当前的 cookie 值,再另外加上时间戳和一个 HMAC_ 签名。如\
+果此 cookie 已经过期,或者签名不对,那么 ``get_secure_cookies`` 会返回 ``None``
+, cookie 不存在也是返回 ``None``\。安全 cookie 的例子如下:
+
+.. code:: python
+
+    class MainHandler(tornado.web.RequestHandler):
+        def get(self):
+            if not self.get_secure_cookie("mycookie"):
+                self.set_secure_cookie("mycookie", "myvalue")
+                self.write("Your cookie was not set yet!")
+            else:
+                self.write("Your cookie was set!")
+
+
+用户认证
+-----------
+
+当前已经认证的用户被保存在每一个请求处理器的 ``self.current_user`` 当中,同时\
+在模板的 ``current_user`` 中也是。默认情况下, ``current_user`` 为 ``None`` 。
+
+在你自己的应用实现用户认证的功能,需要复写请求处理中 ``get_current_user()`` 这\
+个方法,在其中判定当前用户的状态,比如 cookie 。下面的例子让用户简单地使用一个
+nickname 登陆应用,之后会把登陆信息保存到 cookie 中:
+
+.. code:: python
+
+    class BaseHandler(tornado.web.RequestHandler):
+        def get_current_user(self):
+            return self.get_secure_cookie("user")
+
+    class MainHandler(BaseHandler):
+        def get(self):
+            if not self.current_user:
+                self.redirect("/login")
+                return
+            name = tornado.escape.xhtml_escape(self.current_user)
+            self.write("Hello, " + name)
+
+    class LoginHandler(BaseHandler):
+        def get(self):
+            self.write('<html><body><form action="/login" method="post">'
+                       'Name: <input type="text" name="name">'
+                       '<input type="submit" value="Sign in">'
+                       '</form></body></html>')
+
+        def post(self):
+            self.set_secure_cookie("user", self.get_argument("name"))
+            self.redirect("/")
+
+    application = tornado.web.Application([
+        (r"/", MainHandler),
+        (r"/login", LoginHandler),
+    ], cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=")
+
+对于那些必须要求用户登陆的操作,可以使用装饰器 ``tornado.web.authenticated``\。
+如果一个方法套上了这个装饰器,但是当前用户并没有登陆的话,页面会被重定向到
+``login_url``\(应用配置中的一个选项),上面的例子可以被改写成:
+
+.. code:: python
+
+    class MainHandler(BaseHandler):
+        @tornado.web.authenticated
+        def get(self):
+            name = tornado.escape.xhtml_escape(self.current_user)
+            self.write("Hello, " + name)
+
+    settings = {
+        "cookie_secret": "61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
+        "login_url": "/login",
+    }
+    application = tornado.web.Application([
+        (r"/", MainHandler),
+        (r"/login", LoginHandler),
+    ], **settings) #**
+
+
+如果被装饰的是 ``post()`` 方法,那么在用户没有登陆的状态下,服务器会返回 403
+错误。
+
+Tornado 内部集成了对第三方认证形式的支持,比如 Google 的 OAuth 。参阅 ``auth``
+模块以了解更多的细节。在 Tornado 的源码中,有一个 Blog 的例子,展示了一个完整\
+的用户认证的应用(用户数据需要保存到 MySQL 数据库中)。
+
+
+跨站伪造请求的防范
+-----------------------
+
+`跨站伪造请求`_\又简称为 XSRF,对于个性化 Web 应用来说是一个常见的安全问题。
+前面的链接也详细讲述了 XSRF 攻击的实现方式。
+
+当前防范 XSRF 的一种有效方法,是对每一个用户都记录一个无法预先知道的 cookie 
+数据,然后要求所有提交上来的请求中都必须带有这个 cookie 数据。如果此数据不匹配\
+,那么这个请求就可能是被伪造的。
+
+Tornado 内建有 XSRF 的防范机制,要使用它,在应用配置中加上 ``xsrf_cookies``\:
+
+.. code:: python
+
+    settings = {
+        "cookie_secret": "61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
+        "login_url": "/login",
+        "xsrf_cookies": True,
+    }
+    application = tornado.web.Application([
+        (r"/", MainHandler),
+        (r"/login", LoginHandler),
+    ], **settings) #**
+
+如果设置了 ``xsrf_cookies``\,那么 Tornado 的 Web 应用将对所有用户设置一个 
+``_xsrf`` 的 cookie 值,并且假如过来的 ``POST`` ``PUT`` ``DELET`` 请求中没有这\
+个 cookie 值,那么请求会被直接拒绝。如果使用这个机制,那么在所有会被提交的表单\
+中,你都需要加上一个域,以提供此值。有一个 ``xsrf_form_html()`` 可以帮你在模板\
+中做到这点:
+
+.. code:: html
+
+    <form action="/new_message" method="post">
+        {{ xsrf_form_html() }}
+        <input type="text" name="message"/>
+        <input type="submit" value="Post"/>
+    </form>
+
+如果你是使用 AJAX 的方式,以 ``POST`` 方法提交数据的话,你仍然需要在每一个请求\
+中通过脚本添加上 ``_xsrf`` 这个值。下面是在 FriendFeed 中的 AJAX 的 ``POST`` 
+请求,使用了 jQuery_ 来自动添加上 ``_xsrf``\:
+
+.. code:: javascript
+
+    function getCookie(name) {
+        var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
+        return r ? r[1] : undefined;
+    }
+
+    jQuery.postJSON = function(url, args, callback) {
+        args._xsrf = getCookie("_xsrf");
+        $.ajax({url: url, data: $.param(args), dataType: "text", type: "POST",
+            success: function(response) {
+            callback(eval("(" + response + ")"));
+        }});
+    };
+
+对于 ``PUT`` ``DELETE`` 请求,以及不使用将 form 内容作为参数的 ``POST`` 请求来\
+说,你也可以在 HTTP 头中以 ``X-XSRFToken`` 这个参数传递 XSRF token。
+
+
+静态文件和主动式文件缓存
+---------------------------
+
+你能通过在应用配置中指定 ``static_path`` 选项来提供静态文件服务:
+
+.. code:: python
+
+    settings = {
+        "static_path": os.path.join(os.path.dirname(__file__), "static"),
+        "cookie_secret": "61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
+        "login_url": "/login",
+        "xsrf_cookies": True,
+    }
+    application = tornado.web.Application([
+        (r"/", MainHandler),
+        (r"/login", LoginHandler),
+    ], **settings) #**
+
+这样配置后,所有以 ``/static/`` 开头的请求,都会直接访问到指定的静态文件目录,\
+比如 ``http://localhost:8888/static/foo.png`` ,会从指定的静态文件目录中访问到
+``foo.png`` 这个文件。同时, ``/robots.txt`` 和 ``/favicon.ico`` 也是会自动作\
+为静态文件处理(即使它们不是以 ``/static/`` 开头)。
+
+为了提高性能,在浏览器主动缓存静态文件是个不错的主意。这样,浏览器就不需要发送\
+不必要的 ``If-Modified-Since`` 和 ``Etag`` 请求,从而影响页面的渲染速度。
+Tornado 有一套单独的机制支持这种做法。
+
+要使用这个功能,在模板中就不要直接输入静态文件的 URL 地址,而是使用 
+``static_url()`` 这个方法来提供 URL 地址:
+
+.. code:: html
+
+    <html>
+        <head>
+            <title>FriendFeed - {{ _("Home") }}</title>
+        </head>
+        <body>
+            <div><img src="{{ static_url("images/logo.png") }}"/></div>
+        </body>
+    </html>
+
+``static_url()`` 这个方法会把相对地址转成一个 URI ,类似于 
+``/static/images/logo.png?v=aae54`` 这种形式。 ``v`` 参数是 ``logo.png`` 这个\
+文件的散列值, Tornado 服务器会把它发给浏览器,并以此为依据让浏览器对相关内容\
+做永久缓存。
+
+因为 ``v`` 的值是基于文件的内容计算出来的,如果你更新了文件,或者重启了服务器\
+,那么就会得到一个新的 ``v`` 值,这样,浏览器就会请求服务器以获取新的文件内容\
+。如果文件的内容没有改变,浏览器就会一直使用本地缓存的文件,这样可以显著提高页\
+面的渲染速度。
+
+在生产环境下,你可能会使用更有利于静态文件伺服环境的服务器,比如 nginx_\。大多\
+数的 Web 服务器,都可以配置对静态文件缓存的支持,下面是 FriendFeed 使用的 nginx
+的相关配置:
+
+.. code:: nginx 
+
+    location /static/ {
+        root /var/friendfeed/static;
+        if ($query_string) {
+            expires max;
+        }
+     }
+
+
+本地化
+--------
+
+不管是否登陆,当前用户的 locale 设置都被保存在请求处理器的 ``self.locale`` 对\
+象中,同时模板中的 ``locale`` 也是。 locale 的名字(如 ``en_US`` )是
+``locale.name`` 这个变量,你可以使用 ``locale.translate`` 方法来进行本地化的翻\
+译。在模板中,有一个全局的方法叫 ``_()`` ,它的作用就是进行本地化的翻译。这个\
+翻译方法有两种使用形式:
+
+.. code:: python
+
+    _("Translate this string")
+
+将字符串直接翻成本地语言,还有一种是:
+
+.. code:: python
+
+    _("A person liked this",
+        "%(num)d people liked this", len(people)) % {"num": len(people)}
+
+这种形式,会根据第三个参数,来决定是使用单数或是复数的翻译。上面的例子中,如果 
+``len(people)`` 是 ``1`` 的话,就使用第一种形式的翻译,否则,就使用第二种形式\
+的翻译。
+
+进行字符串翻译常用的一种形式,就是使用 Python 的 placeholders 语法,(上面的 
+``%(num)d`` 就是一个 placeholders ),因为 placeholders 相较于普通的格式化字符\
+串占位符,没有顺序的限制。
+
+一个本地化翻译的模板例子:
+
+.. code:: html
+
+    <html>
+        <head>
+            <title>FriendFeed - {{ _("Sign in") }}</title>
+        </head>
+        <body>
+            <form action="{{ request.path }}" method="post">
+                <div>{{ _("Username") }} <input type="text" name="username"/></div>
+                <div>{{ _("Password") }} <input type="password" name="password"/></div>
+                <div><input type="submit" value="{{ _("Sign in") }}"/></div>
+                {{ xsrf_form_html() }}
+            </form>
+        </body>
+    </html>
+
+默认情况下,我们通过 ``Accept-Language`` 这个头来判定用户的 locale,如果没有,\
+则取 ``en_US`` 这个值。如果希望用户手动设置一个 locale 偏好,可以在处理请求的\
+类中复写 ``get_user_locale`` 方法:
+
+.. code:: python
+
+    class BaseHandler(tornado.web.RequestHandler):
+        def get_current_user(self):
+            user_id = self.get_secure_cookie("user")
+            if not user_id: return None
+            return self.backend.get_user_by_id(user_id)
+
+        def get_user_locale(self):
+            if "locale" not in self.current_user.prefs:
+                # Use the Accept-Language header
+                return None
+            return self.current_user.prefs["locale"]
+
+如果 ``get_user_locale`` 返回 ``None``\,那么就会再去取 ``Accept-Language`` 头\
+的值。
+
+你可以使用 ``tornado.locale.load_translations`` 方法获取应用中的所有已存在的翻\
+译。它会找到包含有特定名字的 csv 文件的目录,像 ``es_GT.csv`` ``fr_CA.csv`` 这\
+些 csv 文件。然后从这些 CSV 文件中读取出所有的与特定语言相关的翻译内容。一般我\
+们在 ``main()`` 方法中就调用一下这个方法:
+
+.. code:: python
+
+    def main():
+        tornado.locale.load_translations(
+            os.path.join(os.path.dirname(__file__), "translations"))
+        start_server()
+
+同时,你可以使用 ``tornado.locale.get_supported_locales()`` 方法得到被支持的 
+locale 列表。用户当前的 locale 设置,会使服务器选择一个最接近的,可被支持的
+locale 作为翻译的目标语言。比如,用户的 locale 是 ``es_GT`` ,而只有 ``es`` 
+是可被支持的,那么 ``self.locale`` 的值就是 ``es`` 。如果连最接近的都没有找到\
+的话,就会取 ``es_US`` 。
+
+查看 ``locale`` 模块的源码,获取关于 CSV 文件具体的格式信息,及其它与本地化机\
+制相关的内容。
+
+
+UI模块
+--------------------------------------
+
+Tornado 支持的 UI 模块,可以使你创建标准的,在应用中可以被重用的 UI 组件。 UI
+模块的作用,大概就是定义一些特殊的方法,这些方法可以在页面中渲染出具体的组件,\
+这些组件可以有自己的 CSS 和 JavaScript 。
+
+举一个例子,如果你正在写一个博客的应用,你希望在首页,和具体的博客内容页,都要\
+显示文章的内容,那么你就可以创建一个 ``Entry`` 模型用以在两个页面渲染文章的内\
+容。第一步,为你的 UI 模块创建一个 Python 文件,比如叫 ``uimodules.py`` :
+
+.. code:: python
+
+    class Entry(tornado.web.UIModule):
+        def render(self, entry, show_comments=False):
+            return self.render_string(
+                "module-entry.html", show_comments=show_comments)
+
+然后通过 ``ui_modules`` 配置项告诉 Tornado 在应用当中使用 ``uimodules.py`` :
+
+.. code:: python
+
+    class HomeHandler(tornado.web.RequestHandler):
+        def get(self):
+            entries = self.db.query("SELECT * FROM entries ORDER BY date DESC")
+            self.render("home.html", entries=entries)
+
+    class EntryHandler(tornado.web.RequestHandler):
+        def get(self, entry_id):
+            entry = self.db.get("SELECT * FROM entries WHERE id = %s", entry_id)
+            if not entry: raise tornado.web.HTTPError(404)
+            self.render("entry.html", entry=entry)
+
+    settings = {
+        "ui_modules": uimodules,
+    }
+    application = tornado.web.Application([
+        (r"/", HomeHandler),
+        (r"/entry/([0-9]+)", EntryHandler),
+    ], **settings) #**
+
+在 ``home.html`` 当中,与其直接输入相关的 HTML 代码,引用 ``Entry`` 模型是更优\
+雅的做法:
+
+.. code:: html
+
+    {% for entry in entries %}
+        {{ modules.Entry(entry) }}
+    {% end %}
+
+在 ``entry.html`` 中,同样也是引用 ``Entry`` 模型,只是把参数 ``show_comments``
+设置为 ``True`` :
+
+.. code:: html
+
+    {{ modules.Entry(entry, show_comments=True) }}
+
+每一个 UI 模型,都可以配置自己的 CSS 和 JavaScript ,相关的方法是复写 
+``embedded_css`` ``embedded_javascript`` ``javascipt_files`` ``css_files`` :
+
+.. code:: python
+
+    class Entry(tornado.web.UIModule):
+        def embedded_css(self):
+            return ".entry { margin-bottom: 1em; }"
+
+        def render(self, entry, show_comments=False):
+            return self.render_string(
+                "module-entry.html", show_comments=show_comments)
+
+模型的 CSS 和 JavaScript 部分只会被渲染一次,无论一页中有多少个相同的 UI 组件\
+。 CSS 是在页面的 ``<head>`` 部分,而 JavaScript 被渲染在 ``</body>`` 之前的位\
+置。
+
+
+非阻塞式异步请求
+------------------
+
+当一个处理请求的行为被执行之后,这个请求会自动地结束。因为 Tornado 当中使用了\
+一种非阻塞式的 I/O 模型,所以你可以改变这种默认的处理行为——让一个请求一直保持\
+连接状态,而不是马上返回,直到一个主处理行为返回。要实现这种处理方式,只需要简\
+单地使用 ``tornado.web.asynchronous`` 装饰器就可以了。
+
+使用了这个装饰器之后,只有当显式地调用 ``self.finish()``\,请求才会被返回,在\
+这期间,用户的浏览器会一直处于等待服务器响应的状态:
+
+.. code:: python
+
+    class MainHandler(tornado.web.RequestHandler):
+        @tornado.web.asynchronous
+        def get(self):
+            self.write("Hello, world")
+            self.finish()
+
+下面是一个使用 Tornado 内置的异步请求 HTTP 客户端去调用 FriendFeed 的 API 的例\
+子:
+
+.. code:: python
+
+    class MainHandler(tornado.web.RequestHandler):
+        @tornado.web.asynchronous
+        def get(self):
+            http = tornado.httpclient.AsyncHTTPClient()
+            http.fetch("http://friendfeed-api.com/v2/feed/bret",
+                       callback=self.on_response)
+
+        def on_response(self, response):
+            if response.error: raise tornado.web.HTTPError(500)
+            json = tornado.escape.json_decode(response.body)
+            self.write("Fetched " + str(len(json["entries"])) + " entries "
+                       "from the FriendFeed API")
+            self.finish()
+
+例子中,当 ``get()`` 方法返回时,请求处理还没有完成。在 HTTP 客户端执行它的回\
+调函数 ``on_response()`` 时,从浏览器过来的请求仍然是存在的,只有在显式调用了 
+``self.finish()`` 之后,才会把响应返回到浏览器。
+
+关于更多异步请求的高级例子,可以参阅 demo 中的 ``chat`` 这个实现。它是一个使用\
+了 `long polling`_ 方式的 AJAX 聊天室。一个长连接的用户,在客户端关闭连接时应\
+该调用一下可能被复写了的 ``on_connection_close()`` 方法,以便做一些清理性的工\
+作,更详细的内容,查查看源码中的该方法实现,以避免走入一些误区。
+
+
+第三方认证
+------------
+
+Tornado 的 ``auth`` 模块实现了现在很多流行站点的用户认证方式,包括 
+Google/Gmail、Facebook、Twitter、Yahoo 以及 FriendFeed。这个模块可以让用户使用\
+这些站点的账户来登陆你自己的应用,然后你就可以在授权的条件下访问原站点的一些服\
+务,比如下载用户的地址薄,在 Twitter 上发推等。
+
+这里有一个例子,使用了 Google 的账户认证,之后把 Google 授权了一个签名串保存到 
+cookie 当中,以便之后的访问之用:
+
+.. code:: python
+
+    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()
+
+查看 ``auth`` 模块的源码以了解更多的细节。
+
+
+关于性能
+======================================
+
+.. contents:: :local:
+
+一个 Web 应用的性能表现,主要看它的整体架构,而不仅仅是前端的表现。Tornado 相\
+较于其它的 Python Web 框架,它要快得多。
+
+我们在一些流行的 Python Web 框架上——有 Django_\、\ web.py_\、\ CherryPy_\,针\
+对最简单的 Hello, world 例子作了一个测试。对于 Django 和 web.py,我们使用
+Apache/mod_wsgi 的方式来带,CherryPy 就让它自己裸跑。这种部署方案,也是在生产\
+环境中各框架常用的。对于我们的 Tornado,使用的部署方案为前端使用 nginx_\做反向\
+代理,带 4 个线程模式的 Tornado,这种方案也是我们推荐的在生产环境下的 Tornado
+部署方案(根据具体的硬件情况,我们推荐一个 CPU 核对应一个 Tornado 伺服实例,我\
+们的 CPU 是四核的)。
+
+测试的具体方法是,使用 Apache Benchmark (``ab``) 工具,进行并发访问测试:
+
+.. code:: shell
+
+    ab -n 100000 -c 25 http://10.0.1.x/
+
+在 AMD Opteron 2.4GHz 的四核机器上,结果如下图所示:
+
+.. image:: 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
+
+在我们的测试当中,相较于第二快的服务器,Tornado 在数据上的表现也是它的 4 倍之\
+多。即使只用了一个 CPU 核的裸跑模式, Tornado 也有 33% 的优势。
+
+当然,这仅仅是一个理想状态下的测试结果。但是也足以说明我们对于 Tornado 在性能\
+上的态度。对你的应用而言, Tornado 相较于其它框架在性能上是有优势的。
+
+
+生产环境下的部署
+==================
+
+在 FriendFeed 中,我们使用 nginx_ 做负载均衡,同时也使用它作静态文件的伺服。在\
+多台服务器上,同时部署多个 Tornado 实例。通常,对应 CPU 的内核数,一个核对应一\
+个 Tornado 线程。
+
+因为我们的 Web 服务器是跑在负载均衡服务器后面的,所以,需要把 ``xheaders=True``
+传到 ``HTTPServer`` 的构造器当中去。这是为了让 Tornado 使用像 ``X-Real-IP`` 
+的头部信息来获取用户的真实 IP 地址,而不是使用传统的方法,因为那样你只能得到\
+这个负载均衡服务器的 IP 地址。
+
+下面是 nginx 配置文件的一个示例,整体上与我们在 FriendFeed 中使用的差不多。\
+它假设 nginx 和 Tornado 是跑在同一台机器上的,四个 Tornado 服务跑在 8000-8003
+端口上:
+
+.. code:: nginx
+
+    user nginx;
+    worker_processes 1;
+
+    error_log /var/log/nginx/error.log;
+    pid /var/run/nginx.pid;
+
+    events {
+        worker_connections 1024;
+        use epoll;
+    }
+
+    http {
+        # Enumerate all the Tornado servers here
+        upstream frontends {
+            server 127.0.0.1:8000;
+            server 127.0.0.1:8001;
+            server 127.0.0.1:8002;
+            server 127.0.0.1:8003;
+        }
+
+        include /etc/nginx/mime.types;
+        default_type application/octet-stream;
+
+        access_log /var/log/nginx/access.log;
+
+        keepalive_timeout 65;
+        proxy_read_timeout 200;
+        sendfile on;
+        tcp_nopush on;
+        tcp_nodelay on;
+        gzip on;
+        gzip_min_length 1000;
+        gzip_proxied any;
+        gzip_types text/plain text/html text/css text/xml
+                   application/x-javascript application/xml
+                   application/atom+xml text/javascript;
+
+        # Only retry if there was a communication error, not a timeout
+        # on the Tornado server (to avoid propagating "queries of death"
+        # to all frontends)
+        proxy_next_upstream error;
+
+        server {
+            listen 80;
+
+            # Allow file uploads
+            client_max_body_size 50M;
+
+            location ^~ /static/ {
+                root /var/www;
+                if ($query_string) {
+                    expires max;
+                }
+            }
+            location = /favicon.ico {
+                rewrite (.*) /static/favicon.ico;
+            }
+            location = /robots.txt {
+                rewrite (.*) /static/robots.txt;
+            }
+
+            location / {
+                proxy_pass_header Server;
+                proxy_set_header Host $http_host;
+                proxy_redirect false;
+                proxy_set_header X-Real-IP $remote_addr;
+                proxy_set_header X-Scheme $scheme;
+                proxy_pass http://frontends;
+            }
+        }
+    }
+
+WSGI 和 Google AppEngine 
+==========================
+
+Tornado 只对 WSGI_ 提供了有限的支持,即使如此,因为 WSGI 并不支持非阻塞式的请\
+求,所以如果你使用 WSGI 代替 Tornado 自己的 HTTP 服务的话,那么你将无法使用 
+Tornado 的异步非阻塞式的请求处理方式。比如 ``@tornado.web.asynchronous`` ,
+``httpclient`` 模块,\ ``auth`` 模块,这些都无法使用。
+
+你可以使用 ``wsgi`` 模块中的 ``WSGIApplication`` 来切换到 WSGI ,而不用之前我\
+们使用的 ``tornado.web.Applicaion`` 。下面的例子展示使用内置的 WSGI 
+``CGIHandler`` 来创建一个 `Google AppEngine`_ 应用:
+
+.. code:: python
+
+    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)
+
+查看 demo 中的 ``appengine`` ,了解如何使用 Tornado 创建一个完整的 AppEngine 
+应用。
+
+支持
+======
+
+因为 FriendFeed 以及其它的一些基于 Tornado 的大型应用都是运行在 nginx 或者 
+Apache `代理之后`_\的,所以现在 Tornado 的 HTTP 服务部分并不完整,它无法处理\
+多行的头部信息,同时对于一些非标准的输入也无能为力。
+
+你可以在 `Tornado 邮件列表`_\上讨论和提交 Bug 。
+
+Tornado 是 `Facebook 开源技术`_ 之一,以 `Apache Licence 2.0 版`_\的方式进行授权。
+
+原文档的许可形式为 `Creative Commons 3.0`_\。
+
+.. _FriendFeed: http://friendfeed.com
+.. _web.py: http://webpy.org
+.. _webapp: http://code.google.com/appengine/docs/python/tools/webapp
+.. _Tornado: http://github.com/facebook/tornado
+.. _epoll: http://www.kernel.org/doc/man-pages/online/pages/man4/epoll.4.html
+.. _`The C10K problem`: http://www.kegel.com/c10k.html
+.. _PyPI: http://pypi.python.org/pypi/tornado
+.. _tornado-1.2.1.tar.gz: http://github.com/downloads/facebook/tornado/tornado-1.2.1.tar.gz
+.. _`托管在 GitHub 上`: http://github.com/facebook/tornado
+.. _PycURL: http://pycurl.sourceforge.net
+.. _simplejson: http://pypi.python.org/pypi/simplejson
+.. _`Amazon S3`: http://aws.amazon.com/s3
+.. _HMAC: http://en.wikipedia.org/wiki/HMAC
+.. _跨站伪造请求: http://en.wikipedia.org/wiki/Cross-site_request_forgery
+.. _jQuery: http://jquery.com
+.. _nginx: http://nginx.net
+.. _`long polling`: http://en.wikipedia.org/wiki/Push_technology#Long_polling
+.. _Django: http://www.djangoproject.com
+.. _CherryPy: http://www.cherrypy.org
+.. _WSGI: http://wsgi.org
+.. _`Google AppEngine`: http://code.google.com/appengine
+.. _代理之后: http://www.tornadoweb.org/documentation#running-tornado-in-production
+.. _`Tornado 邮件列表`: http://groups.google.com/group/python-tornado
+.. _`Facebook 开源技术`: http://developers.facebook.com/opensource.php
+.. _`Apache Licence 2.0 版`: http://www.apache.org/licenses/LICENSE-2.0.html
+.. _`Creative Commons 3.0`: http://creativecommons.org/licenses/by/3.0/
+