高并发处理框架—Tornado

Tornado介绍

​ Tornado是一个可扩展的非阻塞式Web服务器及其相关工具的开源版本。Tornado每秒可以处理数以千计的;连接,对于实时的Web服务来说,Tornado是一个理想的Web框架。

​ Tornado是使用Python编写的一个强大的可扩展的Web服务器,它在处理高网络流量时表现得足够强健,却在创建和编写时有着足够的轻量级,并且能够被用在大量的应用和工具中。相比于其他的Python网络框架,Tornado有如下特点。

  • 完备的Web框架:与Django,Flask等一样,Tornado也提供了URL路由映射、Request上下文、基于模板的页面渲染技术等开发Web应用的必备工具。

  • 是一个高效的网络库,性能与Twisted、Gevent等底层Python框架相媲美:提供了异步I/O支持、超时事件处理。这使得Tornado除了可以作为Web应用服务器框架,还可以用来做爬虫应用、物联网关、游戏服务器等后台应用。

  • 提供高效的HTTPClient:除了服务器端框架,Tornado还提供了基于异步框架的HTTP客户端。

  • 提供高效的内部HTTP服务器:虽然其他Python网络框架(Django,Flask)也提供了内部HTTP服务器,但它们的HTTP服务器由于性能原因只能用于测试环境。而Tornado的HTTP服务器与Tornado异步调用紧密结合,可以直接用于生产环境。

  • 完备的WebSocket支持:WebSocket是HTML5的一种新标准,实现了浏览器与服务器之间的双向实时通信。

    因为Tornado的上述特点,Tornado常被用作大型站点的接口服务框架,而不像Django那样着眼于建立完整的大型网站,着重讲解Tornado的异步及协程编程、身份认证框架、独特的非WSGI部署方式。

安装Tornado

​ Tornado已经被配置到PyPI网站中,使得Tornado的安装非常简单。在Windows和Linux中都可以通过一条pip命令来完成安装。

pip install tornado

​ 该条命令可以运行在操作系统中或python虚环境中。安装信息如下图所示:

异步及协程基础

​ 协程是Tornado中推荐的编程方式,使用协程可以开发出简捷,高效的异步处理代码。本章从同步I/O、异步I/O开始,逐步理解和掌握基于Tornado协程的编程技术。

同步和异步I/O

​ 从计算机硬件发展的角度来看,当今计算机系统的CPU和内存发展速度日新月异,摩尔定律体现的十分明显。与之同时的硬盘、网络等于I/O相关的速度指标却进度缓慢。因此,在如今的计算机应用开发中,减少程序在I/O操作中的等待是对资源消耗、提高并发程度的必要考虑。

​ 根据Unix Network Programing 一书中的定义,同步I/O操作(synchronous I/O operation)导致请求进程阻塞,直到I/O操作完成;异步I/O操作(asynchronous I/O operation)不导致请求进程阻塞。在Python中,同步I/O可以被理解为一个被调用的I/O函数会阻塞调用函数的执行,而异步I/O则不会阻塞调用函数的执行。代码举例如下:

1
2
3
4
5
6
from tornado.httpclient import HTTPClient			#Tornado的HTTP客户端

def synchronous_visit():
    http_client = HTTPClient()
	response = http_client.fetch("www.baidu.com")	#阻塞,直到对www.baidu.com访问完成
    print(response.body)

​ HTTPClient是Tornado的同步访问HTTP客户端。上述代码中的synchronous_visit()函数使用了典型的同步I/O操作访问www.baidu.com网站,该函数的执行时间取决于网络速度、对方服务器响应速度等,只有等到对www.baidu.com的访问完成后并获取到结果后,才能完成对synchronous_visit函数的执行。

​ 而使用异步I/O访问www.baidu.com网站的函数无需等待访问完成才能返回,譬如:

1
2
3
4
5
6
7
8
from tornado.httpclient import AsyncHTTPClient

def handle_response(response):
    print(response.body)
    
def asynchronous_visit():
    http_client = AsyncHTTPClient()
    http_client.fetch("www.baidu.com",callback=handle_response)

