一、Tornado概述
Python的Web框架种类繁多(比Python语言的关键字还要多),但在众多优秀的Web框架中,Tornado框架最适合用来开发需要处理长连接和应对高并发的Web应用。Tornado框架在设计之初就考虑到性能问题,它可以处理大量的并发连接,更轻松的应对C10K(万级并发)问题,是非常理想的实时通信Web框架。
Tornado框架源于FriendFeed网站,在FriendFeed网站被Facebook收购之后得以开源,正式发布的日期是2009年9月10日。Tornado能让你能够快速开发高速的Web应用,如果你想编写一个可扩展的社交应用、实时分析引擎,或RESTful API,那么Tornado框架就是很好的选择。Tornado其实不仅仅是一个Web开发的框架,它还是一个高性能的事件驱动网络访问引擎,内置了高性能的HTTP服务器和客户端(支持同步和异步请求),同时还对WebSocket提供了完美的支持。
了解和学习Tornado最好的资料就是它的官方文档,在tornadoweb.org上面有很多不错的例子,你也可以在Github上找到Tornado的源代码和历史版本。
二、tornado项目流程
2.1 tornado的web与ioloop模块
import time
import tornado.web # tornado的基础web框架模块
import tornado.ioloop
# tornado的核心IO循环模块,封装了Linux的epoll和BSD的kqueue,
# 是tornado高效的基础
# 类比Django中的视图
# 一个业务处理类
class IndexHandler(tornado.web.RequestHandler):
def get(self, *args, **kwargs): # 处理get请求的,不能处理post请求
self.write("hello world")
if __name__ == '__main__':
# 实例化一个app对象
# Application:是tornado web框架的核心应用类,是与服务器对应的接口
# 里面保存了路由映射表,有一个listen方法用来创建一个http服务器的实例,并绑定了端口
app = tornado.web.Application([
(r'/', IndexHandler)
])
app.listen(8000)
'''
IOLoop.current():返回当前线程的IOLoop实例
IOLoop.start():启动IOLoop实例的I/O循环,同时开启了监听
'''
tornado.ioloop.IOLoop.current().start()
2.2 tornado高效原理图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qu29ubMz-1574609108198)(C:\Users\Administrator\Desktop\5.web后端\自己创建项目\mytornado\img\tornado高效原理图.png)]
2.3 tornado httpserver模块与多进程
import tornado.web # tornado的基础web框架模块
import tornado.ioloop
import tornado.httpserver # 导入httpserver模块
class IndexHandler(tornado.web.RequestHandler):
def get(self, *args, **kwargs): # 处理get请求的,不能处理post请求
self.write("hello world")
if __name__ == '__main__':
app = tornado.web.Application([
(r'/', IndexHandler)
])
# 1.可以直接绑定端口并启动服务,只能在单进程模式中使用
# 觉得不直观,应该创建一个httpserver
# app.listen(8000)
# 2.实例化一个http服务器对象,单进程
# httpServer = tornado.httpserver.HTTPServer(app)
# httpServer.listen(8000) # listen()只能在单进程模式中使用
# 3.实例化一个http服务器对象,多进程,需用bind()指定端口
# 下面这种方式不能在windows运行
httpServer = tornado.httpserver.HTTPServer(app)
httpServer.bind(8000)
'''
httpServer.start()
# 默认开启1个进程
# 若值大于0,创建对应个数个子进程
# 值为None或者小于等于0,开启对应硬件机器的cup核心数个子进程
这样启动多进程的问题:
1.每个子进程都会从父进程中复制一份IOLoop的实例,
如果在创建子进程前修改了IOLoop,会影响所有的子进程
2.所有的进程都是由一个命令启动的,无法做到在不停止服务的情况下修改代码
3.所有进程共享一个端口,想要分别监控很困难
重点:可以手动开启多个cmd开启多进程解决上述问题,所以一般使用单进程即可
'''
httpServer.start()
tornado.ioloop.IOLoop.current().start()
2.4 tornado options模块
import time
import tornado.web # tornado的基础web框架模块
import tornado.ioloop
import tornado.options
'''
tornado.options
作用: 全局参数的定义、存储、转换
基本的方法与属性:
1.define()
原型:tornado.options.define(name,default=None,type=None,help=None,
metavar=None,multiple=False,group=None,callback=None)
作用:用来定义options选项变量的方法
参数:
name:选项变量名,必须保证其唯一性,否则会报"Option 'xxx' already define in ..."
default:设置选项变量的默认值,默认为None
type:
1.设置选项变量的类型,从命令行或配置文件导入参数时tornado会根据类型转换输入的值,
转换不成会报错。可以str,float,int,datetime,timedelta
2.如果没有设置type,会根据default的值进行转换
3.如果default没有设置,那么不进行转换
multiple:设置选项变量是否可以为多个值,默认为False
2.tornado.options.options
全局的options对象,所有定义的选项变量都会作为该对象的属性
3.tornado.options.parse_command_line()
转换命令行参数,并保存到tornado.options.options中
# 那么启动服务可以使用命令: Python xxx.py --port=9000
4.tornado.options.parse_config_file(path)
定义一个文本config
从配置文件导入参数,并保存到tornado.options.options中
5.将参数写入一个py文件里
导入定义的py模块 import xxx.py
使用文件名.xxx调用
如:config.options.port
在代码中使用parse_command_line()或者parse_config_file(path)方法时,
tornado会默认开启logging模块功能,向屏幕终端输入一些打印信息
关闭日志的方式:
命令行: python server04.py --port=9087 --logging=none
在第一行加入: tornado.options.options.logging = None
'''
import config
tornado.options.define('port', default=8000, type=int)
# tornado.options.define("list",default=[], type=str)
class IndexHandler(tornado.web.RequestHandler):
def get(self, *args, **kwargs): # 处理get请求的,不能处理post请求
print(tornado.options.options.list) # 获取自己定义的参数
# print(config.options.get('list')) # 获取自己定义的参数
self.write("hello world")
if __name__ == '__main__':
# tornado.options.parse_command_line() # 转换命令行参数
# tornado.options.parse_config_file("config")
app = tornado.web.Application([
(r'/', IndexHandler)
])
# app.listen(config.options.get('port'))
app.listen(tornado.options.options.port)
tornado.ioloop.IOLoop.current().start()
2.5 tornado项目拆分
'''
static
---js、css、image
templates
upfile
App
-- models.py
-- views.py
config.py
application.py
server.py
'''
'''
说明:
static、templates、upfile --静态文件、模板、上传文件
App --类视图函数、模型定义目录
config.py -- 参数配置文件: 如端口、模式、静态目录与模板路径等等
application.py --tornado app应用创建 其引用类视图与相关参数
server.py -- tornado httpserver创建 、 IO循环启动
'''
2.5.1 App-views.py类视图
import tornado.web
class IndexHandler(tornado.web.RequestHandler):
def get(self, *args, **kwargs):
self.write('项目拆分')
2.5.2 config.py 参数配置
import os
BASE_DIRS = os.path.dirname(__file__)
options = {
"port": 8000
}
settings = {
"static_path": os.path.join(BASE_DIRS, 'static'), # 静态目录
"template_path": os.path.join(BASE_DIRS, "templates"), # 模板路径
"debug": True
}
2.5.3 application.py应用创建
import tornado.web
from tornado.web import url
from views import index
import config
class Application(tornado.web.Application):
def __init__(self):
handlers = [ # 路由匹配
url(r'/', index.IndexHandler),
]
super().__init__(handlers, **config.settings)
2.5.4 httpserver创建与IOLoop循环启动
import tornado.web
import tornado.ioloop
from application import Application
import config
if __name__ == '__main__':
app = Application()
app.listen(config.options.get('port'))
tornado.ioloop.IOLoop.current().start()
2.6 config.py相关参数解析
import os
BASE_DIRS = os.path.dirname(__file__)
options = {
"port": 8000
}
settings = {
"static_path": os.path.join(BASE_DIRS, 'static'), # 静态目录
"template_path": os.path.join(BASE_DIRS, "templates"), # 模板路径
"debug": True
}
'''
1.debug
作用: 设置tornado是否工作在调试模式下,默认为False即工作在生产模式下。
设置为True的特性:
(1).自动重启
tornado应用会监控源代码文件,当有保存改动时便会重新启动服务器,
可以减少手动重启的次数,提高开发效率。
如果保存后代码有错误会导致重启失败,修改错误后需要手动重启
可以通过autoreload = True设置
(2).取消缓存编译的模板 : 可以通过compiled_template_cache = False单独设置
(3).取消缓存静态文件的hash值: 可以通过static_hash_cache = False单独设置
2.static_path: 设置静态文件目录
3.template_path: 设置模板文件目录
4.cookie_secret: 配置安全cookie秘钥
5.xsrf_cookies: 当为True开启XSRF保护
6.login_url: 用户验证失败会映射该路由
'''
三 、tornado路由解析
3.1 路由内部映射
'''
application.py
'''
import tornado.web
from tornado.web import url
from views import index
import config
class Application(tornado.web.Application):
def __init__(self):
handlers = [
url(r'/', index.IndexHandler, name='index'),
url(r'/params', index.ParamHandler, {'word1':"你好", 'word2':'大家好'}),
url(r'/path/(\w+)/(\w+)', index.PathHandler),
url(r'/path2/(?P<p2>\w+)/(?P<p1>\w+)', index.Path2Handler),
url(r'/redict', index.RedirectHandler),
url(r'/reverse', index.RereverHandler),
]
super().__init__(handlers, **config.settings)
'''
index.py
'''
import tornado.web
class IndexHandler(tornado.web.RequestHandler):
def get(self, *args, **kwargs):
self.write('项目拆分')
# 路由内部传参
# url(r'/params', index.ParamHandler, {'word1':"你好", 'word2':'大家好'}),
class ParamHandler(tornado.web.RequestHandler):
def initialize(self, word1, word2): # 该方法会在HTTP方法之前调用
self.word1 = word1
self.word2 = word2
def get(self, *args, **kwargs):
self.write(self.word1 + self.word2)
# path路径参数
# url(r'/path/(\w+)/(\w+)', index.PathHandler),
class PathHandler(tornado.web.RequestHandler):
def get(self, p1, p2, *args, **kwargs):
self.write(p1 + p2)
# 指定path路径参数对应哪个参数
# url(r'/path2/(?P<p2>\w+)/(?P<p1>\w+)', index.Path2Handler),
class Path2Handler(tornado.web.RequestHandler):
def get(self, p1, p2, *args, **kwargs):
self.write(p1 + p2)
#重定向
class RedirectHandler(tornado.web.RequestHandler):
def get(self, *args, **kwargs):
self.redirect("/")
#重定向+反向解析
class RereverHandler(tornado.web.RequestHandler):
def get(self, *args, **kwargs):
self.redirect(self.reverse_url('index'))
3.2 其他路由设置
'''
模板中使用的资源路由
引入css/js:
<link rel="stylesheet" href="/static/hello.css"> # 相对路径
<link rel="stylesheet" href="{{ static_url('hello.css') }}"> # 反向解析
链接跳转:
<a href="/login/">登录</a> # 静态路由
<a href="{{ reverse_url('login') }}">登录</a> # 反向解析路由
'''
四、tornado 请求解析
4.1 get与post 参数获取
'''
1. get方式传递参数
127.0.0.1:8000/zhangmanyu?a=1&b=2&b=3
# 获取单个参数, 如input输入框
self.get_query_argument(name,default=ARG_DEFAULT, strip=True)
参数:
name:从get请求参数字符串中返回指定参数的值,如果出现过个同名参数,返回最后一个值
default:设置未传的name参数时返回默认的值,如果default也没有设置,
会抛出tornado.web.MissingArgumentError异常
strip:表示是否过滤掉左右两边的空白字符,默认为True过滤
返回:字符串
# 获取参数的多个值,返回列表 如复选框
# self.get_query_arguments(name,strip=True)
'''
import tornado.web
class IndexHandler(tornado.web.RequestHandler):
def get(self, *args, **kwargs):
print(self.get_query_argument('name'))
self.write('get请求成功')
'''
2. post方式传递参数
self.get_body_argument(name,default=ARG_DEFAULT,strip=True) # 获取单个参数值
self.get_body_arguments(name,strip=True) # 获取参数的多个值
可以使用requests库请求:
requests.post('http://127.0.0.1:8000/', data={"name":"lisi7"})
'''
import tornado.web
class IndexHandler(tornado.web.RequestHandler):
def post(self):
print(self.get_body_argument('name'))
self.write('post请求成功')
'''
3.即可以获取get请求,也可以获取post请求
self.get_argument(name,default=ARG_DEFAULT,strip=True)
self.get_arguments(name,strip=True)
'''
4.2 request请求对象
'''
相关属性
'''
import tornado.web
class IndexHandler(tornado.web.RequestHandler):
def get(self, *args, **kwargs):
print(self.request.method) # HTTP请求的方式
print(self.request.host) # 被请求的主机名和端口
print(self.request.uri) # 请求的完整资源地址,包括路径和get查询参数部分
print(self.request.path) # 请求的路径部分
print(self.request.query) # 请求参数部分
print(self.request.version) # 使用的HTTP版本
print(self.request.headers) # 请求的协议头,是一个字典类型
print(self.request.body) # 请求体数据
print(self.request.remote_ip) # 客户端的ip
print(self.request.files)
# 用户上传的文件,字典类型
{
'file': [
{
'filename': 'a.txt',
'body': b'sunck is a good man',
'content_type': 'text/plain'
}
]
}
self.write('get请求成功')
4.3 文件上传
# upfile.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>上传文件</title>
</head>
<body>
<form method="post" action="/upfile" enctype="multipart/form-data">
<input type="file" name="file"/>
<input type="file" name="file"/>
<input type="file" name="img"/>
<input type="submit" value="上传"/>
</form>
</body>
</html>
# 文件上传
class UpFileHandler(tornado.web.RequestHandler):
def get(self, *args, **kwargs):
self.render('upfile.html')
def post(self, *args, **kwargs):
filesDict = self.request.files
for inputname in filesDict: # 文件名字可能不一样
fileArr = filesDict[inputname]
for fileObj in fileArr:
# 存储路径
filePath = os.path.join(config.BASE_DIRS, 'upfile/' + fileObj.filename)
with open(filePath, "wb") as f:
f.write(fileObj.body)
self.write("ok")
五、tornado 服务器响应
5.1 返回响应内容
'''
1. 字符串响应
self.write(chunk)
将chunk数据写到输出缓冲区
self.finish()
#刷新缓冲区,关闭当次请求通道
#在finish下边就不要在write
'''
import tornado.web
class ResponHandler(tornado.web.RequestHandler):
def get(self):
self.write('i ')
self.write('love china')
self.finish()
'''
2. json数据响应
采用write自动序列化方式,Content-Type属性为application/json
自己手动序列化Json方式Content-Type属性值为text/html,
'''
import tornado.web
class JsonHandler(tornado.web.RequestHandler):
def get(self):
data = {
'code': 1000,
'status': 'success',
'data': ['hello world']
}
self.write(data)
'''
3. 模板html渲染并响应
self.render('index.html', **data) # 要展开使用关键字参数
'''
import tornado.web
class HtmlHandler(tornado.web.RequestHandler):
def get(self):
data = {
'name': 'zhangsan'
}
self.render('index.html', **data)
'''
4. 响应服务器内部错误
self.send_error()
抛出HTTP错误状态码,默认为500,抛出错误后tornado会调用
wirte_error()方法进行处理,并返回给浏览器错误界面
'''
import tornado.web
class HtmlHandler(tornado.web.RequestHandler):
def get(self):
data = {
'name': 'zhangsan'
}
self.send_error()
def wirte_error(status_code, **kwargs)
pass # 可以不重写
5.2 响应头设置
'''
1. 设置响应头内容
self.set_header()
手动设置一个名为name,值为value的响应头字段
'''
import json
import tornado.web
class HeaderHandler(tornado.web.RequestHandler):
def get(self):
self.set_header("Content-Type", "application/json; charset=UTF-8")
self.set_header("hh", "good")
data = {
'name': 'zhangsan'
}
self.write(json.dumps(data))
'''
2. 设置响应状态码
self.set_status(status_code, reason=None)
参数:
status_code:状态码值,为int类型。
reason:描述状态码的词组,string类型
'''
import json
import tornado.web
class HeaderHandler(tornado.web.RequestHandler):
def get(self):
self.set_header("Content-Type", "application/json; charset=UTF-8")
self.set_header("hh", "good")
self.set_status(999, "假的状态码")
data = {
'name': 'zhangsan'
}
self.write(json.dumps(data))
5.3 RequestHandler请求执行的方法
'''
1、RequestHandler类中常用的方法:
1、set_default_headers() => 在进入HTTP响应处理方法之前被调用,可以重写该方法来预先设置默认的headers
2、initialize() => 该方法会在prepare方法之前调用
3、prepare() => 预处理方法,在执行对应的请求方法之前调用
4、HTTP方法,如get()、post()
5、write_error() => 用来处理send_error抛出的错误信息,并返回给浏览器错误界面
6、on_finish() => 在请求处理结束后调用。在该方法中进行资源清理释放,或者是日志处理
'''
'''
2、在正常情况未抛出错误时
set_default_headers() =>
initialize() =>
prepare() =>
HTTP方法 =>
on_finish()
'''
'''
3、在抛出错误时
set_default_headers() =>
initialize() =>
prepare() =>
HTTP方法 =>
set_default_headers() =>
write_error() =>
on_finish()
'''
六、tornado模板
'''
1. 变量与表达式
'''
{{ 变量名 }}
{{ data['status'] }} # 字典获取
'''
2.流程控制
'''
if标签:
{% if 条件%} {% end %}
{% if 条件 %}
{% elif 条件%}
{% else %}
{% end %}
for标签:
{% for 变量 in [ ] %} {% end %}
'''
3.转义
tornado默认开启了自动转义功能,能防止网站受到恶意攻击
'''
# 关闭自动转义的方式
# 第一种:只能关闭一行
{% raw str %}
# 第二种:在页面模板中修改, 关闭当前文档的自动转义
{% autoescape None %}
{{str}}
# 第三种:在配置中修改,关闭当前项目的自动转义
"autoescape": None
# 特别地: 在关闭自动转义后,可以使用escape()方法对特定的变量进行转义
{% autoescape None %}
{{escape(str)}}
{{str}}
'''
4.模板与模板继承
'''
# 模板定义
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{title}}</title>
</head>
<body>
{% block main %}
{% end %}
</body>
</html>
# 模板继承
{% extends "base.html" %}
{% block main %}
<h1>这里是购物车页面</h1>
{% end %}
'''
5.静态文件路径配置
作用:告诉tornado从文件系统中的某一个特定的位置提供静态文件
config.py
'''
"static_path": os.path.join(BASE_DIRS, "static")
'''
6.StaticFileHandler提供静态资源文件的handler
有时候需要将一些html页面放到static目录直接访问
但是通过:因为http://127.0.0.1:8000/static/html/index.html对于用户来说体验不佳
解决:可以通过tornado.web.StaticFileHandler来映射静态文件
在application.py配置
最好在路由位置的最下面使用,否则其他的路由不能匹配
path:用来指定提供静态文件的根路径
default_filename: 用来指定访问路由中未指明文件名时,默认提供的静态文件
'''
# http://127.0.0.1:8000/index.html
(r'/(.*)$', tornado.web.StaticFileHandler,{"path":os.path.join(config.BASE_DIRS,"static/html")})
# http://127.0.0.1:8000/
(r'/(.*)$', tornado.web.StaticFileHandler, {"path":os.path.join(config.BASE_DIRS,"static/html"), "default_filename":"index.html"})
七、tornado 模型
7.1 使用sqlarchemy
'''
App
--utils.py
数据库引擎、模型基类、session
'''
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from config import database
# 1.配置mysql数据库连接
# DB_URI = "mysql+pymysql://root:root@localhost:3306/tornadodb"
DB_URI = "mysql+pymysql://{}:{}@{}:{}/{}".format(
database['USER'],
database['PASSWORD'],
database['HOST'],
database['PORT'],
database['DBNAME'],
)
# 2.创建数据库引擎对象
engine = create_engine(DB_URI)
# 3.创建模型基类,给model.py使用
Base = declarative_base(bind=engine)
# 4.创建session, 给views.py操作模型(增删改查crud)
DbSession = sessionmaker(bind=engine)
session = DbSession()
7.2 模型创建
'''
App
--models.py
'''
from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey
from App.utils import Base
# 模型
class User(Base):
__tablename__ = 'user' # 表名
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(50), unique=True)
age = Column(Integer, default=18)
sex = Column(Boolean, default=True)
created = Column(DateTime, default='2019-08-27')
# 执行该文件创建数据库表
if __name__ == '__main__':
Base.metadata.create_all()
7.3 模型操作
'''
App
--views.py
'''
import tornado.web
from App.models import User
from App.utils import session
class IndexHandler(tornado.web.RequestHandler):
def get(self):
# user = session.query(User).get(1)
# print(user, type(user)) # 对象
# all(): 得到所有数据
# users = session.query(User).all()
# print(users, type(users)) # 对象列表
# filter、filter_by
# users = session.query(User).filter(User.age==22)
# print(users, type(users)) # 查询集
# users = session.query(User).filter_by(age=22)
# print(users, type(users)) # 查询集
users = session.query(User).filter(User.age > 22)
print(users.all(), type(users)) # 查询集
self.write('ok')
# 新增
def post(self):
# user = User()
# user.name = '成龙'
# user.age = 59
#
# session.add(user)
# session.commit()
#
# self.write('ok')
# 添加多条
users =[]
for i in range(10):
user = User()
user.name = "总账引擎" + str(i)
user.age = 20 + i
users.append(user)
try:
session.add_all(users)
session.commit()
except:
session.rollback()
session.flush()
self.write('fail')
else:
self.write('ok')
# 修改
def put(self):
# filter: 批量修改
# session.query(User).filter(User.age > 26).update({'age': 18})
# session.commit()
# 修改对象
user = session.query(User).get(1)
user.age = 65
session.commit()
self.write('ok')
def delete(self):
# filter: 批量删除
session.query(User).filter(User.age < 20).delete()
session.commit()
# 删除对象
user = session.query(User).get(1)
session.delete(user)
session.commit()
self.write('ok')
八、tornado 应用安全
8.1 普通cookie
import random
import tornado.web
class IndexHandler(tornado.web.RequestHandler):
def get(self, *args, **kwargs):
'''
# 设置cookie
# 设置cookie实际上是通过设置header的Set-Cookie来实现的
# self.set_header("Set-Cookie","kaige=nice; Path=/")
原型:self.set_cookie(name,value,domain=None,expires=None,
path="/",expires_days=None,**kwargs)
参数:
name => cookie名
value => cookie值
domain => 提交cookie时匹配的域名
path => 提交cookie时匹配的路径
expires => cookie的有效期,可以是时间戳整数、时间元组、datetime类型。为UTC时间
expires_days => cookie的有效期天数。优先级低于expires
# 获取cookie
原型: self.get_cookie(name, default=None)
参数:
name: 要获取的cookie的名称
default: 如果名为name的cookie不存在,则返回default的值
# 删除或清除cookie
self.clear_cookie(name,path="/",domain=None)
=> 删除名为name,并同时匹配domain和path的cookie
self.clear_all_cookies(path="/",domain=None)
=> 删除同时匹配path和domain的所有cookie
执行清除cookie操作后,并不是立即删除浏览器端的cookie,
而是给cookie值设置空,并改变其有限期限为失效。
真正删除cookie是由浏览器自己去清理的
'''
print(self.get_cookie('id', default=None))
self.set_cookie('id', str(random.randint(1000, 10000)))
self.render('index.html')
8.2 相对安全的cookie
'''
1.首先要在config.py
设置cookie_secret
可以使用下面方式获取:
import base64
import uuid
base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes)
'''
settings = {
"static_path": os.path.join(BASE_DIRS, 'static'), # 静态目录
"template_path": os.path.join(BASE_DIRS, "templates"), # 模板路径
"debug": False,
"cookie_secret": "t63VyFj+T4uz+OwspKnQv50hXM9+skLTh1s1Tbaqa5Q="
}
'''
2. 使用secure_cookie
views.py
'''
class SecurityHandler(tornado.web.RequestHandler):
def get(self, *args, **kwargs):
'''
安全cookie设置:
原型: self.set_secure_cookie(name,value,expires_days=30,
version=None,**kwargs)
作用:设置一个带有签名和时间戳的cookie,防止cookie被伪造
安全cookie获取:
原型: self.get_secure_cookie(name,value=None,
max_age_days=31,min_version=None)
如果cookie存在且验证通过,返回cookie值,否则返回None
max_age_days不同于expires_days,expires_days设置浏览器中cookie的有效时间。
而max_age_days是过滤安全cookie的时间戳
'''
print(self.get_secure_cookie('name').decode())
self.set_secure_cookie('name', 'zhangsan')
self.render('index.html')
8.3 XSRF跨站请求处理
'''
1.开启xsrf认证
config.py
'''
settings = {
"static_path": os.path.join(BASE_DIRS, 'static'), # 静态目录
"template_path": os.path.join(BASE_DIRS, "templates"), # 模板路径
"debug": False,
"cookie_secret": "t63VyFj+T4uz+OwspKnQv50hXM9+skLTh1s1Tbaqa5Q=",
"xsrf_cookies": True
}
'''
2.模板设置:
'''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>cookie</title>
</head>
<body>
<form action="" method="post" >
{% module xsrf_form_html() %}
用户名:<input type="text" name="username">
密码: <input type="password" name="password">
<input type="submit">
</form>
</body>
</html>
8.4 用户登录认证
'''
项目中有些接口需要登录才能访问获取数据,
tornado 提供tornado.web.authenticated 装饰器修饰需要登录
才能访问的接口
如何判断是否登录?
访问经过tornado.web.authenticated修饰的函数时会执行get_current_user()函数
如果此函数返回True,则证明已经登录, 否则未登录,未登录会跳转到config.py配置的登录url
settings = {
"static_path": os.path.join(BASE_DIRS, 'static'), # 静态目录
"template_path": os.path.join(BASE_DIRS, "templates"), # 模板路径
"debug": False,
"cookie_secret": "t63VyFj+T4uz+OwspKnQv50hXM9+skLTh1s1Tbaqa5Q=",
"xsrf_cookies": True,
"login_url":"/login"
}
'''
#用户验证
class LoginHandler(tornado.web.RequestHandler):
def get(self, *args, **kwargs):
next = self.get_argument("next", "/")
url = "login?next=" + next
self.render("login.html", url=url)
def post(self, *args, **kwargs):
name = self.get_argument("username")
pawd = self.get_argument("passwd")
if name == "1" and pawd == "1":
next = self.get_argument("next", "/")
self.set_secure_cookie('userid', '111')
self.redirect(next)
else:
next = self.get_argument("next", "/")
print("next = ", next)
self.redirect("/login?next="+next)
class HomeHandler(tornado.web.RequestHandler):
def get_current_user(self):
print(self.get_secure_cookie('userid'))
if self.get_secure_cookie('userid'):
return True
else:
return False
@tornado.web.authenticated
def get(self, *args, **kwargs):
self.render("index.html")
九、tornado异步
9.1 同步请求 + ab 测试
'''
注意tornado版本:
高版本不支持同步请求
'''
import tornado.web
import tornado.httpclient
class IndexHandler(tornado.web.RequestHandler):
def get(self, *args, **kwargs):
wd = self.get_argument('wd')
client = tornado.httpclient.HTTPClient()
response = client.fetch('http://www.baidu.com/s?wd=%s' % wd)
self.write('ttt')
'''
> ab -c 3 -n 10 http://127.0.0.1/?wd=python
ab的命令参数比较多,我们经常使用的是-c和-n参数。
示例:
ab -c 10 -n 100 http://www.baidu.com/
其中:
-c 10 表示并发用户数为10
-n 100 表示请求总数为100
http://www.baidu.com 表示请求的目标URL
'''
9.2 异步请求 + ab 测试
'''
注意tornado版本:
高版本不支持asynchronous
'''
class AsyncHandler(tornado.web.RequestHandler):
@tornado.web.asynchronous # 不关闭通信的通道
def get(self, *args, **kwargs):
wd = self.get_argument('wd')
client = tornado.httpclient.AsyncHTTPClient()
client.fetch('http://www.baidu.com/s?wd=%s' % wd, callback=self.on_response)
self.write('异步测试')
def on_response(self, response):
print(response)
self.write('回调执行')
self.finish()
'''
> ab -c 3 -n 10 http://127.0.0.1/?wd=python
ab的命令参数比较多,我们经常使用的是-c和-n参数。
示例:
ab -c 10 -n 100 http://www.baidu.com/
其中:
-c 10 表示并发用户数为10
-n 100 表示请求总数为100
http://www.baidu.com 表示请求的目标URL
'''
十、tornado websocket
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
'''
注意:websocket继承
tornado.websocket.WebSocketHandler
'''
import tornado.web
import tornado.websocket
# 首页
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.render('index.html')
# 登录
class LoginHandler(tornado.web.RequestHandler):
def get(self):
self.render('login.html')
def post(self):
username = self.get_body_argument('username')
password = self.get_body_argument('password')
if username in ['fanbinbin', 'lichen', 'chenlong', 'honjinbao'] and password=='123':
# 登录成功
self.set_secure_cookie('username', username, expires_days=1) # 设置cookie
self.redirect(self.reverse_url('chat')) # 跳转到聊天室页面
else:
self.write('登录失败!')
# 聊天室界面
class ChatHandler(tornado.web.RequestHandler):
def get(self):
# 获取当前登录的用户,并传入模板显示
username = self.get_secure_cookie('username').decode()
self.render('chat.html', username=username)
# 聊天室功能:服务器端
class ChatRoomHandler(tornado.websocket.WebSocketHandler):
# 存放所有聊天室的用户连接
online_users = []
# 开启:有客户端连接时会被自动调用
def open(self, *args, **kwargs):
print('open')
# 当有新客户端连接时(说明该用户进入聊天室),则加入到列表online_users中
self.online_users.append(self)
# 将当前用户进入聊天室的消息群发
username = self.get_secure_cookie('username').decode()
for user in self.online_users:
user.write_message('[%s]进入了聊天室' % username)
# 接收到客户端消息时会被调用
def on_message(self, message):
print('on_message:', message)
# 群发: 发送给聊天室的所有人
username = self.get_secure_cookie('username').decode()
for user in self.online_users:
user.write_message('【%s】:%s' % (username, message))
# 关闭:当连接断开时被调用
def on_close(self):
print('on_close')
# 将当前用户从online_users中移出
self.online_users.remove(self)
'''
路由
'''
class Application(tornado.web.Application):
def __init__(self):
handlers = [
url(r'/', views.IndexHandler, name='index'),
url(r'/login/', views.LoginHandler, name='login'),
url(r'/chat/', views.ChatHandler, name='chat'),
url(r'/chatroom/', views.ChatRoomHandler, name='chatroom'),
]
super().__init__(handlers, **config.settings)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>聊天室</h2>
<hr>
<p>当前用户: {{ username }}</p>
<div style="width: 500px; height: 400px; border: 1px solid gray;">
<div id="content">
</div>
</div>
<input id="msg" type="text">
<input id="send" type="button" value="发送">
<script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script>
<script>
// websocket
// 创建websocket对象: 主动连接websocket服务器
let ws = new WebSocket('ws://127.0.0.1:8000/chatroom/');
// 发送:发送消息给服务器
$('#send').click(function () {
let message = $('#msg').val(); // 要发送的内容
ws.send(message); // 发送消息给服务器
$('#msg').val(''); // 清空输入框
});
// 接收:接收服务端发过来的消息
ws.onmessage = function (e) {
// console.log('onmessage:', e.data)
// 显示消息
$('#content').append('<p>'+ e.data +'</p>');
};
// 连接成功后自动调用
ws.onopen = function (e) {
console.log('onopen')
};
// 报错时调用
ws.onerror = function (e) {
console.log('onerror')
};
</script>
</body>
</html>
r’/chatroom/’, views.ChatRoomHandler, name=‘chatroom’),
]
super().init(handlers, **config.settings)
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>聊天室</h2>
<hr>
<p>当前用户: {{ username }}</p>
<div style="width: 500px; height: 400px; border: 1px solid gray;">
<div id="content">
</div>
</div>
<input id="msg" type="text">
<input id="send" type="button" value="发送">
<script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script>
<script>
// websocket
// 创建websocket对象: 主动连接websocket服务器
let ws = new WebSocket('ws://127.0.0.1:8000/chatroom/');
// 发送:发送消息给服务器
$('#send').click(function () {
let message = $('#msg').val(); // 要发送的内容
ws.send(message); // 发送消息给服务器
$('#msg').val(''); // 清空输入框
});
// 接收:接收服务端发过来的消息
ws.onmessage = function (e) {
// console.log('onmessage:', e.data)
// 显示消息
$('#content').append('<p>'+ e.data +'</p>');
};
// 连接成功后自动调用
ws.onopen = function (e) {
console.log('onopen')
};
// 报错时调用
ws.onerror = function (e) {
console.log('onerror')
};
</script>
</body>
</html>