01-Tornado简介

 
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
 
 
1.1.2 社区和支持
 
对于问题、示例和一般的指南,Tornado官方文档是个不错的选择。在tornadoweb.org上有大量的例子和功能缺陷,更多细节和变更可以在Tornado在Github上的版本库中看到。而对于更具体的问题,可以到Tornado的Google Group中咨询,那里有很多活跃的日常使用Tornado的开发者。
 
 
1.2 简单的Web服务
 
既然我们已经知道了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 hello.py --port=8000
 
现在你可以在浏览器中打开http://localhost:8000,或者打开另一个终端窗口使用curl测试我们的应用:
 
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
 
1、在程序的最顶部,我们导入了一些Tornado模块。虽然Tornado还有另外一些有用的模块,但在这个例子中我们必须至少包含这四个模块。
 
 
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
 
2、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!')
 
3、这是Tornado的请求处理函数类。当处理一个请求时,Tornado将这个类实例化,并调用与HTTP请求方法所对应的方法。在这个例子中,我们只定义了一个get方法,也就是说这个处理函数将对HTTP的GET请求作出响应。我们稍后将看到实现不止一个HTTP方法的处理函数。
 
 
greeting = self.get_argument('greeting', 'Hello')
 
4、Tornado的RequestHandler类有一系列有用的内建方法,包括get_argument,我们在这里从一个查询字符串中取得参数greeting的值。(如果这个参数没有出现在查询字符串中,Tornado将使用get_argument的第二个参数作为默认值。)
 
 
查询字符串query string,在地址栏中 “?” 号后面的字符串, key01=value01&key02=value02
 
self.write(greeting + ', friendly user!')
 
5、RequestHandler的另一个有用的方法是write,它以一个字符串作为函数的参数,并将其写入到HTTP响应中。在这里,我们使用请求中greeting参数提供的值插入到greeting中,并写回到响应中。
 
 
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(handlers=[(r"/", IndexHandler)])
 
6、这是真正使得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()
 
7、从这里开始的代码将会被反复使用:一旦Application对象被创建,我们可以将其传递给Tornado的HTTPServer对象,然后使用我们在命令行指定的端口进行监听(通过options对象取出。)最后,在程序准备好接收HTTP请求后,我们创建一个Tornado的IOLoop的实例。
 
 
1.2.1.1 参数handlers
 
让我们再看一眼hello.py示例中的这一行:
 
app = tornado.web.Application(handlers=[(r"/", IndexHandler)])
 
这里的参数handlers非常重要,值得我们更加深入的研究。它应该是一个元组组成的列表,其中每个元组的第一个元素是一个用于匹配的正则表达式,第二个元素是一个RequestHanlder类。在hello.py中,我们只指定了一个正则表达式-RequestHanlder对,但你可以按你的需要指定任意多个。
 
 
 
 
1.2.1.2 使用正则表达式指定路径
 

Tornado在元组中使用正则表达式来匹配HTTP请求的路径。(这个路径是URL中主机名后面的部分,不包括查询字符串和碎片。)Tornado把这些正则表达式看作已经包含了行开始和结束锚点(即,字符串"/"被看作为"^/$")。

 
如果一个正则表达式包含一个捕获分组(即,正则表达式中的部分被括号括起来),匹配的内容将作为相应HTTP请求的参数传到RequestHandler对象中。我们将在下个例子中看到它的用法。
 

1.2.2 字符串服务

 
例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)
])
 
1、在上面的代码中,Application类在"handlers"参数中实例化了两个RequestHandler类对象。第一个引导Tornado传递路径匹配下面的正则表达式的请求:
 
/reverse/(\w+)

正则表达式告诉Tornado匹配任何以字符串/reverse/开始并紧跟着一个或多个字母的路径。括号的含义是让Tornado保存匹配括号里面表达式的字符串,并将其作为请求方法的一个参数传递给RequestHandler类。让我们检查ReverseHandler的定义来看看它是如何工作的:
 
class ReverseHandler(tornado.web.RequestHandler):
def get(self, input):
self.write(input[::-1])

 

你可以看到这里的get方法有一个额外的参数input。这个参数将包含匹配处理函数正则表达式第一个括号里的字符串。(如果正则表达式中有一系列额外的括号,匹配的字符串将被按照在正则表达式中出现的顺序作为额外的参数传递进来。)

 
2、现在,让我们看一下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和传入到getpost的参数)以及写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)响应码。

400 Bad Request

如果你调用了一个没有默认值的get_argument函数,并且没有发现给定名称的参数,Tornado将自动返回一个400(Bad Request)响应码。

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的模板系统。
 
 
 