​ AsyncHTTPClient是Tornado的异步访问HTTP客户端。在上述代码的asynchronous_visit函数中使用AsyncHTTPClient对第三方网站进行异步访问,http_client.fetch()函数会在调用后立刻返回而无需等待实际返回的完成,从而导致asynchronous_visit()也会立刻执行完成。当对www.baidu.com的访问实际完成后,AsyncHTTPClient会调用callback参数指定的函数,开发者可以在其中写入处理访问结果的逻辑代码。

yield关键字

​ 协程是Tornado中进行异步I/O代码开发的方法,协程使用了Python的关键字yield将调用者挂起和恢复执行。在理解协程概念前,应首先理解Python中yield关键字的概念和使用方法,而学习yield之前需要了解迭代器的概念。

1. 迭代器

​ 迭代器(Iterator)是访问集合内元素的一种方式。迭代器对象从集合的第1个元素开始访问,直到所有元素都被访问一遍后结束。迭代器不能回退,只能往前进行迭代。

​ Python中最常使用迭代器的场景是循环语句for,它用迭代器封装集合,并且逐个访问集合元素以执行循环体。比如:

1
2
for number in range(6):		#range返回一个列表
    print(number)

​ 其中的range()返回一个包含所指定元素的集合,而for语句将其封装一个迭代器后访问,使用iter()调用可以将列表、集合转换为迭代器,比如:

1
2
3
4
5
6
7
8
wjq@Ubuntu:~$ python
Python 2.7.12 (default, Jul 18 2016, 15:02:52) 
[GCC 4.8.4] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> numbers = [1,3,5,7,8]
>>> t = iter(numbers)
>>> print(t)
<listiterator object at 0x7fd46be79050>

​ 其中,t就是迭代器。迭代器与普通的Python对象的区别是迭代器有一个next()方法,每次调用该方法可以返回一个元素。调用者可以通过不断调用next()方法来逐个访问集合元素。比如:

1
2
3
4
5
6
7
8
9
>>> iter = iter(range(6))
>>> print(iter.next())
0
>>> print(iter.next())
1
>>> print(iter.next())
2
>>> print(iter.next())
...

​ 调用者可以一直这样调用next()方法来访问迭代器,直到next()方法返回StopIteration异常以表示迭代已经完成,比如:

1
2
3
4
5
6
7
8
9
>>> print(iter.next())
4
>>> print(iter.next())
5
>>> print(iter.next())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> 

2. 使用yield

​ 迭代器在Python编程中适用范围很广,开发者可以使用yield定制自己的迭代器。调用任何定义中包含yield关键字的函数都不会执行该函数,而是会获得一个对应该函数的迭代器。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
code example:
#!/usr/bin/env python
#-*- coding:utf-8 -*-

def demoIterator():     #定义一个迭代器函数
    print("first call of next()")
    yield 1
    print("second call of next()")
    yield 3
    print("third call of next()")
    yield 9

for i in demoIterator():
    print(i)

​ 执行该部分代码结果如下:

1
2
3
4
5
6
7
ssh://wjq@59.68.29.xx:22/home/wjq/TornadoWork/tor_env/bin/python -u /home/wjq/TornadoWork/tornado_0/temp/demo/7_1.py
first call of next()
1
second call of next()
3
third call of next()
9

​ 每次调用迭代器的next()函数,将执行迭代器函数。并返回yield的结果作为迭代返回元素。当迭代器函数return时,迭代器会抛出StopIteration异常使迭代终止。

​ 注:在Python中,使用yield关键字定义的迭代器也被称为"生成器"。

协程

​ 使用Tornado协程可以开发出类似同步代码的异步行为,并且因为协程本身不使用线程,所以减少了线程上下文切换的开销,是一种更为高效的开发模式。

1. 编写协程函数

​ 使用协程技术开发网页访问的代码如下:

1
2
3
4
5
6
7
8
from tornado import gen			#引入协程库gen
from tornado.httpclient import AsyncHTTPClient

