##1.1 Tornado是什么
$ curl -L -O https://github.com/facebook/tornado/archive/v3.1.0.tar.gz
$ tar xvzf v3.1.0.tar.gz
$ cd tornado-3.1.0
$ python setup.py build
$ sudo python setup.py install
Tornado官方并不支持Windows,但是可以通过ActivePython的PyPM包管理器进行安装,类似如下所示:
C:\> pypm install tornado
一旦Tornado在你的机器上安装好,我们就可以很好的开始了!压缩包中包含很多demo,比如建立博客、整合Facebook、运行聊天服务等的示例代码。
当然,这一切的基础都建立在我们是在使用Unix的基础上来总结的
###1.1.2 社区和支持
对于问题、示例和一般的指南,Tornado官方文档是个挺好的选择。在tornadoweb.org上有大量的例子和功能缺陷,更多细节和变更可以在Tornado在Github上的版本库中看到。而对于更具体的问题,可以到Tornado的Google Group中咨询,那里有很多活跃的日常使用Tornado的开发者。
##1.2 简单的web服务
下面我们从Tornado的第一个web应用开始
###1.2.1 Hello Tornado
Tornado是一个编写对HTTP请求相应的框架。作为程序员,我们的工作是编写相应特定条件HTTP请求的响应的handler。下面是一个全功能的Tornado应用的基础示例:
hello.py
----------------------------------------------------------------------------------------------------------------------------------------------------------
# 此例中,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):
"""重写get请求处理"""
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()
然后我们可以在命令行里尝试运行这个程序以测试输出:
$ python hello.py --port=8000
现在可以在浏览器中打开http://localhost:8000 ,或者打开另一个终端窗口使用crul测试我们的应用:
$ curl http://localhost:8000/
Hello,friendly user!
$ curl http://localhost:8000/?greeting=Salutations
Salutations, friendly user!
###1.2.2 字符串服务
例1-2是一个我们目前为止看到的更复杂的例子,它将介绍更多Tornado的基本概念。
代码清单1-2 处理输入:string_service.py
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://localhost:8000/reverse/stressed
desserts
$ curl http://localhost:8000/reverse/slipup
pupils
其二,到/wrap的POST请求将从参数text中取得指定的文本,并返回按照参数width指定宽度装饰的文本。下面的请求指定一个没有宽度的字符串,所以它的输出宽度被指定为程序中的get_argument的默认值40个字符。
$ http://localhost:8000/wrap -d text=Lorem+ipsum+dolor+sit+amet,+consectetuer+adipiscing+elit.
Lorem ipsum dolor sit amet, consectetuer
adipiscing elit.
字符串服务示例和上一节示例代码中大部分是一样的。让我们关注那些新的代码。首先,让我们看看传递给Application
构造函数的handlers
参数的值:
app = tornado.web.Application(handlers=[
(r"/reverse/(\w+)", ReverseHandler),
(r"/wrap", WrapHandler)
])
在上面的代码中,
Application
类在"handlers"参数中实例化了两个RequestHandler
类对象。第一个引导Tornado传递路径匹配下面的正则表达式的请求:
/reverse/(\w+)
正则表达式告诉Tornado匹配任何以字符串**
/reverse/
**开始并紧跟着一个或多个字母的路径。括号的含义是让Tornado保存匹配括号里面表达式的字符串,并将其作为请求方法的一个参数传递给RequestHandler类。让我们检查ReverseHandler的定义来看看它是如何工作的:
class ReverseHandler(tornado.web.RequestHandler):
def get(self, input):b nzxg
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响应中。
到目前为止,我们已经了解了RequestHandler对象的基础:如何从一个传入的HTTP请求中获得信息(使用get_argument和传入到get和post的参数)以及写HTTP响应(使用write方法)。除此之外,还有很多需要学习的,我们将在接下来的章节中进行讲解。同时,还有一些关于RequestHandler和Tornado如何使用它的只是需要记住。
截止到目前讨论的例子,每个RequestHandler类都只定义了一个HTTP方法的行为。但是,在同一个处理函数中定义多个方法是可能的,并且是有用的。把概念相关的功能绑定到同一个类是一个很好的方法。比如,你可能会编写一个处理函数来处理数据库中某个特定ID的对象,既使用GET方法,也使用POST方法。想象GET方法来返回这个部件的信息,而POST方法在数据库中对这个ID的部件进行改变:
#matched with (r"/widget/(\d+)", WidgetHandler)
class WidgetHandler(tornado.web.RequestHandler):
def get(self, widget_id):
widget = retrieve_from_db(widget_id)
self.write(widget.serialize())
def post(self, widget_id):
widget = retrieve_from_db(widget_id)
widget['foo'] = self.get_argument('foo')
save_to_db(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 is not None:
self.set_status(200)
else:
self.set_status(404)
def get(self, frob_id):
frob = retrieve_from_db(frob_id)
self.write(frob.serialize())
从上面的代码可以看出,我们可以使用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示例添加了常规的错误消息的版本。
代码清单1-3 常规错误响应:hello-errors.py
-----------------------------------------------------------------------------------------------------------------------------
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("Gosh darnit, user! You caused a %d error." % 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 -d foo=bar http://localhost:8000/
Gosh darnit, user! You caused a 405 error.