Tornado 特点

Tornado是一个用Python写的相对简单的、不设障碍的Web服务器架构,用以处理上万的同时的连接口,让实时的Web服务通畅起来。虽然跟现在的一些用Python写的Web架构相似,比如Django,但Tornado更注重速度,能够处理海量的同时发生的流量。 
FriendFeed的联合创始人Bret Taylor的博客里介绍了更多,他说:把Tornado开源,FriendFeed和Facebook期望第三方能够用以建筑实时的Web服务。其具体的工作原理如上图,看起来很像是FriendFeed的评论系统。

Taylor认为Tornado的三个关键部分是: 
         完整的用以构建网站的基础模块。Tornado包含内置的用以解决网络开发最难和最烦的功能模块,包括模板、signed cookies、用户认证、地方化(localization)、aggressive static file caching, cross-site request forgery protection,以及类似Facebook Connect的第三方认证。开发者可以随取所需,并且自由组合,甚至把Tornado与其他架构组合。 
实时服务。Tornado支持大量的同时发生的信息连接。用Tornado,能够通过HTTP或者Long Polling方便的书写实时服务。要知道,每一个FriendFeed的活跃用户都保持有一个连通FriendFeed服务器的开放通路。 
高效能。Tornado比大多数用Python写的Web架构更快。根据一些实验,Tornado的速度是一般架构的4倍。

Tornado 支持二种模式 一种是串行访问处理,异步非阻塞。当客户端访问web,tornado 可以自行定义前面2个的处理方式。天生支持RESTful

安装

pip install tornado

官方:http://www.tornadoweb.org/en/stable/

一 、HELLO word

 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"/index", MainHandler),
]) if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()

访问 ip:8888/index 测试内容

配置模板

返回html页面

#_*_coding:utf-8_*_
import tornado.ioloop
import tornado.web #通过字典定义的方式定义配置文件属性,
settings = {
'template_path':'template',
} class MainHandler(tornado.web.RequestHandler):
def get(self):
#self.write("Hello, world")
#返回模板中的页面
self.render('index.html') def make_app():
#把setting这个配置参数带入到方法中
return tornado.web.Application([
(r"/index", MainHandler),],**settings) if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()

然后就可以测试页面了,非常的简单

这里介绍render都做了什么
class MainHandler(tornado.web.RequestHandler):
               def get(self):

1先找到template的路径

2根据路径找到对应的html页面(拼接)

3python open方法读取这个html页面内容,将内容读取到了内存中。实际上读出来的其实就是一个字符串

4 self.write(open('html').read()) 读出这个文件然后写回(self.write方法)

5self.write用socket的send方法,将字符串发送给客户端用户(这里会将这个字符串塞进一个双向队列里面,send方法回去队列中去数据然后发送给客户端),用户就可以看见web页面了(这个就是一个简单的过程)

dic ={ 'name'='aaaa' }

这里在说一下模板渲染,上面定义了一个字典dic。那过程就需要多加一步了。

在上面4步:

4open('html').read() 先把文件读取出来

5渲染html页面(实际就是一个很长的字符串),替换字符串中的特殊模板语言(在html文件中也会有模板语言{{ name }},这个key可以dic中的key对应上)

6self.write

self.render('index.html')

#self.render('index.html',**dic)  渲染必须将字典当参数一起传递

路由系统

#_*_coding:utf-8_*_
import tornado.ioloop
import tornado.web #通过字典定义的方式定义配置文件属性,
settings = {
'template_path':'template',
} class MainHandler(tornado.web.RequestHandler):
def get(self):
dic={
'name':'abc'
}
self.render('index.html',**dic) class Home(tornado.web.RequestHandler):
def get(self):
self.render('home.html') class news(tornado.web.RequestHandler):
#接受从url传递过来的参数,nid是从url中传递过来的
def get(self,nid):
self.write(str(nid))
'''
(r"/news/(\d+)",news),这里也可以用正则匹配.想要当参数传递必须()中写表达式
'''
def make_app():
#把setting这个配置参数带入到方法中
return tornado.web.Application([
(r"/index", MainHandler),
(r"/home",Home),
(r"/news/(\d+)",news),
],**settings) if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()

匹配二级域名,这个是Tornado独有的,一般都是匹配xxx.xxxx.xxx/后面的参数,现在要匹配 psp.xxx.com/index  ,匹配psp 这个前缀