@gen.coroutine					#使用gen.coroutine修饰器
def coroutine_visit():
    http_client = AsyncHTTPClient()
    response = yield http_client.fetch("www.baidu.com")
    print(response.body)

​ 本例中仍然使用异步客户端AsyncHTTPClient进行页面访问,使用@gen.coroutine表明用装饰器声明这是一个协程函数。由于yield关键字的使用,使得代码中不用再编写回调函数用于处理访问结果,而可以直接在yield语句的后面编写结果处理语句。

2. 编写协程函数

由于Tornado协程基于Python的yield关键字实现,所以不能像调用普通函数一样调用协程函数。 协程函数可以通过以下三种方式进行调用。

  • 在本身是协程的函数内通过yield关键字调用。

  • 在IOLoop尚未启动时,通过IOLoop的run_sync()函数调用。

  • 在IOLoop已经启动时,通过IOLoop的spawn_callback()函数调用。

    举一个“通过协程函数调用协程函数”的例子:

1
2
3
4
5
6
7
from tornado import gen				# 引入协程库gen

@gen.coroutine
def outer_coroutine():
    print("start call another coroutine")
    yield coroutine_visit()
    print("end of outer_coroutine")

​ 本例中coroutine_visit和outer_coroutine都是协程函数,所以它们之间可以通过yield关键字进行调用。IOLoop是Tornado的主事件循环对象,Tornado程序通过它监听外部客户端的访问请求,并执行相应的操作。当程序尚未进入IOLoop的running状态时,可以通过run_sync()函数调用协程函数。比如:

1
2
3
4
5
6
from tornado.ioloop import IOLoop		#引入IOLoop对象

def func_normal():
    print("start to call a coroutine")
	IOLoop.current().run_sync(lambda: coroutine_visit())
    print("end of calling a coroutine")

​ 此处无需过分了解IOLoop,后面会逐步了解IOLoop的具体概念及应用方法。

​ 上例中引用tornado.ioloop包中的IOLoop对象,之后在普通函数中使用run_sync()函数调用经过lambda封装的协程函数。run_sync()函数将阻塞当前函数的执行,直到被调用的协程执行完成。

​ 事实上,Tornado要求协程函数在IOLoop的runing状态中才能被调用,只不过run_sync函数自动完成了启动、停止IOLoop的步骤,它的实现逻辑为:启动IOLoop—>调用被lambda封装的协程函数—>停止IOLoop。当Tornado程序已经处于running状态时的协程函数的调用示例如下:

1
2
3
4
5
6
from tornado.ioloop import IOLoop		#引入IOLoop对象

def func_normal():
    print("start to call a coroutine")
    IOLoop.current().spawn_callback(coroutine_visit)
    print("end of calling a coroutine")

​ 本例中spawn_callback()函数将不会等待被调用协程执行完成。所以spawn_callback()之前和之后的print语句将会被连续执行,而coroutine_visit本身将会由IOLoop在合适的时机进行调用。

​ IOLoop的spawn_callback()函数没有为开发者提供获取协程函数调用返回值的方法,所以只能用spawn_callback()调用没有返回值的协程函数。

3. 在协程中调用阻塞函数

​ 在协程中直接调用阻塞函数会影响协程本身的性能,所以Tornado提供了在协程中利用线程池调度阻塞函数,从而不影响协程本身继续执行的方法。示例如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from concurrent.futures import ThreadPoolExecutor

thread_tool = ThreadPoolExecutor(2)
def mySleep(count):
    import time
    for i in range(count):
        time.sleep(1)

@gen.coroutine
def call_blocking():
    print("start of call_blocking")
    yield thread_pool.submit(mySleep,10)
    print("end of call_blocking")

​ 代码中首先引用了concurrent.futures中的ThreadPoolExecutor类,并实例化了一个有两个线程的线程池thread_pool。在需要调用阻塞函数的协程call_blocking中,使用thread_pool.submit调用阻塞函数,并通过yield返回。这样便不会阻塞协程所在线程的继续执行,也保证了阻塞函数前后代码的执行顺序。

4. 在协程中等待多个异步调用

