Tornado 之web
上一篇文章讲解了httpserver。它最终传入的是一个函数,接受的是Request对象,这和Tornado所展示的Hello World有一些差距。tornado的web模块就是将它从一个函数变化成一个类对象,并且包含了基础的路由功能,最终实现类似与这样的Hello World(代码基于1.0.0版本)
1 | from tornado import web |
将handler_request回调封装为RequestHandler类和Application
和上一篇的httpserver并没有太大的变动,传入的对象由单个函数变成了application对象,并且web模块有RequestHandler和Application类,能猜想到web模块提供了路由功能,对请求的HTTP。它先得到请求路径和HTTP方法。找到对应的类。调用对应的方法。由此写出以下最简代码
1 | class RequestHandler: |
将所有的RequestHandler类均放置到Application下面。当它被tornado.httpserver调用传入request对象的时候会调用Application的__call__
函数。因为self.handlers中,每一个handler均有对应的路由地址,毫无疑问,这个地方会存在一个匹配过程。因为调用__call__
的时候request对象已经可以提取到host、port、url、header、body等各种信息,最常用的只需要使用host和url即可确定调用哪一个RequestHandler。匹配后对RestsHandler进行初始化,此时传入了request对象和Application对象本身,最后调用_execute完成整个过程的执行
路由功能
路由功能的实现主要依靠正则匹配。比如实现对博客地址的匹配/(\d{4})/(\d{2})/(\d{2})/(.+?)
。分别表示年、月、日、主题,或者使用正则命名分组/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<theme>.+?)
。那么我们只需要再添加的时候执行re.compile。在匹配的时候执行re.match即可。对于普通的不存在正则分组的情况,就不需要传入正则分组匹配的内容。否则像以下这样
1 | class BlogHandler(RequestHandler): |
在Tornado里面它使用了URLSpec类来封装这个事情
其他
和上一篇的httpserver中使用handler_request函数不同。本篇的web模块对它进行了进一步的封装。我们面对的编写主要的业务逻辑均是继承自RequestHandler对象。在上一章基本面对的还是直接对iostream进行写操作,写入header、写入cookies、写入etag、模板渲染这些都是没有直接提供的。web模块作为它的更高级别封装,它提供了这些功能。
- 路由匹配
- 给RequestHandler对象暴露Application对象和Request对象
- 提供状态码、跳转、url参数、header、cookies、缓存头、模板渲染、CSRF、日志记录等功能
- 提供http请求生命周期内的各项hook事件prepare、on_finish、on_connection_close
- 提供最常用的辅助函数get_login_url、get_current_user等
- 基于RequestHandler继承出了一些其他对象,比如ErrorHandler、StaticFileHandler、RedirectHandler等。注意,这一些和Flask等web框架的用法不一样。在Flask中是直接return ErrorHandler()等等。可是在Tornado中最后执行的是RequestHandler._execute,它是不支持直接返回这一些对象的。比如跳转直接执行self.redirect即可。这一些继承对象应该使用Application.add_handler进行调用。配合url匹配规则,然后最终被调用ErrorHandler._execute等等
web里面还存在一个装饰器函数asynchronous,这个装饰器在Tornado存在了很长时间,只是现在基本可以不使用它了。可是依旧很多人喜欢一言不合就带上它。用法是这样
1 | class MyRequestHandler(web.RequestHandler): |
产生的原因是最终web.RequestHandler._execute调用了get函数。get执行完http.fetch整个过程就完成了。只是http.fetch它并没有阻塞,最终的结果是加入了IOLoop的回调,_execute的默认逻辑是当get函数执行完毕后会立即调用self.finish表示请求流程结束。明显这不是我们需要的逻辑,正常逻辑是等待网页下载完毕后再返回内容,因此web.asynchronous的逻辑很简单。将get函数设置一个标志位self._auto_finish = False
表明该函数不需要调用self.finish()
操作,自然最后再手动调用finish就好了