tornado知识点

一、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>
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值