​ 目前只讲述了协程中一个yield关键字等待一个异步调用,实际上tornado允许在协程中用一个yield关键字等待多个异步调用,只需要把这些调用用列表(list)或字典(dictionary)的方式传递给yield关键字即可。

​ 使用列表方式传递多个异步调用的示例代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from tornado import gen		# 引入协程库
from tornado.httpclient import AsyncHTTPClient

@gen.coroutine				# 使用gen.coroutine修饰器
def coroutine_visit():
    http_client = AsyncHTTPClient()
    list_response = yield[ http_client.fetch("www.baidu.com"),
                         http_client.fetch("www.sina.com"),
                        http_client.fetch("www.163.com"),
                        http_client.fetch("www.google.com")
                         ]
   for response in list_response:
       print response.body

​ 在代码中仍然用@gen.coroutine装饰器定义协程,在需要yield的地方用列表传递若干个异步调用,只有在列表中的所有调用都执行完成后,yield才会返回并继续执行。yield以列表形式返回N个调用的输出结果,可以通过for语句逐个访问。

​ 使用字典方式传递多个异步调用的示例代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from tornado import gen		# 引入协程库
from tornado.httpclient import AsyncHTTPClient

@gen.coroutine				# 使用gen.coroutine修饰器
def coroutine_visit():
    http_client = AsyncHTTPClient()
    dict_response = yield[ "baidu": http_client.fetch("www.baidu.com"),
                         "sina": http_client.fetch("www.sina.com"),
                        "163": http_client.fetch("www.163.com"),
                        "google": http_client.fetch("www.google.com")
                         ]
 print dict_response["sina"].body

​ 本例中以字典形式给yield关键字传递异步调用要求,并且Tornado以字典形式返回异步调用的结果。

Tornado框架知识

Tornado概述

​ Tornado全称是Tornado Web Server,是一个用Python语言写成的Web服务器兼网络框架。特点和性能总结如下:

特点:

  • 轻量级的Web框架,其拥有异步非阻塞IO的处理方式。

  • 作为Web服务器,Tornado有较为出色的抗负载能力,官方用Nginx反向代理的方式部署Tornado和其它Python Web应用框架,为了最大化的利用Tornado的性能,推荐同时使用tornado和其它Python Web应用框架进行对比,结果最大浏览量超过第二名近40%.

    性能:

  • Tornado性能优异,它试图解决 C10K问题,即处理大于或等于一万的并发连接。

    Tornado框架和服务器一起可以组成一个WSGI的全栈替代品,如果单独在WSGI容器中使用tornado网络框架或者tornaod http服务器 ,存在一定的局限性。为了最大化的利用tornado的性能,推荐同时使用tornado+HTTP服务器。

应用场景

  • 用户量大,高并发

    如秒杀抢购,双11某宝购物、春节抢火车票等。

  • 大量的HTTP持久连接

​ 使用同一个TCP连接来发送和接收多个HTTP请求 or 应答,而不是为每一个请求 or 应答打开新的连接的方法。

​ 对于HTTP 1.0,可以在请求的包头(Header)中添加Connection:Keep-Alive。

​ 对于HTTP 1.1,所有的连接默认为持久连接。

Tornado与Django比较

Tornado

​ Tornado走的是少而精的方向,注重的是性能优越,最为出名的是它的异步非阻塞的设计方式。

  • HTTP服务器

  • 异步编程

  • WebSocket

    Djangoo

    Django走的是大而全的方向,注重开发高效,最为出名的是它自动化的管理后台,只需要使用起ORM,做简单的对象定义,它就能自动生成数据库,以及功能齐全的管理后台。

    Django所提供的方便,意味着Django内置的ORM跟框架内的其他模块耦合度高,应用程序必须使用Django内置的ORM,否则就不能享受到框架内提供的种种基于ORM的便利。

  • Session功能

  • 后台管理

  • ORM

简单Tornado示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#-*- coding:utf-8 -*-
import tornado.httpserver
import tornado.ioloop
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):
        '''对应http的get请求方式'''
        greeting = self.get_argument('greeting','Hello')
        self.write(greeting + ', tornado!')


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()

