python web框架——初识tornado

时间:2023-03-09 15:50:50
python web框架——初识tornado

一 Tornado概述

Tornado是FriendFeed使用的可扩展的非阻塞式web框架及其相关工具的开源版本。这个Web框架看起来有些像web.py或者Google的 webapp,不过为了能有效利用非阻塞式服务器环境,这个Web框架还包含了一些相关的有用工具和优化。
   Tornado和现在的主流Web服务器框架(包括大多数Python的框架)有着明显的区别:它是非阻塞式服务器框架,而且速度相当快。由于其非阻塞的方式和对epoll的运用,Tornado每秒可以处理数以千计的连接,这意味着对于实时Web服务来说,Tornado是一个理想的Web框架。开发这个Web框架的主要目的是为了处理FriendFeed的实时功能——在FriendFeed的应用里每一个活动用户都会保持着一个服务器连接(关于如何扩容服务器,以处理数以千计的客户端的连接的问题,请参阅C10K问题),同时Tornado是轻量级的web框架,它主要解决了性能问题,让开发者可以灵活的去扩展相关组件。

                                                          C10K问题

    基于线程的服务器,如Apache,为了传入的连接,维护了一个操作系统的线程池。Apache会为每个HTTP连接分配线程池中的一个线程,如果所有的线程都处于被占用的状态并且尚有内存可用时,则生成一个新的线程。尽管不同的操作系统会有不同的设置,大多数Linux发布版中都是默认线程堆大小为8MB。Apache的架构在大负载下变得不可预测,为每个打开的连接维护一个大的线程池等待数据极易迅速耗光服务器的内存资源。

   大多数社交网络应用都会展示实时更新来提醒新消息、状态变化以及用户通知,这就要求客户端需要保持一个打开的连接来等待服务器端的任何响应。这些长连接或推送请求使得Apache的最大线程池迅速饱和。一旦线程池的资源耗尽,服务器将不能再响应新的请求。

   异步服务器在这一场景中的应用相对较新,但他们正是被设计用来减轻基于线程的服务器的限制的。当负载增加时,诸如Node.js,lighttpd和Tornodo这样的服务器使用协作的多任务的方式进行优雅的扩展。也就是说,如果当前请求正在等待来自其他资源的数据(比如数据库查询或HTTP请求)时,一个异步服务器可以明确地控制以挂起请求。异步服务器用来恢复暂停的操作的一个常见模式是当合适的数据准备好时调用回调函数。

二  Tornado使用入门

1 安装

pip安装

pip install tornado
源码安装

https://pypi.python.org/packages/source/t/tornado/tornado-4.3.tar.gz

python setup.py install

2 Tornado执行流程

#!/usr/bin/env python
# -*- coding:utf-8 -*- import tornado.ioloop
import tornado.web class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world") application = tornado.web.Application([
(r"/index", MainHandler),
]) if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
第1步:执行脚本,监听8888端口;
第2步:浏览器客户端访问/index --> http://127.0.0.1:8888/index;
第3步:服务器接受请求,并交由对应的类处理该请求;
第4步:类接收到请求之后,根据请求方式(post/get/delete ...)的不同调用并执行相应的方法;
第5步:对应方法返回的字符串内容发送到浏览器;

3 路由系统

Tornado中,路由系统其实就是url和类的对应关系,而其他web框架,很多均是url和函数的对应关系。

#!/usr/bin/env python
# -*- coding:utf-8 -*- import tornado.ioloop
import tornado.web class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world") class StoryHandler(tornado.web.RequestHandler):
def get(self, story_id):
self.write("You requested the story " + story_id) class BuyHandler(tornado.web.RequestHandler):
def get(self):
self.write("buy.wupeiqi.com/index") application = tornado.web.Application([
(r"/index", MainHandler),
(r"/story/([0-9]+)", StoryHandler),
]) application.add_handlers('buy.wupeiqi.com$', [
(r'/index',BuyHandler),
]) if __name__ == "__main__":
application.listen(80)
tornado.ioloop.IOLoop.instance().start()

4 模板

Tornado中的模板语言与django类似,模板引擎先将模板文件加载到内存,然后将模板文件与数据进行混合,最终获取到一个完整的字符串,最后将字符串返回给请求者。
   Tornado的模板语言支持"控制语句"和"表达语句",控制语句是使用{%  %}包起来的,例如{% if len(items) > 2 %}。表达语句是使用{{ xx }}包起来的,例如{{ items[0] }}。
   控制语句和对应的Python语句的格式基本完全相同。支持if、for、while和try,这些语句逻辑结束的位置需要用 {% end %} 做标记。还通过extends和block语句来实现模板的继承。这些在template模块的代码文档中有着详细的描述。

tornado程序主文件

#!/usr/bin/env python
# -*- coding:utf-8 -*- import tornado.ioloop
import tornado.web class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render('index.html') settings = {
'template_path': 'template',
'static_path': 'static',
'static_url_prefix': '/static/',
} application = tornado.web.Application([
(r"/index", MainHandler),
], **settings) if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()

layout.html文件(母板)

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test</title>
{% block css%} {% end %}
</head>
<body>
<div><h1>TEST</h1></div>
{% block htmlbody %}{% end %}
<script src="{{static_url('js/jquery-1.8.2.min.js')}}"></script>
{% block JavaScript %}{% end %}
</body>
</html>

