首先,为什么选择Tornado:
1.高性能的网络库,这可以和gevent,twisted,libevent等做对。
提供了异步io支持,超时事件处理,在此基础上提供了tcpserver,httpclient,尤其是curlhttpclient,
在现有http客户端中肯定排第一。可以用来做爬虫,游戏服务器,据我所知业界已有使用tornado作为游戏服务器
2.web框架,这可以和django,flask对。
提供了路由,模板等web框架必备组件。与其他区别是tornado是异步的,天然适合长轮训,
这也是friendfeed发明tornado的原因,当前flask也可以支持,但必须借助gevent等
3.较为完备的http服务器,这点可以和nginx,apache对比,
但只支持http1.0,所以使用nginx做前段不仅是为了更好利用多核,也是让其支持http1.1
4.完备的wsgi服务器,这可以和gunicore,gevent wsgi server做对比,
也就是说可以让flask运行在tornado之上,让tornado加速flask
5.提供了完备的websocket支持,这让html5的游戏等提供了便利。
像知乎长轮训就是使用了websocket,但websocket手机支持的不是很好,
前段时间不得不使用定时ajax发送大量请求,期待手机浏览器赶快奋起直追
使用tornado创建一个简单的图书介绍页
好了,言归正传,下面我们来看一下这个图书介绍页的代码实现:
1.创建一个web服务的入口文件 blockmain.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
#coding:utf-8 import tornado.web import tornado.httpserver import tornado.ioloop import tornado.options import os.path import json import urllib2 from tornado.options import define, options define( "port" , default = 8000 , help = "run on the given port" , type = int ) class MainHandler(tornado.web.RequestHandler): def get( self ): self .render( "index.html" , page_title = "Burt's Books ¦ Home" , header_text = "Welcome to Burt's Books!" , books = [ '细说php' , 'python' , 'PHP' , '小时代' ] ) class HelloModule(tornado.web.UIModule): def render( self ): return '<h1>I am yyx and this is an information from module hello!</h1>' class BookModule(tornado.web.UIModule): def render( self ,bookname): doubanapi = r 'https://api.douban.com/v2/book/' searchapi = r 'https://api.douban.com/v2/book/search?q=' searchurl = searchapi + bookname searchresult = urllib2.urlopen(searchurl).read() bookid = json.loads(searchresult)[ 'books' ][ 0 ][ 'id' ] bookurl = doubanapi + bookid injson = urllib2.urlopen(bookurl).read() bookinfo = json.loads(injson) return self .render_string( 'modules/book.html' ,book = bookinfo) def embedded_javascript( self ): return "document.write(\"hi!\")" def embedded_css( self ): return '''.book {background-color:#F5F5F5} .book_body{color:red} ''' def html_body( self ): return '<script>document.write("Hello!")</script>' if __name__ = = "__main__" : tornado.options.parse_command_line() app = tornado.web.Application( handlers = [ (r '/' ,MainHandler), ], template_path = os.path.join(os.path.dirname(__file__), 'templates' ), static_path = os.path.join(os.path.dirname(__file__), 'static' ), debug = True , ui_modules = { 'Hello' :HelloModule, 'Book' :BookModule} ) http_server = tornado.httpserver.HTTPServer(app) http_server.listen(options.port) tornado.ioloop.IOLoop.instance().start() |
说明一下,一些基本的MVC概念:
tornado也是通过pathinfo模式来匹配用户的输入来获得参数,然后再调用相应的处理函数,它是通过为各种匹配模式设定相应的class类来处理,比如我这里就是通过class MainHandler来处理来自/的get请求
MainHandler把请求render渲染到index.html,参数在index.html中通过{{参数}}来调用
2.建立相应的模板,先创建一个基础的父类main.html模板,创建templates目录,在它下面创建main.html,这个模板只是定义了最基础的网页框架,里面的具体内容由继承于它的子类来具体实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
< html > < head > < title >{{ page_title }}</ title > < link rel = "stylesheet" href = "{{ static_url(" css/style.css") }}" /> </ head > < body > < div id = "container" > < header > {% block header %}< h1 >Burt's Books</ h1 >{% end %} </ header > < div id = "main" > < div id = "content" > {% block body %}{% end %} </ div > </ div > < footer > {% set mailLink = '< a href = "mailto:contact@burtsbooks.com" >Contact Us</ a >' %} {% set script = '< script >alert("hello")</ script >' %} {% block footer %} < p > For more information about our selection, hours or events, please email us at{% raw mailLink %} <!-- {% raw script %} 这里将原样输出,也就是会弹一个框--> </ p > {% end %} </ footer > </ div > < script src = "{{ static_url(" js/script.js") }}"></ script > </ body > </ html > |
这里是定义了一个主框架,其中里面的{% block header %}<h1>Burt's Books</h1>{% end %}是为了子类模板的继承的块(block),当子类继承了这个main.html,具体这个块里写什么内容由子类来实现,不实现的话就使用父类的默认 值,如是这里的<h1>Burt's Books</h1>,MainHandler类是render到一个index.html,那么接下来写一个index.html来继承这 个父类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
{ % extends "main.html" % } { % block header % } <h1>{{ header_text }}< / h1> { % end % } { % block body % } <div id = "hello" > <p>Welcome to Burt's Books!< / p> { % module Hello() % } { % for book in books % } { % module Book(book) % } { % end % } <p>...< / p> < / div> { % end % } |
简单简洁吧,这也是使用了继承的好处,不用再重复写父类的东西,只要实现父类的block内容即可
MainHandler类里的render方法中的参数
1
2
3
|
page_title = "Burt's Books | Home" , header_text = "Welcome to Burt's Books!" , books = [ '细说php' , 'python' , 'PHP' , '小时代' ] |
将会通过参数传送到这里来
tornado的模板里可以使用python的代码,加上{% %}当使用if for while等要使用{% end %}结尾
代码中{% module Book(book) %} 将会调用入口服务文件中的定义和'Book'所对应的模块
ui_modules={'Hello':HelloModule,'Book':BookModule} 也就是BookModule,查看上面的BookModule定义
1
2
3
4
5
6
7
8
9
10
11
|
class BookModule(tornado.web.UIModule): def render( self ,bookname): doubanapi = r 'https://api.douban.com/v2/book/' searchapi = r 'https://api.douban.com/v2/book/search?q=' searchurl = searchapi + bookname searchresult = urllib2.urlopen(searchurl).read() bookid = json.loads(searchresult)[ 'books' ][ 0 ][ 'id' ] bookurl = doubanapi + bookid injson = urllib2.urlopen(bookurl).read() bookinfo = json.loads(injson) return self .render_string( 'modules/book.html' ,book = bookinfo) |
BookModule 继承自tornado.web.UIModule,UI模块的使用是最后render_string()方法来把一个对象渲染到一个模板中去,我这里简单 的使用了豆瓣的图书api,先通过search来查询一下包含关键词的图书信息,返回第一条图书的id,再使用book api来查询该图书的具体信息,将这个具体图书的信息render到对应的模板
在templates 目录下创建modules目录,再下创建一个book.html,这里是具体的book要显示的内容框架
1
2
3
4
5
6
7
8
9
10
11
12
13
|
< div class = "book" > < h3 class = "book_title" >{{ book["title"] }}</ h3 > < a href = "{{book['alt']}}" target = "_blank" >< p >点击查看详情</ p ></ a > {% if book["subtitle"] != "" %} < h4 class = "book_subtitle" >{{ book["subtitle"] }}</ h4 > {% end %} < img src = "{{ book[" images"]["large"] }}" class = "book_image" /> < div class = "book_details" > < div class = "book_date_released" >Released: {{ book["pubdate"]}}</ div > < h5 >Description:</ h5 > < div class = "book_body" >{% raw book["summary"] %}</ div > </ div > </ div > |
最后的文件目录结构应该是这样的
1
2
3
4
5
6
|
├── blockmain.py └── templates ├── index.html ├── main.html └── modules └── book.html |
程序的执行是这样的:
先通过路径‘/'来使用MainHandler类访问index.html---->index.html继承自 main.html---->index.html中的{% module Book(book) %}反过来查找blockmain.py中的Book对应的ui_modules---->ui_modules中将查询得到的book对象内容渲 染到modules下的book.html中,这样就把完整的内容呈现出来了,没有做前端…… 通过python blockmain.py启动服务,通过http://localhost:8000 来访问得到如下的网页