上述代码解释说明:

tornado的基础Web框架模块

  • RequestHandler

    封装了对应一个请求的所有信息和方法,write(响应信息)就是写响应信息的一个方法;对应每一种http请求方式(get、post等),把对应的处理逻辑写进同名的成员方法中(如对应get请求方式,就将对应的处理逻辑写在get()方法中),当没有对应请求方式的成员方法时,会返回“405: Method Not Allowed”错误。

  • Application

    Tornado Web框架的核心应用类,是与服务器对接的接口,里面保存了路由信息表,其初始化接收的第一个参数就是一个路由信息映射元组的列表;其listen(端口)方法用来创建一个http服务器实例,并绑定到给定端口(注意:此时服务器并未开启监听)。

Tornado核心IOLoop循环模块

​ tornado的核心io循环模块,封装了Linux的epoll和BSD的kqueue,也是tornado高性能的基石。以Linux的epoll为例,其原理如下图:

WwGci6.png

​ 针对具体实例,访问路径如下图:

WwGhsH.png

  • IOLoop.current()返回当前线程的IOLoop实例。
  • IOLoop.start() 启动IOLoop实例的I/O循环,同时服务器监听被打开。

2.6 Tornado Web程序编写思路

  1. 创建Web应用实例对象,第一个初始化参数为路由映射列表。
  2. 定义实现路由映射表中的handler类。
  3. 创建服务器实例,绑定服务器端口。
  4. 启动当前线程的IOLoop。

开发Tornado网站

​ 下面会展示使用Tornado建立Web站点的方法。

网站结构

​ 通过编写hellowowld学习Tornado网站的基本结构:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello World!")

def make_app():
    return tornado.web.Application([(r"/",MainHandler),])

def main():
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()
    
if __name__ == "__main__":
    main()

​ 下面逐行解析上面的代码做了些什么。

​ (1) 首先是通过import语句引入tornado包中ioloop和web类。引入这两个类是Tornado程序的基础。

​ (2) 实现一个web.RequestHandler子类,重载其中的get()函数,该函数负责相应定位到该Request

Handler的HTTP GET请求的处理。本例中简单的使用self.write()函数输出hello world。

​ (3) 定义了make_app()函数,该函数返回一个web.Application对象。该对象的第1个参数用于定义Tornado程序的路由映射。本例中将对根URL的访问映射到了RequestHandler子类MainHandler中。

​ (4) 用web.Application.listen()函数指定服务器监听的端口。

​ (5) 用tornado.ioloop.IOLoop.current().start()启动IOLoop,该函数将一直运行且不退出,用于处理完所有的客户端的访问请求。

路由解析

​ 向web.Application对象传递的第1个参数URL路由映射列表的配置方式与Django类似,用正则字符串进行路由匹配。Tornado的路由字符串有两种,固定字符路径和参数字串路径。

  • 固定字串路径

    固定子串是普通的字符串固定匹配,比如:

1
2
3
4
Handlers = [("/",MainHandler),					#只匹配根路径
            ("/entry",EntryHandler),			#只匹配/entry
            ("/entry/2015",Entry2015Handler)	#只匹配/entry/2015
           ]
  • 参数字串路径

​ 参数字串可以将具备一定模式的路径映射到同一个RequestHandler中处理,其中路径中的参数部分用小括号"()“标识,下面是一个参数路径的例子:

1
2
3
4
5
6
7
8
9
# url handler
handlers = [(r"/entry/([^/]+)",EntryHandler),]

class EntryHandler(tornado.web.RequestHandler):
    def get(self,slug):
        entry = self.db.get("SELECT * FROM entries WHERE slug = %s",slug)
        if not entry:
            raise tornado.web.HTTPError(404)
        self.render("entry.html",entry=entry)

​ 例中用 "/entry/([^/]+)" 定义以/entry/开头的URL模式。小括号的内容是正则表达式,URL尾部的变量部分以参数形式传递给RequestHandler中的get()函数,本例中将该参数命名为slug。

  • 带默认值的参数路径