index.html文件(子板)

{% extends 'layout.html' %}

{% block css %}
<link href="{{static_url('css/index.css')}}" rel="stylesheet" />
{% end %} {% block htmlbody %}
<h1 id="test_id" class="tim">Tornado模板.</h1>
{% end %} {% block JavaScript %} {% end %}

模板中for循环的语法

{% extends 'layout.html'%}
{% block CSS %}
<link href="{{static_url("css/index.css")}}" rel="stylesheet" />
{% end %} {% block RenderBody %}
<h1>Index</h1> <ul>
{% for item in li %}
<li>{{ item }}</li>
{% end %}
</ul> {% end %} {% block JavaScript %} {% end %}
在模板中默认提供了一些函数、字段、类以供模板使用:
    escape: tornado.escape.xhtml_escape的別名
    xhtml_escape: tornado.escape.xhtml_escape的別名
    url_escape: tornado.escape.url_escape的別名
    json_encode: tornado.escape.json_encode的別名
    squeeze: tornado.escape.squeeze的別名
    linkify: tornado.escape.linkify的別名
    datetime: Python 的 datetime模组
    handler: 当前的 RequestHandler对象
    request: handler.request的別名
    current_user: handler.current_user的別名
    locale: handler.locale的別名
    _: handler.locale.translate的別名
    static_url: for handler.static_url的別名
    xsrf_form_html: handler.xsrf_form_html的別名

Tornado默认提供的这些功能其实本质上就是UIMethod和UIModule,我们也可以自定义从而实现类似于Django的simple_tag的功能:
   (1)定义

# uimethods.py
def tab(self):
return 'UIMethod'
#uimodule.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from tornado.web import UIModule
from tornado import escape class custom(UIModule):
def render(self, *args, **kwargs):
return escape.xhtml_escape('<h1>wupeiqi</h1>')

(2)注册

#!/usr/bin/env python
# -*- coding:utf-8 -*- import tornado.ioloop
import tornado.web
from tornado.escape import linkify
import uimodules as md
import uimethods as mt class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render('index.html') settings = {
'template_path': 'template',
'static_path': 'static',
'static_url_prefix': '/static/',
'ui_methods': mt,
'ui_modules': md,
} application = tornado.web.Application([
(r"/index", MainHandler),
], **settings) if __name__ == "__main__":
application.listen(8009)
tornado.ioloop.IOLoop.instance().start()

(3)使用

<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<link href="{{static_url("commons.css")}}" rel="stylesheet" />
</head>
<body>
<h1>hello</h1>
{% module custom(123) %}
{{ tab() }}
</body>

5 实用功能

(1)静态文件

#!/usr/bin/env python
# -*- coding:utf-8 -*- import tornado.ioloop
import tornado.web class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render('index.html') settings = {
'template_path': 'template',
'static_path': 'static',
'static_url_prefix': '/static/',
} application = tornado.web.Application([
(r"/index", MainHandler),
], **settings) if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()

对于静态文件,可以配置静态文件的目录和前段使用时的前缀,并且Tornaodo还支持静态文件缓存。

<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<link href="{{static_url("commons.css")}}" rel="stylesheet" />
</head>
<body>
<h1>hello</h1>
</body>
</html>

静态文件的配置:

静态文件的配置

<link href="{{static_url('css/index.css')}}" rel="stylesheet" />
    这里的路径写成变量static_url的形式,是方便更改维护静态文件的路径,这点与Django是一致的,

同时这种写法在Tornado中还有另外一个功能,即起到缓存的作用。

缓存作用的解释:

对于静态文件/static/commons.js,如果前端使用static_url配置路径后,则类似于在url的后面加了

一串字符串,即/static/commons.js?v=asdf123,那么当浏览器访问时会携带这个特殊的字符串,如果发现

客户端缓存的静态文件没变,则直接渲染前端页面就行,不必再去服务端重新加载页面,加快了访问的速度。

Tornado实现静态文件缓存的代码

def get_content_version(cls, abspath):
"""Returns a version string for the resource at the given path. This class method may be overridden by subclasses. The
default implementation is a hash of the file's contents. .. versionadded:: 3.1
"""
data = cls.get_content(abspath)
hasher = hashlib.md5()
if isinstance(data, bytes):
hasher.update(data)
else:
for chunk in data:
hasher.update(chunk)
return hasher.hexdigest()

   (2)CSRF

Tornado中的跨站请求伪造和Django中的相似,跨站请求伪造(Cross-site request forgery)。

配置python代码

settings = {
"xsrf_cookies": True,
}
application = tornado.web.Application([
(r"/", MainHandler),
(r"/login", LoginHandler),
], **settings)

form表单提交

<form action="/new_message" method="post">
{{ xsrf_form_html() }}
<input type="text" name="message"/>
<input type="submit" value="Post"/>
</form>

Ajax操作

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 + ")"));
}});
};

注:Ajax使用时,本质上就是去获取本地的cookie,携带cookie再来发送请求

参考资料:

    http://www.cnblogs.com/wupeiqi/articles/5341480.html