中文翻译地址:http://demo.pythoner.com/itt2zh/index.html
虽然一个是几年前的书了,还是值的读一下
翻译的非常好,以下只是个人的笔记

引言

import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web

from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)

class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        greeting = self.get_argument('greeting', 'Hello')
        self.write(greeting + ', friendly user!')

    # 覆盖错误返回
    def write_error(self, status_code, **kwargs):
        self.write("Gosh darnit, user! You caused a %d error." % status_code)

if __name__ == "__main__":
    tornado.options.parse_command_line()
    app = tornado.web.Application(handlers=[(r"/", IndexHandler)])
    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

405 Method Not Allowed

表单和模板

class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.render('index.html')

    def post(self):
        noun1 = self.get_argument('noun1')

模板流程控制

if、for、while、try、{% set foo = 'bar' %}
结束使用{% end %}
模板默认自动转译, 可以关闭,在App初始化时配置,autoescape=None
模板中使用函数

escape(s)
url_escape(s)
json_encode(val)
squeeze(s)   # 把连续的多个空白字符替换成一个空格
static_url("style.css"), # static_url函数创建了一个基于文件内容的hash值

使用自己定义的函数

from tornado.template import Template
Template("my name is {{d('mortimer')}}").generate(d=disemvowel)

static和template设置

    app = tornado.web.Application(
        handlers=[(r'/', IndexHandler), (r'/poem', MungedPageHandler)],
        template_path=os.path.join(os.path.dirname(__file__), "templates"),
        static_path=os.path.join(os.path.dirname(__file__), "static"),
        debug=True
    )

模板块

{% extends "main.html" %}
{% block header %}
    <h1>Hello world!</h1>
{% end %}

可以利用linkify()简记的好处,又可以保持在其他地方自动转义的好处

{% raw linkify("https://fb.me/burtsbooks", extra_params='ref=website') %}.

UI模块

class HelloModule(tornado.web.UIModule):
   def render(self, sample):
        return self.render_string(
            "modules/sample.html",
            sample=sample
        )

    def html_body(self):
        return "<div class=\"addition\"><p>html_body()</p></div>"

    def embedded_javascript(self):
        return "document.write(\"<p>embedded_javascript()</p>\")"

    def embedded_css(self):
        return ".addition {color: #A1CAF1}"

    def css_files(self):
        return "/static/css/sample.css"

    def javascript_files(self):
        return "/static/js/sample.js"

app = tornado.web.Application(
        ui_modules={'Hello': HelloModule}
)

{% module Hello() %}

数据库

MongoDB基本操作, 使用PyMongo,
连接:

>>> import pymongo
>>> conn = pymongo.Connection("localhost", 27017)
conn = pymongo.Connection("mongodb://user:password@staff.mongohq.com:10066/your_mongohq_db")

获得db对象,没有就创建

db = conn.example or: db = conn.['example']

获取所有集合

db.collection_names()

插入数据

widgets = db.widgets or: widgets = db['widgets']
widgets.insert({'foo': 'bar'})
# 自动增加一个`_id`字段

获取数据

doc = widgets.find_one({'foo': 'bar'})

修改数据

doc['foo'] = ['barbar']

保存的数据库

widgets.save(doc)

返回所有集合列表

widgets.find()

删除对象

widgets.remove({'foo': 'barbar'})

将返回的对象转换为字典

del doc['_id']  # 删除_id
json.dumps(doc)

异步Web请求

同步请求

client = tornado.httpclient.HTTPClient()
response = client.fetch("http://search.twitter.com/search.json?" + \
        urllib.urlencode({"q": query, "result_type": "recent", "rpp": 100}))
body = json.loads(response.body)

异步请求

@tornado.web.asynchronous   # 实现长轮询,不会自动断开连接
def get(self):
    query = self.get_argument('q')
    client = tornado.httpclient.AsyncHTTPClient()   # 异步client
    client.fetch("http://search.twitter.com/search.json?" + \
            urllib.urlencode({"q": query, "result_type": "recent", "rpp": 100}),
            callback=self.on_response)  # 回调函数
def on_response(self, response):
    body = json.loads(response.body)
    ...
    self.write()
    self.finish()       # 表示请求结束,断开请求

异步生成器

 @tornado.web.asynchronous
    @tornado.gen.engine
    def get(self):
        query = self.get_argument('q')
        client = tornado.httpclient.AsyncHTTPClient()
        response = yield tornado.gen.Task(client.fetch,     # yield, 继续
                "http://search.twitter.com/search.json?" + \
                urllib.urlencode({"q": query, "result_type": "recent", "rpp": 100}))
        body = json.loads(response.body)
        ...
        self.write()
        self.finish()       # 表示请求结束,断开请求