​ 之前例子中的handlers = [(r"/entry/([^/]+)",EntryHandler),]模式定义了客户端必须输入路径参数。比如,其能够匹配如下路径:

1
2
http://xx.xx.xx.xx/entry/abc
http://xx.xx.xx.xx/entry/2019-03-10

​ 但是其无法匹配,http://xx.xx.xx.xx/entry

​ 对于需要匹配客户端未传入时的路径,则需要用如下方法改变URL路径和对get()函数的定义:

1
2
3
4
5
6
7
8
9
# url handler
handlers = [(r"/entry/([^/]*)",EntryHandler),]

class EntryHandler(tornado.web.RequestHandler):
    def get(self,slug='default'):
        entry = self.db.get("SELECT * FROM entries WHERE slug = %s",slug)
        if not entry:
            raise tornado.web.HTTPError(404)
        self.render("entry.html",entry=entry)

​ 本例中首先用星号”*“取代加号”+“定义了URL模式,然后为RequestHandler子类的get()函数的slug参数配置了默认值default。

  • 多参数路径

    参数路径还允许在一个URL模式中定义多个可变参数,比如:

1
2
3
4
5
6
7
handlers = [
    (r'/(\d{4})/(\d{2})/(\d{2})/([a-zA-Z\-0-9\.:,_]+)/?',DetailHandler)
]

class DetailHandler(tornado.web.RequestHandler):
    def get(self, year, month, day, slug):
        self.write("%d-%d-%d %s"%(year, month, day, slug))

​ 本例中的URL模式定义了year、month、day、slug等4个参数。

RequestHandler

​ 经过前面的学习,大致了解到RequestHandler类在Tornado网站程序中的重要作用,它是配置和响应URL请求的核心类,下面将介绍RequestHandler的更多内容。

  • 接入点函数

​ 需要子类继承并定义具体行为的函数在RequestHandler中被称为接入点函数(Entry Point),之前常用的get()函数就是典型的接入点函数。其它可用的接入点函数如下所述。

​ (1)RequestHandler.initialize()

​ 该方法被子类重写,实现了RequestHandler子类实例的初始化过程。可以将该函数传递参数,参数来源于配置URL映射时的定义。比如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from tornado.web import RequestHandler
from tornado.web import Application

class ProfileHandler(RequestHandler):
    def initialize(self,database):
        self.database = database
    
    def get(self):
        pass
    
    def post(self):
        pass

app = Application([(r'/account',ProfileHandler,dict(database="c:\\example.db")),])

​ 本例中的initialize有参数database,该参数由Application定义URL映射时以dict形式给出。

​ (2)RequestHandler.prepare()、RequestHandler.on_finish()

​ prepare()方法用于调用请求处理(get、post等)方法之前的初始化处理,而on_finish()用于请求处理结束后的一些清理工作,这两种方法一个在处理前,一个在处理后,可以根据实际需要进行重写。通常prepare()方法做资源初始化操作,而用on_finish()方法可做清理对象占用的内存或者关闭数据库连接等工作。

​ (3)HTTP Action处理函数

​ 每个HTTP Action在RequestHandler中都以单独的函数进行处理。

  • RequestHandler.get(*args,**kargs)

  • RequestHandler.head(*args,**kargs)

    RequestHandler.post(*args,**kargs)

  • RequestHandler.delete(*args,**kargs)

  • RequestHandler.patch(*args,**kargs)

  • RequestHandler.put(*args,**kargs)

    RequestHandler.options(*args,**kargs)

    每个处理函数都以它们对应的HTTP Action小写的方式命名。

    输入捕获

​ 输入捕获是指在RequestHandler中获取客户端输入的工具函数和属性,比如获取URL查询字符串、POST提交参数等。

​ (1)RequestHandler.get_argument(name)、RequestHandler.get_arguments(name)

​ 都是返回给定参数的值。get_argument获得单个值;而get_arguments是针对参数存在多个值的情况下使用的,返回多个值的列表。用get_argument/get_arguments()方法获取的是URL查询字符串参数与Post提交参数的参数合集。