01-tornado学习笔记-Tornado简介的更多相关文章

  1. Tornado学习笔记(一) helloword/多进程/启动参数

    前言 当你觉得你过得很舒服的时候,你肯定没有在进步.所以我想学习新的东西,然后选择了Tornado.因为我觉得Tornado更匹配目前的我的综合素质. Tornado学习笔记系列主要参考<int ...

  2. tornado 学习笔记1 引言

    从事软件开发这行业也快5年啦,其实从事的工作也不完全是软件开发,软件开发只是我工作中的一部分.其中包括课题研究.信息化方案设计.软件开发.信息系统监理.项目管理等工作,比较杂乱.开发的软件比较多,但是 ...

  3. Linux内核学习笔记-1.简介和入门

    原创文章,转载请注明:Linux内核学习笔记-1.简介和入门 By Lucio.Yang 部分内容来自:Linux Kernel Development(Third Edition),Robert L ...

  4. tornado 学习笔记5 构建Tornado网站应用

    一个Tornado 网站应用通常由一个或多个RequestHanlde的子类.一个负责将请求路由至handlers的Application以及一个启动服务器的main()函数等组成. 一个最小的“he ...

  5. tornado 学习笔记

    import tornado.ioloop import tornado.web class MainHanlwe(tornado.web.RequestHandler): def get(self) ...

  6. React学习笔记 - JSX简介

    React Learn Note 2 React学习笔记(二) 标签(空格分隔): React JavaScript 一.JSX简介 像const element = <h1>Hello ...

  7. CUBRID学习笔记 1 简介 cubrid教程

    CUBRID 是一个全面开源,且完全免费的关系数据库管理系统.CUBRID为高效执行Web应用进行了高度优化,特别是需要处理大数据量和高并发请求的复杂商务服务.通过提供独特的最优化特性,CUBRID可 ...

  8. tornado 学习笔记2 Python web主流框架

    2.1 Django 官方网址:https://www.djangoproject.com/ 简介:Django is a high-level Python Web framework that e ...

  9. tornado 学习笔记7 RequestHandler功能分析

           在第5部分讲到,构建一个tornado网站,必须包含一个或者多个handler,这些handler是RequestHandler的子类.每个请求都会被映射到handler中进行处理,处理 ...

随机推荐

  1. Unity事件系统

    # 1.前言Unity中事件/委托有着广泛的应用,本文通过封装一个简易的事件的系统,来统一管理消息的传递.此功能在简易应用或者事件较少的体现不出太好的作用,但是对于事件应用较多时,可以减少脚本之间的耦 ...

  2. node.js的File模块

    1.Node.js是什么? (1) Nodejs是为了开发高性能的服务器而诞生的一种技术 (2) 简单的说 Node.js 就是运行在服务端的 JavaScript,基于V8进行运行 (3) Node ...

  3. django-模板之if语句(九)

  4. css过渡transition属性

    一.CSS3 过渡 (一).CSS3过渡简介 CSS3过渡是元素从一种样式逐渐改变为另一种的效果. 实现过渡效果的两个要件: 规定把效果添加到哪个 CSS 属性上 规定效果的时长 定义动画的规则 过渡 ...

  5. kaldi中CD-DNN-HMM网络参数更新公式手写推导

    在基于DNN-HMM的语音识别中,DNN的作用跟GMM是一样的,即它是取代GMM的,具体作用是算特征值对每个三音素状态的概率,算出来哪个最大这个特征值就对应哪个状态.只不过以前是用GMM算的,现在用D ...

  6. CTR@DeepFM

    1. DeepFM算法 结合FM算法和DNN算法,同时提取低阶特征和高阶特征,然后组合.FM算法负责对一阶特征及由一阶特征两两组合成的二阶特征进行特征提取:DNN算法负责对由输入的一阶特征进行全连接等 ...

  7. 程序员接私活经验总结,来自csdn论坛语录

    以下为网上摘录,以做笔记: 可是到网上看看,似乎接私活也有很多不容易,技术问题本身是个因素,还有很多有技术的人接私活时被骗,或者是合作到最后以失败告终,所以想请有经验的大侠们出来指点一下,接私活是怎么 ...

  8. regexp盲注的一些改进

    index.php?id=1 and 1=(SELECT 1 FROM information_schema.tables WHERE TABLE_SCHEMA="blind_sqli&qu ...

  9. NOIP模拟 4

    T1没开longlong T2忘了有向... T3是个好题,可以说将复杂度从N^2优化到NlogN是一个质的飞跃 考虑分治(要想出log可不就要分治么!(segtree也行 但我不会) 对于一个分治区 ...

  10. 学习 Git

    Git 简介 Git是目前比较流行的分布式版本控制系统之一,能够记录文件的每次修改,还实现了多人并行开发; Git 组成 工作区(写东西之地) 暂存区 本地仓库(.git) 远程仓库(.repro) ...