Tornado框架
简介:
Tornado就是我们在 FriendFeed 的 Web 服务器及其常用工具的开源版本[1] 。Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快。得利于其 非阻塞的方式和对epoll的运用,Tornado 每秒可以处理数以千计的连接,因此 Tornado 是实时 Web 服务的一个 理想框架。Tornado是一个Python的web框架,拥有异步网络库,最初是在FriendFeed(一款社交服务)上开发的。通过使用非阻塞网络I/O,Tornado可以扩展到成千上万的开放连接,使它成为长轮询、WebSockets和其他需要长连接到每个用户应用程序的理想选择。
安装:
C:\Program Files\RabbitMQ Server\rabbitmq_server-3.6.9\sbin>pip3 install tornado Collecting tornado Downloading tornado-4.5.1-cp36-cp36m-win_amd64.whl (422kB) 100% |████████████████████████████████| 430kB 16kB/s Installing collected packages: tornado Successfully installed tornado-4.5.1
Python3.6上执行 pip3 install tornado 后即可安装成功
简单示例:
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), ]) if __name__ == "__main__": app = make_app() app.listen(8888) tornado.ioloop.IOLoop.current().start()
运行后,浏览器输入http://127.0.0.1:8888即可访问,显示Hello world。这个示例不使用任何Tornado的异步特性,看起来只是像个简单的聊天室。
简单的Web服务
1、应用示例:
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) # python hello.py --port=8000 指定运行端口默认8000 传入类型int class IndexHandler(tornado.web.RequestHandler): # 处理类 def get(self): # get请求 greeting = self.get_argument('greeting', 'Hello') # 接收greeting参数,默认为Hello self.write(greeting + ', friendly user!') # 返回的数据 if __name__ == "__main__": # 这是真正使得Tornado运转起来的语句。首先,我们使用Tornado的options模块来解析命令行 # 然后我们创建了一个Tornado的Application类的实例。传递给Application类__init__方法的最重要的参数是handlers。 # 它告诉Tornado应该用哪个类来响应请求。 tornado.options.parse_command_line() app = tornado.web.Application(handlers=[(r"/", IndexHandler)]) # 从这里开始的代码将会被反复使用:一旦Application对象被创建,我们可以将其传递给Tornado的HTTPServer对象, # 然后使用我们在命令行指定的端口进行监听(通过options对象取出。) # 最后,在程序准备好接收HTTP请求后,我们创建一个Tornado的IOLoop的实例。 http_server = tornado.httpserver.HTTPServer(app) http_server.listen(options.port) tornado.ioloop.IOLoop.instance().start()
直接运行程序或者指定端口:
$ python hello.py --port=8000
通过浏览器访问对应的端口服务,传入greeting参数获取不同的结果:
$ curl http://localhost:8000/ Hello, friendly user! $ curl http://localhost:8000/?greeting=Salutations Salutations, friendly user!
① 参数handlers:
让我们再看一眼hello.py示例中的这一行:
app = tornado.web.Application(handlers=[(r"/", IndexHandler)])
这里的参数handlers非常重要,值得我们更加深入的研究。它应该是一个元组组成的列表,其中每个元组的第一个元素是一个用于匹配的正则表达式,第二个元素是一个RequestHanlder类。在hello.py中,我们只指定了一个正则表达式-RequestHanlder对,但你可以按你的需要指定任意多个。
② 使用正则表达式指定路径:
Tornado在元组中使用正则表达式来匹配HTTP请求的路径。(这个路径是URL中主机名后面的部分,不包括查询字符串和碎片。)Tornado把这些正则表达式看作已经包含了行开始和结束锚点(即,字符串"/"被看作为"^/$")。
如果一个正则表达式包含一个捕获分组(即,正则表达式中的部分被括号括起来),匹配的内容将作为相应HTTP请求的参数传到RequestHandler对象中。我们将在下个例子中看到它的用法。
2、更复杂的示例:
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), # 正则匹配访问url,调用不同的函数类 (r"/wrap", WrapHandler) ] ) http_server = tornado.httpserver.HTTPServer(app) http_server.listen(options.port) tornado.ioloop.IOLoop.instance().start()
启动程序,这个程序是一个通用的字符串操作的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.
RequestHandler更多
到目前为止,我们已经了解了RequestHandler对象的基础:如何从一个传入的HTTP请求中获得信息(使用get_argument和传入到get和post的参数)以及写HTTP响应(使用write方法)。除此之外,还有很多需要学习的,我们将在接下来的章节中进行讲解。同时,还有一些关于RequestHandler和Tornado如何使用它的只是需要记住。
1、HTTP方法:
截止到目前讨论的例子,每个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())
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示例添加了常规的错误消息的版本。
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.