Tornado是使用Python编写的一个强大的、可扩展的Web服务器。它在处理严峻的网络流量时表现得足够强健,但却在建立和编写时有着足够的轻量级,并可以被用在大量的应用和工具中。
咱们如今所知道的Tornado是基于Bret Taylor和其余人员为FriendFeed所开发的网络服务框架,当FriendFeed被Facebook收购后得以开源。不一样于那些最多只能达到10,000个并发链接的传统网络服务器,Tornado在设计之初就考虑到了性能因素,旨在解决C10K问题,这样的设计使得其成为一个拥有很是高性能的框架。此外,它还拥有处理安全性、用户验证、社交网络以及与外部服务(如数据库和网站API)进行异步交互的工具。
延伸阅读:C10K问题
基于线程的服务器,如Apache,为了传入的链接,维护了一个操做系统的线程池。Apache会为每一个HTTP链接分配线程池中的一个线程,若是全部的线程都处于被占用的状态而且尚有内存可用时,则生成一个新的线程。尽管不一样的操做系统会有不一样的设置,大多数Linux发布版中都是默认线程堆大小为8MB。Apache的架构在大负载下变得不可预测,为每一个打开的链接维护一个大的线程池等待数据极易迅速耗光服务器的内存资源。
大多数社交网络应用都会展现实时更新来提醒新消息、状态变化以及用户通知,这就要求客户端须要保持一个打开的链接来等待服务器端的任何响应。这些长链接或推送请求使得Apache的最大线程池迅速饱和。一旦线程池的资源耗尽,服务器将不能再响应新的请求。
异步服务器在这一场景中的应用相对较新,但他们正是被设计用来减轻基于线程的服务器的限制的。当负载增长时,诸如Node.js,lighttpd和Tornodo这样的服务器使用协做的多任务的方式进行优雅的扩展。也就是说,若是当前请求正在等待来自其余资源的数据(好比数据库查询或HTTP请求)时,一个异步服务器能够明确地控制以挂起请求。异步服务器用来恢复暂停的操做的一个常见模式是当合适的数据准备好时调用回调函数。咱们将会在第五章讲解回调函数模式以及一系列Tornado异步功能的应用。
自从2009年9月10日发布以来,Tornado已经得到了不少社区的支持,而且在一系列不一样的场合获得应用。除FriendFeed和Facebook外,还有不少公司在生产上转向Tornado,包括Quora、Turntable.fm、Bit.ly、Hipmunk以及MyYearbook等。
总之,若是你在寻找你那庞大的CMS或一体化开发框架的替代品,Tornado可能并非一个好的选择。Tornado并不须要你拥有庞大的模型创建特殊的方式,或以某种肯定的形式处理表单,或其余相似的事情。它所作的是让你可以快速简单地编写高速的Web应用。若是你想编写一个可扩展的社交应用、实时分析引擎,或RESTful API,那么简单而强大的Python,以及Tornado(和这本书)正是为你准备的!
1.1.1 Tornado入门
安装
pip install tornado
一旦Tornado在你的机器上安装好,你就能够很好的开始了!压缩包中包含不少demo,好比创建博客、整合Facebook、运行聊天服务等的示例代码。咱们稍后会在本书中经过一些示例应用逐步讲解,不过你也应该看看这些官方demo
既然咱们已经知道了Tornado是什么了,如今让咱们看看它能作什么吧。咱们首先从使用Tornado编写一个简单的Web应用开始。
1.2.1 Hello Tornado
Tornado是一个编写对HTTP请求响应的框架。做为程序员,你的工做是编写响应特定条件HTTP请求的响应的handler。下面是一个全功能的Tornado应用的基础示例:
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!')
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应用中最多的工做是定义类继承Tornado的RequestHandler类。在这个例子中,咱们建立了一个简单的应用,在给定的端口监听请求,并在根目录("/")响应请求。python
你能够在命令行里尝试运行这个程序以测试输出
$ python hello.py --port=8000
curl http://192.168.201.130:8000/
Hello, friendly user!
curl http://192.168.201.130:8000/?greeting=Salutations
Salutations, friendly user!
让咱们把这个例子分红小块,逐步分析它们:
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
一、在程序的最顶部,咱们导入了一些Tornado模块。虽然Tornado还有另一些有用的模块,但在这个例子中咱们必须至少包含这四个模块。
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
二、Tornado包括了一个有用的模块(
tornado.options
)来从命令行中读取设置。咱们在这里使用这个模块指定咱们的应用监听HTTP请求的端口。它的工做流程以下:若是一个与
define
语句中同名的设置在命令行中被给出,那么它将成为全局
options
的一个属性。若是用户运行程序时使用了
--help
选项,程序将打印出全部你定义的选项以及你在
define
函数的
help
参数中指定的文本。若是用户没有为这个选项指定值,则使用
default
的值进行代替。Tornado使用
type
参数进行基本的参数类型验证,当不合适的类型被给出时抛出一个异常。所以,咱们容许一个整数的
port
参数做为
options.port
来访问程序。若是用户没有指定值,则默认为8000。
$ python 1_lesson_helloworld.py -- help
class IndexHandler(tornado.web.RequestHandler):
def get(self):
greeting = self.get_argument('greeting', 'Hello')
self.write(greeting + ', friendly user!')
三、这是Tornado的请求处理函数类。当处理一个请求时,Tornado将这个类实例化,并调用与HTTP请求方法所对应的方法。在这个例子中,咱们只定义了一个
get
方法,也就是说这个处理函数将对HTTP的
GET
请求做出响应。咱们稍后将看到实现不止一个HTTP方法的处理函数。
greeting = self.get_argument('greeting', 'Hello')
四、Tornado的
RequestHandler
类有一系列有用的内建方法,包括
get_argument
,咱们在这里从一个查询字符串中取得参数
greeting
的值。(若是这个参数没有出如今查询字符串中,Tornado将使用
get_argument
的第二个参数做为默认值。)
查询字符串query string,在地址栏中 “?” 号后面的字符串, key01=value01&key02=value02
self.write(greeting + ', friendly user!')
五、RequestHandler
的另外一个有用的方法是
write
,它以一个字符串做为函数的参数,并将其写入到HTTP响应中。在这里,咱们使用请求中
greeting
参数提供的值插入到greeting中,并写回到响应中。
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(handlers=[(r"/", IndexHandler)])
六、这是真正使得Tornado运转起来的语句。首先,咱们使用Tornado的
options
模块来解析命令行。而后咱们建立了一个Tornado的
Application
类的实例。传递给
Application
类
__init__
方法的最重要的参数是
handlers
。它告诉Tornado应该用哪一个类来响应请求。
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
七、从这里开始的代码将会被反复使用:一旦
Application
对象被建立,咱们能够将其传递给Tornado的
HTTPServer
对象,而后使用咱们在命令行指定的端口进行监听(经过
options
对象取出。)最后,在程序准备好接收HTTP请求后,咱们建立一个Tornado的
IOLoop
的实例。
让咱们再看一眼
hello.py
示例中的这一行:
app = tornado.web.Application(handlers=[(r"/", IndexHandler)])
这里的参数
handlers
很是重要,值得咱们更加深刻的研究。它应该是一个元组组成的列表,其中每一个元组的第一个元素是一个用于匹配的正则表达式,第二个元素是一个
RequestHanlder
类。在
hello.py
中,咱们只指定了一个正则表达式-
RequestHanlder
对,但你能够按你的须要指定任意多个。
Tornado在元组中使用正则表达式来匹配HTTP请求的路径。(这个路径是URL中主机名后面的部分,不包括查询字符串和碎片。)Tornado把这些正则表达式看做已经包含了行开始和结束锚点(即,字符串"/"被看做为"^/$")。git
若是一个正则表达式包含一个捕获分组(即,正则表达式中的部分被括号括起来),匹配的内容将做为相应HTTP请求的参数传到
RequestHandler对象中。咱们将在下个例子中看到它的用法。
例1-2是一个咱们目前为止看到的更复杂的例子,它将介绍更多Tornado的基本概念。
import textwrap
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 ReverseHandler(tornado.web.RequestHandler):
def get(self, input):
self.write(input[::-1])
class WrapHandler(tornado.web.RequestHandler):
def post(self):
text = self.get_argument('text')
width = self.get_argument('width', 40)
self.write(textwrap.fill(text, int(width)))
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(
handlers=[
(r"/reverse/(\w+)", ReverseHandler),
(r"/wrap", WrapHandler)
]
)
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
如同运行第一个例子,你能够在命令行中运行这个例子使用以下的命令:
$ python string_service.py --port=8000
这个程序是一个通用的字符串操做的Web服务端基本框架。到目前为止,你能够用它作两件事情。其一,到
/reverse/string
的
GET
请求将会返回URL路径中指定字符串的反转形式。
$ curl http://192.168.201.130:8000/reverse/stressed
desserts
$ curl http://192.168.201.130:8000/reverse/slipup
pupils
其二,到
/wrap
的
POST
请求将从参数
text
中取得指定的文本,并返回按照参数
width
指定宽度装饰的文本。下面的请求指定一个没有宽度的字符串,因此它的输出宽度被指定为程序中的
get_argument
的默认值40个字符。
$ curl http://192.168.201.130:8000/ -d text=sfefgsgsergsdrhsdrtsdfhgsdrhsdrt
$ curl http://192.168.201.130:8000/ -d foo=bar
字符串服务示例和上一节示例代码中大部分是同样的。让咱们关注那些新的代码。首先,让咱们看看传递给
Application
构造函数的
handlers
参数的值:
app = tornado.web.Application(handlers=[
(r"/reverse/(\w+)", ReverseHandler),
(r"/wrap", WrapHandler)
])
一、在上面的代码中,
Application
类在"handlers"参数中实例化了两个
RequestHandler
类对象。第一个引导Tornado传递路径匹配下面的正则表达式的请求:
正则表达式告诉Tornado匹配任何以字符串/reverse/开始并紧跟着一个或多个字母的路径。括号的含义是让Tornado保存匹配括号里面表达式的字符串,并将其做为请求方法的一个参数传递给
RequestHandler
类。让咱们检查
ReverseHandler
的定义来看看它是如何工做的:
class ReverseHandler(tornado.web.RequestHandler):
def get(self, input):
self.write(input[::-1])
你能够看到这里的get方法有一个额外的参数input。这个参数将包含匹配处理函数正则表达式第一个括号里的字符串。(若是正则表达式中有一系列额外的括号,匹配的字符串将被按照在正则表达式中出现的顺序做为额外的参数传递进来。)程序员
二、如今,让咱们看一下WrapHandler的定义:
class WrapHandler(tornado.web.RequestHandler):
def post(self):
text = self.get_argument('text')
width = self.get_argument('width', 40)
self.write(textwrap.fill(text, int(width)))
WrapHandler类处理匹配路径为/wrap
的请求。这个处理函数定义了一个post方法,也就是说它接收HTTP的POST方法的请求。
咱们以前使用
RequestHandler对象的
get_argument方法来捕获请求查询字符串的的参数。一样,咱们也可使用相同的方法来得到
POST请求传递的参数。(Tornado能够解析URLencoded和multipart结构的
POST请求)。一旦咱们从
POST中得到了文本和宽度的参数,咱们使用Python内建的
textwrap模块来以指定的宽度装饰文本,并将结果字符串写回到HTTP响应中。
1.2.3 关于RequestHandler的更多知识
到目前为止,咱们已经了解了
RequestHandler
对象的基础:如何从一个传入的HTTP请求中得到信息(使用
get_argument
和传入到
get
和
post
的参数)以及写HTTP响应(使用
write
方法)。除此以外,还有不少须要学习的,咱们将在接下来的章节中进行讲解。同时,还有一些关于
RequestHandler
和Tornado如何使用它的只是须要记住。
1.2.3.1 HTTP方法
截止到目前讨论的例子,每一个
RequestHandler
类都只定义了一个HTTP方法的行为。可是,在同一个处理函数中定义多个方法是可能的,而且是有用的。把概念相关的功能绑定到同一个类是一个很好的方法。好比,你可能会编写一个处理函数来处理数据库中某个特定ID的对象,既使用
GET
方法,也使用
POST
方法。想象
GET
方法来返回这个部件的信息,而
POST
方法在数据库中对这个ID的部件进行改变:
# matched with (r"/widget/(\d+)", WidgetHandler)
widget = {'1': 'aaaa', '2': 'bbbb', '3': 'cccc'}
def retrieve_from_db(widget_id):
return True if widget.get(widget_id) else {}
def save_to_db(wid):
widget.update(wid)
class WidgetHandler(tornado.web.RequestHandler):
def get(self, widget_id):
wid = retrieve_from_db(widget_id)
self.write(widget[widget_id] if wid else 'no')
def post(self, widget_id):
wid = retrieve_from_db(widget_id)
if not wid:
wid[widget_id] = self.get_argument('foo')
save_to_db(wid)
self.write(widget)
咱们到目前为止只是用了
GET
和
POST
方法,但Tornado支持任何合法的HTTP请求(
GET
、
POST
、
PUT
、
DELETE
、
HEAD
、
OPTIONS
)。你能够很是容易地定义上述任一种方法的行为,只须要在
RequestHandler
类中使用同名的方法。下面是另外一个想象的例子,在这个例子中针对特定frob ID的
HEAD
请求只根据frob是否存在给出信息,而
GET
方法返回整个对象:
# matched with (r"/frob/(\d+)", FrobHandler)
class FrobHandler(tornado.web.RequestHandler):
def head(self, frob_id):
frob = retrieve_from_db(frob_id)
if frob:
self.set_status(200)
else:
self.set_status(404)
def get(self, frob_id):
frob = retrieve_from_db(frob_id)
if frob:
self.write(widget[frob_id])
curl -i -X HEAD http://192.168.201.130:8000/frob/1
curl http://192.168.201.130:8000/frob/2
1.2.3.2 HTTP状态码
从上面的代码能够看出,你可使用
RequestHandler类的
set_status()方法显式地设置HTTP状态码。然而,你须要记住在某些状况下,Tornado会自动地设置HTTP状态码。下面是一个经常使用状况的:
404 Not Found
Tornado会在HTTP请求的路径没法匹配任何RequestHandler类相对应的模式时返回404(Not Found)响应码。github
400 Bad Request
若是你调用了一个没有默认值的get_argument函数,而且没有发现给定名称的参数,Tornado将自动返回一个400(Bad Request)响应码。web
405 Method Not Allowed
若是传入的请求使用了RequestHandler中没有定义的HTTP方法(好比,一个POST请求,可是处理函数中只有定义了get方法),Tornado将返回一个405(Methos Not Allowed)响应码。正则表达式
500 Internal Server Error
当程序遇到任何不能让其退出的错误时,Tornado将返回500(Internal Server Error)响应码。你代码中任何没有捕获的异常也会致使500响应码。数据库
200 OK
若是响应成功,而且没有其余返回码被设置,Tornado将默认返回一个200(OK)响应码。浏览器
当上述任何一种错误发生时,Tornado将默认向客户端发送一个包含状态码和错误信息的简短片断。若是你想使用本身的方法代替默认的错误响应,你能够重写
write_error方法在你的
RequestHandler类中。好比,代码清单1-3是
hello.py示例添加了常规的错误消息的版本。
#coding=utf-8
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("%d \n" % 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()
当咱们尝试一个
POST
请求时,会获得下面的响应。通常来讲,咱们应该获得Tornado默认的错误响应,但由于咱们覆写了
write_error
,咱们会获得不同的东西:
$ curl http://192.168.201.130:8000/ -d foo=bar
405
1.2.4 下一步
如今你已经明白了最基本的东西,咱们渴望你想了解更多。在接下来的章节,咱们将向你展现可以帮助你使用Tornado建立成熟的Web服务和应用的功能和技术。首先是:Tornado的模板系统。