前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >tornado全面剖析与实践系列1

tornado全面剖析与实践系列1

作者头像
企鹅号小编
发布2018-02-27 09:46:25
8350
发布2018-02-27 09:46:25
举报
文章被收录于专栏:编程编程

猿助猿的技术栈是基于Tornado的, 在学习的过程中参考了很多文章, 但是内容大都碎片化, 缺少系统性讲解, 而且不少关于异步应用的内容还是基于过时的旧版本. 因此打算将开发过程中遇到的问题和应用整理下来, 一来方便日后查阅, 二来也希望能够帮助到和我一样的Tornado开发者, 于是就有了这个系列的文章。 (注: 文章并不是为初学者准备的, 阅读前要需要确认你已经了解Python语法, HTTP协议等Web开发所需的基础知识)

在Python Web框架中, 最为人熟知的三个是Django, Flask和Tornado, 前两者是一重一轻的同步框架, 而后者则是以高性能著称的异步框架. 在使用Tornado的开发团队中,Quara和知乎是最常被提起的(参考:How-does-Quora-use-Tornado和知乎使用了哪些框架和开源库?).

我想在正文开始之前, 需要说明的是, 请不要迷信框架所谓的”高性能”, 框架的作用是让开发者更快速和便捷的构建起所需的应用, 而性能则是由包括系统架构和开发人员能力在内的诸多因素决定的. 况且, 在高性能服务器价格相较开发人员的薪资”不值一提”和”面向上线时间编程”的今天, 过度追求高性能, 恐怕只会弊大于利. 倘若你将Tornado作为一个同步框架使用, 并认为框架能够”自主”实现高性能的话, 那我可真是无FUCK说了。

简介

首先来看Tornado的介绍:

Tornado是一个Python Web框架兼异步网络库, 最初由FriendFeed开发. 得益于非阻塞网络I/O, Tornado可以支撑起数以万计的连接, 因此它很非常适合开发长轮询,WebSockets和那些需要与每个用户建立持久连接的应用.

Tornado包含以下四大模块:

Web框架

HTTP服务器和客户端

异步网络库

协程库

Tornado的Hello World:

运行以后, 在浏览器访问localhost:8888, 就能看到Hello Tornado! 是的, 这个简单的示例并没有用到任何异步功能, 就是一个最基础的阻塞应用.

Web框架

Tornado在Web框架部分中使用频率最高的RequestHandler, 同时也包括Application等其余相关内容.

RequestHandler

作为每一个HTTP请求的”必经之地”, 一个请求在RequestHandler内的大致处理流程如下:

根据正则匹配创建相应RequestHandler

.initialize()初始化

.prepare()准备

根据请求的http verb method进入相应入口, 如.get() .post()等

.finish()完成请求

.on_finish()后续操作

(注: 这个流程是不够严谨的, 只是希望读者对此能先有个大概的认识)

RequestHandler内的方法可以划分成以下几类: 入口, 输入, 输出, Cookie和其他, 这里只分析其中最常用的方法, 如果想要了解全部内容则需要查阅官方文档.

入口:.initialize()

进行初始化工作, 可以接收来自注册路由时传递的参数. 虽然这里也可以做输出操作, 但是并不建议这么做, 输出操作放到.prepare()会使逻辑更清晰.

.prepare()

可以理解为一个请求”真正”的开始, 主要用来处理一些请求的准备工作, 比如预处理请求, 也可以做输出操作. 完成以后进入到.get() .post()等. 需要注意的是, 如果在这里结束请求, 如调用.finish()等, 那就不会执行.get() .post()等. 有一个比较有意思的点是.prepare()是可以”异步”的, 更准确的说法应该是可以”协程化”, 通过@gen.coroutine或@return_future可以实现(不能使用@asynchronous). 关于Tornado实现协程和异步的方法, 后续会有文章深入探讨, 这里就不展开说了.

.on_finish()

请求完成后自动调用(实际上是由.finish()调用的), 可以根据需要做一些释放资源或写日志等操作. 注意, 这里是不能进行输出操作的.

默认支持的http verb method

.get() .post() .put() .patch() .delete() .head() .options()

跑一个例子能更好的理解这个流程

输入:请求参数

.get_argument() .get_arguments()

从body和url中获取参数(参数都是unicode编码的), 两者不同点在于.get_arguments()返回的是参数列表, 而.get_argument()返回参数列表的最后一个参数, 并且.get_argument()会在目标参数不存在的时候抛出MissingArgumentError异常.

.get_query_argument() .get_query_arguments()

从url中获取参数, 区别参考.get_argument() .get_arguments()

.get_body_argument() .get_body_arguments()

从body中获取参数, 区别参考.get_argument() .get_arguments()

.get_json()

实际上, Tornado并未直接提供获取json格式数据的方法, 如果有需要的话, 可以参考下面这段代码

请求信息.request

.request实际上是一个HTTPServerRequest对象, 包含method uri query version headers body remote_ip protocol host arguments query_arguments body_arguments files connection cookies full_url() request_time().

这里只介绍headers和files(cookies放在后面与相关方法一起介绍), 其余的可以参考官方文档, 又或是print出来看看是什么.

在上传文件时(Content-Type: multipart/form-data; boundary=----WebKitFormBoundary*random_string*), 文件变为HTTPFile对象

headers 是一个HTTPHeaders对象, 使用方法参考:

输出(参考链接):HTTP status.set_status()

设置响应HTTP状态码

