Tornado作为web server,提供了web framework的api,可以来直接构建自己的web程序。同时,Tornado支持WSGI ( http://www.python.org/dev/peps/pep-0333/ ),也就是说它可以有能力其它的一些python的框架一起使用,比如django, bottle, flask等。
不妨看下bottle在不同server下的性能评测,其中就有tornado( http://bottlepy.org/page/2009-12-19_Comparing_HelloWorld_Performance )。额外说的是,WSGI的框架是不支持异步的,所以如果有异步调用的逻辑的web程序,Tornado也是选择之一。
注:
其实网络模型、web server、web framework是三个不同层次,它们之间并不冲突,是选择的关系。理解前,不妨先理清概念
tornado应该是最简单的基于epoll(或kqueue)的httpserver和httpclient,说白了就是封装了python标准库的socket和select.epoll(或select.kqueue),所以你也可以照着tornado实现一个媲美nginx的httpserver。
当然,为了利用好这个基于epoll的的server,你需要在io处搞个回调函数,只有当你这个io结束,才执行回调函数,如果你这个io不结束,你就执行了回调函数(同步的写法),产生的后果当然是阻塞(因为一直在等你的io结束),所以用了epoll,你就需要层层回调,代码肯定很难看的,tornado为了解决这个问题,就加了个装饰器以及利用了python的yield关键字,装饰器的内部实现的函数名字就叫replace_callback,顾名思义,就是替换回调,yield会使函数或方法冻结,相当于你搞了个回调,但看起来像同步的,当然,这个写法也有很不方便的地方,但已经比回调爽多了。
Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快。得利于其 非阻塞的方式和对 epoll 的运用,Tornado 每秒可以处理数以千计的连接,这意味着对于实时 Web 服务来说,Tornado 是一个理想的 Web 框架。
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虽然是最简单的基于epoll的httpserver,但坑也非常明显,前面说了,tornado仅仅封装了socket和select.epoll,这就导致,木有python数据库适配器能和tornado无缝对接,只有两种情况下满足其一才能把数据库适配器改造成tornado能用的,第一:这个适配器是纯python的,关键是socket通信是纯python的;第二:这个适配器自带异步,例如psycopg2。可是就算满足了上面的情况,你还得用tornado自带的ioloop和iostream进行封装,相当麻烦。
本质上什么是协程?举个例子:就是类似goto一样 可以在多个fun之间来回跳转~ 比如A函数执行到一半然后switch到B函数执行~诸如此类. 那如果只考虑一个函数呢??那对于A函数而言:其实就是其可以执行到一半先停下,干点别的事情后再接着往下运行~ 看到这里你是不是想到了debug时的打断点??执行到断点的时候就停下,只有next往下setp时候这个函数才接着往下执行~对不对? 那如果我们可以实现类似的断点机制,是不是就可以实现多个函数来回跳转呢?A函数执行到断点时就停下,系统去B执行~等B执行完毕了才回来接着step往断点后边的代码航执行~~~~
协程其实就是这么个东西,那么在Python中山门可以实现debug断点功能呢?当然是yield了~执行到中间的yield系统就停下返回了~之后在调用next时才回接着往下执行~~~ 你看,是不是通过这个东西就可以做到多个函数之间来回跳转??!
恩,那山门时候跳转呢?之前是yield才会,那我是不是可以将之封装一下呢?封装成:一个协程,然后sleep就yield出来~~ 折下来就做到协程的功能来~~
本质上就是这么回事~别听别人说的多悬乎~只是在这个基础模型上又加了很多manager的功能等等~
最重要的一个模块是web, 它就是包含了 Tornado 的大部分主要功能的 Web 框架。其它的模块都是工具性质的, 以便让 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 循环
Tornado 的 Web 程序会将 URL 或者 URL 范式映射到 tornado.web.RequestHandler 的子类上去。在其子类中定义了 get() 或 post() 方法,用以处理不同的 HTTP 请求。
下面的代码将 URL 根目录 / 映射到 MainHandler,还将一个 URL 范式 /story/([0-9]+) 映射到 StoryHandler。正则表达式匹配的分组会作为参数引入 的相应方法中:
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() 方法来获取查询字符串参数,以及解析 POST 的内容:
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 访问到,该对象将名称(HTML元素 的 name 属性)对应到一个文件列表。每一个文件都以字典的形式 存在,其格式为 {“filename”:…, “content_type”:…, “body”:…}。
如果你想要返回一个错误信息给客户端,例如“403 unauthorized”,只需要抛出一个 tornado.web.HTTPError 异常:
if not self.user_is_logged_in():
raise tornado.web.HTTPError(403)
请求处理程序可以通过 self.request 访问到代表当前请求的对象。该 HTTPRequest 对象包含了一些有用的属性,包括:
arguments - 所有的 GET 或 POST 的参数
files - 所有通过 multipart/form-data POST 请求上传的文件
path - 请求的路径( ? 之前的所有内容)
headers - 请求的开头信息
除了 get()/post()等以外,RequestHandler 中的一些别的方法函数,这都是 一些空函数,它们存在的目的是在必要时在子类中重新定义其内容。对于一个请求的处理 的代码调用次序如下:
1.程序为每一个请求创建一个 RequestHandler 对象
2.程序调用 initialize() 函数,这个函数的参数是 Application 配置中的关键字 参数定义。(initialize 方法是 Tornado 1.1 中新添加的,旧版本中你需要 重写 init 以达到同样的目的) initialize 方法一般只是把传入的参数存 到成员变量中,而不会产生一些输出或者调用像 send_error 之类的方法。
3.程序调用 prepare()。无论使用了哪种 HTTP 方法,prepare 都会被调用到,因此 这个方法通常会被定义在一个基类中,然后在子类中重用。prepare可以产生输出 信息。如果它调用了finish(或send_error` 等函数),那么整个处理流程 就此结束。
4.程序调用某个 HTTP 方法:例如 get()、post()、put() 等。如果 URL 的正则表达式模式中有分组匹配,那么相关匹配会作为参数传入方法。
下面是一个示范 initialize() 方法的例子:
class ProfileHandler(RequestHandler):
def initialize(self, database):
self.database = database
def get(self, username):
...
app = Application([
(r'/user/(.*)', ProfileHandler, dict(database=database)),
])