#_*_coding:utf-8_*_
import tornado.ioloop
import tornado.web #通过字典定义的方式定义配置文件属性,
settings = {
'template_path':'template',
} class MainHandler(tornado.web.RequestHandler):
def get(self):
dic={
'name':'abc'
}
self.render('index.html',**dic) class psphandler(tornado.web.RequestHandler):
def get(self):
self.write("psp.tb.com/index")
app=tornado.web.Application([
(r"/index", MainHandler),
],**settings) #对app的add_handlers方法在增加一个匹配项,如果匹配上就掉对应的方法。当url过来的时候如果psp.tb.com匹配不上还是回去其它定义的规则中继续匹配
app.add_handlers('psp.tb.com',[
(r"/index",psphandler),
]) if __name__ == "__main__":
app.listen(8888)
tornado.ioloop.IOLoop.current().start()

测试结果:

路由psp

路由正常规则

静态文件

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link href="{{static_url("commons.css")}}" rel="stylesheet" />
</head>
<body>
<h1>index.html</h1>
<h1>{{ name }}</h1>
</body>
</html>

index.html

 #_*_coding:utf-8_*_
import tornado.ioloop
import tornado.web '''
必须定义'static_path':'static',否则'static_path_prefix':'/static/',会报错误。
现在不定义'static_path_prefix':'/static/',前端html文件"{{static_url("commons.css")}}" 也会对文件添加md5码。
当文件更新的时候就会非常方便了,客户端也可以发现静态文件更新,再也不用手动强制更新ie缓存了
'''
settings = {
'template_path':'template',
'static_path':'static',
'static_path_prefix':'/static/',
} class MainHandler(tornado.web.RequestHandler):
def get(self):
dic={
'name':'abc'
}
self.render('index.html',**dic)
app=tornado.web.Application([
(r"/index", MainHandler),
],**settings) if __name__ == "__main__":
app.listen(8888)
tornado.ioloop.IOLoop.current().start()

访问展示:

主要看静态文件后面的自己生成的md5值,然后自己改一下样式重启服务器,客户端刷新就会直接刷新缓存了。

这里还有一个好处是前端不用管后端static文件夹的改变,后端更改路径不会影响前端

cookie操作

#_*_coding:utf-8_*_
import tornado.ioloop
import tornado.web #cookie_secret这个配置的是加"盐"的值
settings = {
'template_path':'template',
'static_path':'static',
'static_path_prefix':'/static/',
'cookie_secret':'asdasdasda',
}
class HomeHandler(tornado.web.RequestHandler):
def get(self):
#读取客户端cookie,k1就是客户端cookie的一个字符串
#self.set_cookie('k1') #设置cookie (参数):name, value, domain=None, expires=None, path="/",expires_days=None, **kwargs
#path="/" 这个参数是设置cookie的作用域范围。www.xxx.com/home 如果这样写就说明这个cookie只作用在home这个url上别的url无法使用
#set_cookie是不加密的 直接明文就传递给客户端,可以用调试工具查看到
self.set_cookie('name','abc')
#加密cookie 进行签名
#self.set_secure_cookie('name','123')
self.render('home.html') def make_app():
return tornado.web.Application([
(r"/home", HomeHandler),
],**settings) if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()

演示:

因为这里无法同时开启self.set_cookie和self.set_secure_cookie。所以这个实验是执行完一个在换另一个。加密的还是相对比较安全的 。

看源码是如何处理加密:

源码:

def set_secure_cookie(self, name, value, expires_days=30, version=None,
**kwargs):
# 这里默认将超时时间设置了expires_days=30,注意看它这直接调用了不加密码的cookie方法self.set_cookie()。
#self.create_signed_value(name, value,ersion=version)加密方法
self.set_cookie(name, self.create_signed_value(name, value,ersion=version),xpires_days=expires_days, **kwargs)

create_signed_value加密方法

def create_signed_value(self, name, value, version=None):
#获取加盐的方法self.require_setting("cookie_secret", "secure cookies")
#在前面的setting增加这几个key就可以
self.require_setting("cookie_secret", "secure cookies") secret = self.application.settings["cookie_secret"]
key_version = None
if isinstance(secret, dict):
if self.application.settings.get("key_version") is None:
raise Exception("key_version setting must be used for secret_key dicts")
key_version = self.application.settings["key_version"]
#这个是全局的变量,虽然跟这个类中的方法名字相同。但是这个不是传给自己的 create_signed_value
return create_signed_value(secret, name, value, version=version,
key_version=key_version)

create_signed_value