长轮询的缺陷
当使用长轮询开发应用时,记住对于浏览器,请求超时无法控制是非常重要的。由浏览器决定在任何中断情况下重新开启HTTP连接。另一个潜在的问题是许多浏览器限制了对于打开的特定主机的并发请求数量。当有一个连接保持空闲时,剩下的用来下载网站内容的请求数量就会有限制。此外,你还应该明白请求是怎样影响服务器性能的。

WebSocket

WebSockets是HTML5规范中新提出的客户-服务器通讯协议。

class StatusHandler(tornado.websocket.WebSocketHandler):
    def open(self):
        self.application.shoppingCart.register(self.callback)

    def on_close(self):
        self.application.shoppingCart.unregister(self.callback)

    def on_message(self, message):
        pass

    def callback(self, count):
        self.write_message('{"inventoryCount":"%d"}' % count)  # 返回数据

JS:

function requestInventory() {
    var host = 'ws://localhost:8000/cart/status';

    var websocket = new WebSocket(host);

    websocket.onopen = function (evt) { };
    websocket.onmessage = function(evt) {
        $('#count').html($.parseJSON(evt.data)['inventoryCount']);
    };
    websocket.onerror = function (evt) { };
}

安全的Cookies

有很多方式可以在浏览器中截获cookies。JavaScript和Flash对于它们所执行的页面的域有读写cookies的权限。浏览器插件也可由编程方法访问这些数据。跨站脚本攻击可以利用这些访问来修改访客浏览器中cookies的值。干脆窃听未加密的网络数据。
Tornado的安全cookies使用加密签名来验证cookies的值没有被服务器软件以外的任何人修改过。Tornado的set_secure_cookie()和get_secure_cookie()函数发送和取得浏览器的cookies,以防范浏览器中的恶意修改。为了使用这些函数,你必须在应用的构造函数中指定cookie_secret参数。让我们来看一个简单的例子。
然而,Tornado的安全cookies仍然容易被窃听。安全的cookie值是签名的而不是加密的。

我们还需要注意用户可能修改他自己的cookies的可能性,这会导致提权攻击。比如,如果我们在cookie中存储了用户已付费的文章剩余的浏览数,我们希望防止用户自己更新其中的数值来获取免费的内容。httponlysecure属性可以帮助我们防范这种攻击。
为cookie设置secure属性来指示浏览器只通过SSL连接传递cookie。从Python 2.6版本开始,Cookie对象还提供了一个httponly属性。包括这个属性指示浏览器对于JavaScript不可访问cookie,这可以防范来自读取cookie值的跨站脚本攻击。
self.set_cookie(‘foo’, ‘bar’, httponly=True, secure=True)

CSRF

跨站请求伪造,通常被简写为CSRF或XSRF,发音为"sea surf"。
为了防范伪造POST请求,我们会要求每个请求包括一个参数值作为令牌来匹配存储在cookie中的对应值。我们的应用将通过一个cookie头和一个隐藏的HTML表单元素向页面提供令牌。当一个合法页面的表单被提交时,它将包括表单值和已存储的cookie。如果两者匹配,我们的应用认定请求有效。由于第三方站点没有访问cookie数据的权限,他们将不能在请求中包含令牌cookie。这有效地防止了不可信网站发送未授权的请求。

使用Tornado的XSRF保护

settings = {
    "cookie_secret": "bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E=",
    "xsrf_cookies": True
}
application = tornado.web.Application([
    (r'/', MainHandler),
    (r'/purchase', PurchaseHandler),
], **settings)

模板中使用

<form action="/purchase" method="POST">
    {% raw xsrf_form_html() %}
    <input type="text" name="title" />
    <input type="text" name="quantity" />
    <input type="submit" value="Check Out" />
</form>

XSRF令牌和AJAX请求

function getCookie(name) {
    var c = document.cookie.match("\\b" + name + "=([^;]*)\\b");
    return c ? c[1] : undefined;
}

jQuery.postJSON = function(url, data, callback) {
    data._xsrf = getCookie("_xsrf");
    jQuery.ajax({
        url: url,
        data: jQuery.param(data),
        dataType: "json",
        type: "POST",
        success: callback
    });
}

用户验证
authenticated装饰器
self.current_user 默认为None
覆盖get_current_user()方法
如果current_user值为假(None、False、0、""),重定向到login_url
在app初始化时,加入设置"login_url": "/login",
self.clear_cookie(“username”) 删除cookie
self.redirect("/") 重定向
self.clear_all_cookies() 删除所有cookie
raise tornado.web.HTTPError(500, “Couldn’t retrieve user information”) 返回错误

外部服务认证

Tornado的auth模块

部署

设想在一个Tornado执行的数据库查询或磁盘访问块中,进程不允许回应新的请求。这个问题最简单的解决方法是运行多个解释器的实例。通常情况下,你会使用一个反向代理,比如Nginx,来非配多个Tornado实例的加载。
Nginx的SSL解密
使用Supervisor监控Tornado进程

10-06 11:56