​ (2) RequestHandler.get_query_argument(name)、RequestHandler.get_query_argument

(name)

​ 它们与get_argument、get_arguments的功能类似,但是仅从URL查询参数中获取参数值。

​ (3)RequestHandler.get_body_argument(name)、RequestHandler.get_body_arguments

(name)

​ 与get_argument、get_arguments的功能类似,但是仅从Post提交参数中获取参数值。

​ 一般来说,使用get_argument/get_arguments即可。因为它们是get_query_argument/get_query_arguments和get_body_argument/get_body_arguments的合集。

​ (4)RequestHandler.get_cookie(name,default=None)

​ 根据Cookie名称获取Cookie值。

​ (5)RequestHandler.request

​ 返回tornado.httputil.HTTPServerRequest对象实例的属性,通过该对象可以获取关于HTTP请求的一切信息。比如:

1
2
3
4
5
6
import tornado.web

class DetailHandler(tornado.web.RequestHandler):
    def get():
        remote_ip = self.request.remote_ip				#获取客户端IP地址
        host = self.request.host						#获取请求的主机地址

​ 常用的httputil.HTTPServerRequest对象属性如下表所示:

属性名 说明
method HTTP请求方法,比如GET、POST等
url 客户端请求的url的完整内容
query url中的查询字符串
version 客户端发送请求时使用的HTTP版本,比如HTTP/1.1
headers 以字典方式表达的HTTP Headers
body 以字符串方式表达的HTTP消息体
remote_ip 客户端的IP地址
Protocol 请求协议,比如HTTP、HTTPS
host 请求消息中的主机名
arguments 客户端提交的所有参数
files 以字典方式表达的客户端上传的文件,每个文件名对应一个HTTPFile
cookies 客户端提交的cookie字典
  • 输出响应函数

​ 输出响应函数是指一组为客户端生成处理结果的工具函数,开发者调用它们以控制URL的处理结果。常用的输出相应函数如下。

​ (1) RequestHandler.set_status(status_code,reason=None)

​ 设置HTTP Resource中的返回码,如果有描述性的语句,则可以赋值给reason参数。

​ (2)RequestHandler.set_header(name,value)

​ 以键值对的方式配置HTTP Response中的HTTP头参数。使用set_header配置的Header值将覆盖之前配置的Header,比如:

1
2
3
4
5
6
import tornado.web
class DetailHandler(tornado.web.RequestHandler):
    def get():
        self.set_header("NUMBER",9)
        self.set_header("LANGUAGE","France")
        self.set_header("LANGUAGE","Chinese")
本例中的get()函数调用了3次set_header,但是只配置了两个header参数,最后的HTTP Header中的参数将会是:
1
2
NUMBER: 9
LANGUAGE: Chinese 

​ (3)RequestHandler.add_header(name,value)

​ 以键值对的方式设置HTTP Response中的HTTP头参数。与set_header不同的是add_header配置的Header值将不会覆盖之前配置的Header,比如:

1
2
3
4
5
6
import tornado.web
class DetailHandler(tornado.web.RequestHandler):
    def get():
        self.set_header("NUMBER",8)
        self.set_header("LANGUAGE","France")
        self.set_header("LANGUAGE","Chinese")

​ 最后HTTP Header中的参数将会是:

1
2
3
NUMBER: 8
LANGUAGE: France   
LANGUAGE: Chinese 

​ (4)RequestHandler.write(chunk)

​ 将给定的块作为HTTP Body发送给客户端。在一般情况下,用本函数输出字符串给客户端,如果给定的块是一个字典,则会将这个块以JSON格式发送给客户端,同时将HTTP header中的Content_type设置成application/json。

​ (5)RequestHandler.finish(chunk=None)

​ 本方法通知Tornado:Response的生成工作已完成,chunk参数是需要传递给客户端的HTTP body。调用finish()后,Tornado将向客户端发送HTTP Response。本方法适用于对RequestHandler的异步请求处理,异步请求的具体方法详见3.4节。

注意:在同步或协程访问处理的函数中,无需调用finish()函数。