def create_signed_value(secret, name, value, version=None, clock=None,
key_version=None):
if version is None:
version = DEFAULT_SIGNED_VALUE_VERSION
if clock is None:
clock = time.time
#生成时间戳
timestamp = utf8(str(int(clock())))
#用base64.b64encode加密,base64是可以反解回来的
value = base64.b64encode(utf8(value))
#有2个版本加密一个是v1一个v2
if version == 1:
signature = _create_signature_v1(secret, name, value, timestamp)
value = b"|".join([value, timestamp, signature])
return value
elif version == 2:
# The v2 format consists of a version number and a series of
# length-prefixed fields "%d:%s", the last of which is a
# signature, all separated by pipes. All numbers are in
# decimal format with no leading zeros. The signature is an
# HMAC-SHA256 of the whole string up to that point, including
# the final pipe.
#
# The fields are:
# - format version (i.e. 2; no length prefix)
# - key version (integer, default is 0)
# - timestamp (integer seconds since epoch)
# - name (not encoded; assumed to be ~alphanumeric)
# - value (base64-encoded)
# - signature (hex-encoded; no length prefix)
def format_field(s):
return utf8("%d:" % len(s)) + utf8(s)
to_sign = b"|".join([
b"",
format_field(str(key_version or 0)),
format_field(timestamp),
format_field(name),
format_field(value),
b'']) if isinstance(secret, dict):
assert key_version is not None, 'Key version must be set when sign key dict is used'
assert version >= 2, 'Version must be at least 2 for key version support'
secret = secret[key_version] signature = _create_signature_v2(secret, to_sign)
return to_sign + signature
else:
raise ValueError("Unsupported version %d" % version)

全局create_signed_value

def _create_signature_v1(secret, *parts):
hash = hmac.new(utf8(secret), digestmod=hashlib.sha1)
for part in parts:
hash.update(utf8(part))
return utf8(hash.hexdigest()) def _create_signature_v2(secret, s):
hash = hmac.new(utf8(secret), digestmod=hashlib.sha256)
hash.update(utf8(s))
return utf8(hash.hexdigest())

加密V1和V2

这样做比较安全,当黑客如果知道你的加密过程伪造cookie,Tornado会根据它的cookie先去做签名,如果签名跟服务器上的一致那么就OK。但是这里有一个重要的因素是

secret = self.application.settings["cookie_secret"]  这个值是定在服务器这边的 很难获取到。如果这值知道了那么久没有任何办法了。

反解过程可以看self.get_secure_cookie() 过程都在这里面了。

最新文章

  1. 单选按钮加confirm进行判断返回false任被选中问题
  2. 解析大型.NET ERP系统数据访问 对象关系映射框架LLBL Gen Pro
  3. (Collection)350. Intersection of Two Arrays II
  4. python——第二天
  5. Linux守护进程的启动方法
  6. zoj The 12th Zhejiang Provincial Collegiate Programming Contest Team Formation
  7. flex脚本的申明
  8. Java数组的复制
  9. __stdcall,__cdecl,_cdecl,_stdcall,。__fastcall,_fastcall 区别简介
  10. CSS自学笔记(6):CSS的模型
  11. 云计算:创业的好时机——上海够快网络科技有限公司总经理蒋烁淼专访(评价阿里云的OSS的4个优点)(够快科技正式宣布已成功挂牌新三板)
  12. 【翻译】在Ext JS 6通用应用程序中使用既共享又特定于视图的代码
  13. jsonp 请求
  14. vscode mysql v0.3插件 连接不了
  15. 解决通过Nginx转发的服务请求头header中含有下划线的key,其值取不到的问题
  16. App设计模式纵横谈(1)
  17. 蓝桥杯 方格填数 DFS 全排列 next_permutation用法
  18. Struts2方法调用的三种方式(有新的!调用方法的说明)
  19. 今日头条 2018 AI Camp 视频面试
  20. CLRInjection - 通用托管注入(超级灰色按钮克星升级版)

热门文章

  1. JavaScript入门笔记(一)
  2. Packet Tracer 5.0 构建CCNA实验(3)—— 路由器实现vlan间通信
  3. (function($){})(jQuery)---Javascript的神级特性:闭包
  4. js获取上、下级html元素 js删除html元素方法
  5. centOS7 vsftp ExecStart=/usr/sbin/vsftpd /etc/vsftpd/vsftpd.conf (code=exited, status=0/SUCCESS) 启动失败问题?
  6. 解决pl/sq可视化工具的中文乱码问题
  7. vista风格的cms企业html后台管理系统模板——后台
  8. 深入理解Spring系列之十二:@Transactional是如何工作的
  9. Linux下libevent安装与示例
  10. 日常开发技巧:x11-forward,使用远程机器的gui程序