.send_error() .write_error()

.send_error()用于发送HTTP错误页(状态码). 该操作会调用.clear() .set_status() .write_error()用于清除headers, 设置状态码, 发送错误页. 重写.write_error()可以自定义错误页.

HTTP header

.add_header() .set_header() .set_default_headers()

设置响应HTTP头, 前两者的不同点在于多次设置同一个项时, .add_header()会”叠加”参数, 而.set_header()则以最后一次为准.

.set_default_headers()比较特殊, 是一个空方法, 可根据需要重写, 作用是在每次请求初始化RequestHandler时设置默认headers.

.clear_header() .clear()

.clear_header()清除指定的headers, 而.clear()清除.set_default_headers()以外所有的headers设置.

数据流.write()

将数据写入输出缓冲区. 如果直接传入dict, 那Tornado会自动将其识别为json, 并把Content-Type设置为application/json, 如果你不想要这个Content-Type, 那么在.write()之后, 调用.set_header()重新设置就好了. 需要注意的是, 如果直接传入的是list, 考虑到安全问题(json数组会被认为是一段可执行的JavaScript脚本, 且可以绕过跨站限制), list将不会被转换成json.

.flush()

将输出缓冲区的数据写入socket. 如果设置了callback, 会在完成数据写入后回调. 需要注意的是, 同一时间只能有一个”等待”的flush callback, 如果”上一次”的flush callback还没执行, 又来了新的flush, 那么”上一次”的flush callback会被忽略掉.

.finish()

完成响应, 结束本次请求. 通常情况下, 请求会在return时自动调用.finish(), 只有在使用了异步装饰器@asynchronous或其他将._auto_finish设置为False的操作, 才需要手动调用.finish().

页面.render()

返回渲染完成的html. 调用后不能再进行输出操作.

.redirect()

重定向, 可以指定3xx重定向状态码. 调用后不能再进行输出操作.

Cookie:获取.cookies

.request.cookies的别名, Cookie.SimpleCookie()对象(了解更多).

设置和解析

.set_cookie() .set_secure_cookie() .get_cookie() .get_secure_cookie()

设置和解析cookies. 两组方法的用法基本一致, 不过使用.set_secure_cookie() .get_secure_cookie()前需要在Application中设置cookie_secret.

清除

.clear_cookie() .clear_all_cookies()

清除cookie. 前者清除指定值, 后者清除所有.

安全签名.create_signed_value()

这个方法比较特殊, 作用是生成一个难以被伪造的带时间戳的加密字符串, 这是.set_secure_cookie()之所以”secure”的关键. 同样也需要先在Application中设置cookie_secret.

其他Application.application

获取处理这个请求的Application对象. 可以用来访问Application内部的变量.

.setting

.application.setting 的别名, 用于获取Application当前配置(dict格式).

.require_setting()

查询Application是否有配置此选项, 如果没有会触发异常.

用户验证

.current_user .get_current_user()

获取当前用户. 只有第一次在请求内调用.current_user时, 才会通过.get_current_user()获取当前用户, 所以.current_user相当于当前用户的缓存. .get_current_user()是一个需要复写的空方法, 用于获取当前用户.

.get_login_url()

获取登录页面链接. Tornado内置的身份验证是由@authenticated.current_user .get_login_url()实现的. 使用@authenticated后, 会在.current_user为None时跳转到login_url. 默认情况下, 使用.get_login_url()需要先在Application设置login_url, 当然也可以通过复写.get_login_url()免去配置, 同时也能更加灵活的配置登录链接.

防御跨站请求伪造.xsrf_form_html()

内置的防御跨站请求伪造功能, 需要放在html里面, 使用前要在Application设置cookie_secret xsrf_cookies. 实现原理是给把两个由同一token签名过的字符串分别放置在cookie和html中, 然后在”正式”处理请求前, 解密这两个字符串然后比对token是否相同.

有意思的是token的比较并不是简单采用a == b这种方式, 而是使用了一个叫_time_independent_equals的函数. 为什么要绕一大圈呢? 实际上是出于安全的考虑, 常规的比较方法如a == b, 一旦发现两者的不同点, 就会立即退出比较, 这样好像确实也没什么不妥的, 从头到尾比较两个字符串确实太低效. 不过既然考虑到了安全, 就不能以常规的角度去看.

现在我们假设比较一个字符串的时间是1s(当然这是极度夸张放大的耗时), 此时我们需要匹配一个长度为3的字符串, 那么按照a == b比较法, 在命中第一个字符后继续比较第二个字符, 那么此次比较耗时肯定是大于1s的, 如果没有命中第一个字符, 那么耗时是1s. 这样的话, 现在我不就能根据耗时”猜出”我给的第一个字符是否匹配了吗. 当然在实际情况下, 不可能有如此夸张的时间差, 但倘若攻击者能够发起大量请求并分析其结果的话, 这也并不是”mission impossible”, 所以做一个”恒时”匹配还是有比要的.

Application

本文来自企鹅号 - 猿助猿媒体

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文来自企鹅号 - 猿助猿媒体

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
多因子身份认证
多因子身份认证(Multi-factor Authentication Service,MFAS)的目的是建立一个多层次的防御体系,通过结合两种或三种认证因子(基于记忆的/基于持有物的/基于生物特征的认证因子)验证访问者的身份,使系统或资源更加安全。攻击者即使破解单一因子(如口令、人脸),应用的安全依然可以得到保障。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档