​ (6)RequestHandler.render(template_name, **kwargs)

​ 用给定的参数渲染模板,可以在本函数中传入模板文件名称和模板参数,比如:

1
2
3
4
5
import tornado.web
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        items = ["Python","C++","Java"]
        self.render("template.html",title="Tornado Template",items=items)

​ render()的第1个参数是对模板文件的命名,之后以命名参数的形式传入多个模板参数。Tornado的基本模板语法和Django相同,功能上弱化,高级过滤器不可用。

​ (7) RequestHandler.redirect(url,permanent=False,status=None)

​ 进行页面重定向。在RequestHandler处理过程中,可以随时调用redirect()函数进行页面重定向,比如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import tornado.web

class LoginHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("login.html",next=self.get_argument("next","/"))
    
    def post(self):
        username = self.get("username","")
        password = self.get("password","")
        auth = self.db.authenticate(username, password)
        if auth:
            self.set_current_user(username)
            self.redirect(self.get_argument("next",u"/"))
        else:
            error_msg = u"?error=" + tornado.escape.url_escape("Login incorrect.")
            self.redirect(u"/login" + error_msg)

​ 在本例LoginHandler的post处理函数中,根据验证是否成功将客户端重定向到不同的页面,如果成功则重定向到next参数所指向的URL;如果不成功,则重定向到”/login"页面。

​ (8) RequestHandler.clear()

​ 清空所有在本次请求中之前写入的Header和Body内容,比如:

1
2
3
4
5
6
import tornado.web
class DetailHandler(tornado.web.RequestHandler):
    def get():
        self.set_header("NUMBER",8)
        self.clear()
        self.set_header("LANGUAGE","France")

​ (9) RequestHandler.set_cookie(name,value)

​ 按键值对设置Response中的Cookie值。

​ (10) RequestHandler.clear_all_cookies

​ 清空本次请求中的所有Cookie。

异步化及协程化

​ 上述例子都是用同步的方法来处理用户的请求,即在RequestHandler的get()和post()函数中完成所有的处理,当退出get()和post()等函数后马上向客户端返回Response。但是处理逻辑比较复杂或需要等待外部I/O时,这样的处理机制会阻塞服务器线程,并不适合大量客户端高并发的应用场景。

​ Tornado有两种方式改变同步的处理流程。

  • 异步化:针对RequestHandler的处理函数,使用@tornado.web.asynchronous修饰器,将默认的同步机制改为异步机制。
  • 协程化:针对RequestHandler的处理函数,使用@tornado.gen.coroutine修饰器,将默认的同步机制改为协程机制。
  1. 异步化

    异步化的RequestHandler处理如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import tornado.web
import tornado.httpclient

class MainHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        http = tornado.httpclient.AsyncHTTPClient()
        http.fetch("http://www.baidu.com",callback=self.on_respinse())
        
    def on_resoponse(self,response):
        if response.error:raise tornado.web.HTTPError(500)
        self.write(response.body)
        self.finish()

​ 本例中用装饰器tornado.web.asynchronous定义了HTTP访问处理函数get()。这样,当get()函数返回时,对该HTTP访问的请求尚未完成,所以Tornado无法发送HTTP Response给客户端。只有当在随后的on_response()中的finish()函数被调用时,Tornado才知道本次处理已完成,可以发送给Response给客户端。

​ 异步编程虽然提高了服务器的并发能力,但编程方法繁琐。

2.协程化

​ 协程的编程方法示例如下:

1
2
3
4
5
6
7
8
import tornado.web
import tornado.httpclient
class MainHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get():
        http = tornado.httpclient.AsyncHTTPClient()
        response = yield http.fetch("http://www.baidu.com")
        self.write(response.body)

​ 本例中展示仍然是一个转发网站内容的处理器,代码量与相应的同步版本差不多。协程化的关键技术点如下。

  • 用tornado.gen.coroutine装饰MainHandler的get()、post()等处理函数。
  • 使用异步对象处理耗时操作,比如本例的AsyncHTTPClient。
  • 调用yield关键字获取异步对象的处理结果。