python 全栈开发,Day139(websocket原理,flask之请求上下文)

昨日内容回顾

flask和django对比

flask和django本质是一样的,都是web框架。

但是django自带了一些组件,flask虽然自带的组件比较少,但是它有很多的第三方插件。

那么在什么情况下,使用flask呢?

比如让flask写一个大型项目,它需要很多第三方插件。
那么堆着堆着,就和django一样了!

总结:

如果一个项目需要的插件比较少,可以使用flask。
如果需要的插件比较多,使用django更加方便。

flask知识点

装饰器

在flask中,装饰器用的是比较多的。看下面一段代码

from flask import Flask

app = Flask(__name__)

@app.route('/index')
def index():
    return 'index'

if __name__ == '__main__':
    app.run()

现在有一个装饰器函数xxx,如果需要在每次请求index页面时,做一些操作。

那么装饰器,应该加在哪里呢?

这样?

@xxx
@app.route('/index')

还是这样呢?

@app.route('/index')
@xxx

答案是,必须在@app.route('/index')下面才行。为什么呢?

因为如果加在@app.route上面,那么执行@xxx之后,那么就直接走视图函数了。已经没有意义了!

而如果在@app.route下面,那么执行到路由后,就会先执行@xxx,再执行视图函数!

装饰器的顺序

看下面一段代码,index视图函数,加了一个装饰器xxxx

from flask import Flask

app = Flask(__name__)

def xxxx(func):
    def inner(*args,**kwargs):
        print('before')
        return func(*args,**kwargs)

    return inner

@app.route('/index')
@xxxx
def index():
    return 'index'

if __name__ == '__main__':
    app.run()
View Code

启动程序,访问首页

http://127.0.0.1:5000/index

查看Pycharm控制台输出:  before

如果再加视图函数home,并应用xxxx装饰器

from flask import Flask

app = Flask(__name__)

def xxxx(func):
    def inner(*args,**kwargs):
        print('before')
        return func(*args,**kwargs)

    return inner

@app.route('/index')
@xxxx
def index():
    return 'index'

@app.route('/home')
@xxxx
def home():
    return 'home'

if __name__ == '__main__':
    app.run()
View Code

启动之后,会直接报错

AssertionError: View function mapping is overwriting an existing endpoint function: inner

为什么呢?由于代码是从上至下执行的。视图函数执行xxxx装饰器之后,使用__name__方法获取函数名时,名字是inner

那么因此执行到home时,函数名也是inner。那么flask就会抛出异常,inner函数重复了!

如何解决呢?使用functools就可以了!它会保留原函数信息,包括函数名!

from flask import Flask
import functools

app = Flask(__name__)

def xxxx(func):
    @functools.wraps(func)
    def inner(*args,**kwargs):
        print('before')
        return func(*args,**kwargs)

    return inner

@app.route('/index')
@xxxx
def index():
    return 'index'

@app.route('/home')
@xxxx
def home():
    return 'home'

if __name__ == '__main__':
    app.run()
View Code

再次执行,就不会报错了。

因此,以后为了装饰器不出问题,一定要加functools

before_request/after_request

看下面的代码,b1和b2谁会先执行?

# import pymysql
# from DBUtils.PooledDB import PooledDB, SharedDBConnection
# POOL = PooledDB(
#     creator=pymysql,  # 使用链接数据库的模块
#     maxconnections=6,  # 连接池允许的最大连接数,0和None表示不限制连接数
#     mincached=2,  # 初始化时,链接池中至少创建的空闲的链接,0表示不创建
#     maxcached=5,  # 链接池中最多闲置的链接,0和None不限制
#     maxshared=3,  # 链接池中最多共享的链接数量,0和None表示全部共享。PS: 无用,因为pymysql和MySQLdb等模块的 threadsafety都为1,所有值无论设置为多少,_maxcached永远为0,所以永远是所有链接都共享。
#     blocking=True,  # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错
#     maxusage=None,  # 一个链接最多被重复使用的次数,None表示无限制
#     setsession=[],  # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]
#     ping=0,
#     # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always
#     host='127.0.0.1',
#     port=3306,
#     user='root',
#     password='123',
#     database='pooldb',
#     charset='utf8'
# )
'''
1.Flask路由
    1.endpoint="user" # 反向url地址
    2.url_address = url_for("user")
    3.methods = ["GET","POST"] # 允许请求进入视图函数的方式
    4.redirect_to # 在进入视图函数之前重定向
    5./index/<nid> # 动态参数路由 <int:nid> def index(nid)
    6.strict_slashes # 是否严格要求路由地址 /
    7.defaults={"nid":1} # def index(nid)

2.Flask初始化配置(实例化):
    1.template_folder # 指定模板路径
    2.static_url_path # 指定静态文件目录的URL地址
    3.static_folder # 指定静态文件目录路径

3.Flask对象配置
    1.DEBUG #开发模式的调试功能 True False
    2.app.config.from_object(class) # 通过对象的方式导入配置
    3.secret_key # 开启session功能的时候需要添加的配置

4.Blueprint
    1.将功能和主程序分离,注册
    2.bl = Blueprint("dongdong",__name__)
    3.注册 register_blueprint(bl)

5.send_file jsonify
    1.send_file # 打开并返回文件 content-type:文件类型
    2.jsonify # 将一个字符串 转为JSON格式 加入 content-type:application/json 头

6.特殊的装饰器:
    1.before_request # 在请求进入视图函数之前执行的函数(登录认证)
    2.after_request # 在请求响应回浏览器之前执行的函数
    3.before_first_request # 在第一次请求进入视图函数之前执行的函数
    4.errorheader(404) # 当遇到此类错误响应的时候(自定义错误页面)

7.flash
    1.flash("msg","tag") # 闪现存储
    2.get_flashed_messages(category_filter=["tag"]) # 闪现取值
    只要用到了get_flashed_messages就一定清空flash



1.DButils 数据库连接池
    创建连接池同时创建连接
    用到连接时从连接池中抽取一个连接
    释放连接时将连接放回连接池中
    节省与mysql的通讯次数和时长

2.Websocket 通讯协议

 Web + socket
 QQ 即时通讯软件 97

 初期轮询:
    QQ 联众 软件不断的循环访问服务器问它有没有给我发送的消息
    优点:响应及时
    缺点:浪费CPU资源,浪费带宽

 长轮询:
    当客户端发起询问,服务器说你等着1分钟之后,你再来问我
    断开再次发起连接,服务器帮你轮询
    优点:响应及时
    缺点:用户一旦形成规模,服务器消耗是致命的

 新的协议 websocket
    规定了一个数据格式
    收发数据
    该收就收
    该发就发

3.群聊

4.私聊

'''
# from flask import Flask,request,redirect,session
#
# app = Flask(__name__)
# app.secret_key = "DragonFire"
#
#
# @app.before_request
# def is_login():  # 判断是否登录
#     # 白名单设置,判断为登录页面时
#     if request.path == "/login":
#         # 跳过处理
#         return None
#     # 判断session是不存在时
#     if not session.get("user"):
#         # 重定向到登录页面
#         return redirect("/login")
#
# @app.after_request
# def foot_log(environ):  # 记录访问日志
#     print(environ)  # 响应信息
#     # 判断请求路径不是登录页面
#     if request.path != "/login":
#         # 打印访问路径
#         print("有客人访问了",request.path)
#
#     return environ
#
# @app.route("/login",methods=["POST","GET"])
# def login():
#     if request.method == "GET":
#         return "Login"
#
#     user = request.form["username"]  # form表单获取
#     pwd = request.form["password"]  # form表单获取
#     # 判断form表示数据和 后台数据库匹配
#     # models.UserInfo.objects.filter(username=user,password=pwd).first()
#     if user == 'xiao' and pwd == '123':
#         # 设置session
#         session["user"] = user
#         # 跳转首页
#         return redirect("/index")
#
#
# @app.route("/index")
# def index():
#     return "Index"
#
# @app.route("/home")
# def home():
#     return "Home"
#
# if __name__ == '__main__':
#     app.run("0.0.0.0", 5000)

'''

1.玩具开机提示语
刚刚开机的时候:
    1.授权问题(MD5授权码)提示语 : 请联系玩具厂商
    2.绑定问题 提示语 : 快给我找一个小主人
    3.成功 提示语:欢迎使用 

    
2.为多个玩具发送点播:
    mpop 弹出菜单 


3.聊天界面:
    <div class="leftd">
        <img src="avatar/girl.jpg" class="leftd_h" />
        <div class="speech left">点击播放</div>
    </div>
    <div class="rightd">
        <img src="avatar/girl.jpg" class="rightd_h" />
        <div class="speech right">点击播放</div>
    </div>
    
    按住录音:
        hold: 按住事件 开始录音(回调函数)
        release: 松开事件 结束录音 执行录音中的回调函数
    
4.app录音:
    var rec = plus.audio.getRcorder()
    rec.record(
        {filename:"_doc/audio/",format:"amr"},
        function(success){ success //录音文件保存路径 },
        function(error){}
    )
    
    rec.stop()
    
5.app与服务器端文件传输(ws传输):
    1.app使用dataURL方式打开录音文件 : base64 文件
    2.通过某个函数 将 Base64 格式的文件 转为 Blob 用于 websocket传输
    3.将Blob对象使用Ws发送至服务端
    4.服务端保存文件(amr)
    5.将amr 转换为 mp3  使用 ffmpeg -i xxx.amr xxx.mp3
    

6.简单的对话(app向玩具(web)发起):
    app:    1.发起两次 ws.send({to_user:}) 告诉服务端我要发给谁消息
            2. ws.send(blob) app与服务器端文件传输
    
    websocket服务:
        0.创建两个变量,用于接收to_user 和 blob对象
        1.收到用户的JSON字符串,to_user
            获取对方的Websocket,用户send
        2.收到用户的Blob对象,语音文件
            保存成amr文件,转换成mp3
            注意保存文件的路径
            
        3.将转换完成的文件发送给 to_user
        
        4.两个变量置空

'''

from flask import Flask
import functools

app = Flask(__name__)

@app.before_request
def b1():
    print('b1')

@app.before_request
def b2():
    print('b2')

def xxxx(func):
    @functools.wraps(func)
    def inner(*args,**kwargs):
        print('before')
        return func(*args,**kwargs)

    return inner

@app.route('/index')
@xxxx
def index():
    return 'index'

@app.route('/home')
@xxxx
def home():
    return 'home'

if __name__ == '__main__':
    app.run(debug=True)
View Code

启动程序,访问index页面

http://127.0.0.1:5000/index

查看Pycharm控制台输出:

b1
b2
before

可以发现,b1先执行。为什么呢?因为代码是从上至下执行的,所以谁先加载,谁就先执行!

关于before_request源码分析,请参考链接:

https://blog.csdn.net/slamx/article/details/50491192

举例:

from flask import Flask

app = Flask(__name__)


@app.before_request
def b1():
    print('b1')

@app.after_request
def a1(environ):
    print('a1')
    return environ


@app.route('/index')
def hello_world():
    return 'Hello World!'


if __name__ == '__main__':
    app.run()
View Code

访问首页:http://127.0.0.1:5000/index,效果如下:

Pycharm输出:

b1
a1

总结:before_request实际上是将视图函数,append到一个列表中。after_request也是将视图函数append到一个列表中,但是它对列表做了reverse操作!具体,可以看源码。

endpoint

endpoint主要是做反向解析的,使用url_for模块,就可以反向生成url

from flask import Flask,url_for

app = Flask(__name__)

@app.route('/index',endpoint='n1')
def index():
    print(url_for('n1'))
    return 'index'


if __name__ == '__main__':
    app.run(debug=True)
View Code

访问url:

http://127.0.0.1:5000/index

执行输出:

/index

flask内置session

flask的session默认存储在哪里呢?在django中,session默认是保存在表里面的。

那么flask的session其实是保存在 用户浏览器的cookie中

它是如何存储的呢?看下面一段代码

from flask import Flask,request,session

app = Flask(__name__)
app.secret_key = 'fdsa'  # 必须要指定这个参数

@app.route('/login')
def login():
    #认证过程省略...
    # 设置session
    session['user_info'] = 'xiao'
    return '123'

if __name__ == '__main__':
    app.run(debug=True)
View Code

访问登录页面,效果如下:

查看请求, 发现一个Set-Cookie。这个cookie的key就是session,值为一堆字符串。它是已经加密过的!

那么它是如何实现的呢?看这一行代码

session['user_info'] = 'xiao'

它在内存中,维护了一个空间,这个空间是一个字典。由于服务端是单进程,单线程。

所有请求过来时,会排队。这个字典,会放一个key,这个key就是程序的线程id,value存放用户信息。

而value是一个字典,比如:{'user_info':'xiao'}

假设有100个用户,那么有100个值。大概是这样的样子:

{
    "线程id": {
        "user_info": "xiao"
    },
    "线程id": {
        "user_info": "zhang"
    },
    ...
}

返回给浏览器时,将内存中的字典做序列化,并做了加密
加完密之后,在cookie中写了一点数据
key是随机的,但是vlaue才是真正的数据

这个时候,flask字典,就清空了。
用户浏览器cookie中就有数据了,但是flask中的数据已经没有了!
这个时候,如果再来一用户,也是执行上面的流程。


总之,作为服务器,我不存储数据。
那么问题来了,flask如何做session验证?

如果之前的用户来了,它会携带cookie。
flask会读取cookie值,如果发现有,进行解密。如果解密成功,那么就是已经登录过了,否则没有登录过。

解密之后,它会将数据放到字典中!
那么读取时,它会直接从内存中读取。

关于flask的源码分析,请参考链接:

https://blog.csdn.net/m0_37519490/article/details/80774069

一、websocket原理

由于时间关系,步骤略...

关于websocket原理,请参考链接:

https://www.cnblogs.com/wupeiqi/p/6558766.html

二、flask之请求上下文

flask上下文管理,主要分为2类:

请求上下文管理

应用上下文管理

由于时间关系,步骤略...

草稿图

关于flask上下文管理,请参考链接:

https://www.cnblogs.com/zhaopanpan/p/9457343.html 

https://blog.csdn.net/bestallen/article/details/54429629

关于flask面试题,请参考链接:

https://www.cnblogs.com/caochao-/articles/8963610.html

今日内容总结:

内容详细:
    1. websocket原理
        a. websocket是一个协议。
            websocket解决了一个问题:服务端可以向客户端推送消息。
            
            
            http协议规定:
                - 请求体请求体
                - 一次请求一次响应(无状态短链接)
            websocket协议规定:
                - 握手
                    - base64(sha1(key + magic string ))
                - 收发数据(加密)
                    -  =127
                    -  =126 
                    -  <=125
                - 连接创建不断开(持久连接)
                
        b. 使用 
            - flask: werkzurg / geventwebsocket
            - django: wsgiref / channel 
            - tornado: 自己写全支持:http和ws
            
    
    2. flask上下文管理 

        前戏:
            a. threading.local 
            
                # 创建threading.local对象 
                val = threading.local()

                def task(arg):
                    # threading.local对象.xxx = 123
                    # 内部,获取当前线程ID
                    #   {
                    #         7800:{'x1':1}
                    #         7180:{'x1':2}
                    #   }
                    val.x1 = arg
                    

                for i in range(10):
                    t = threading.Thread(target=task,args=(i,))
                    t.start()
                
                # ####### flask中搞了一个升级版的threading.local() #######
                # 创建threading.local对象 
                val = threading.local()

                def task(arg):
                    # threading.local对象.xxx = 123
                    # 内部,获取当前协程ID
                    #   {
                    #         7800:{'x1':1}
                    #         7180:{'x1':2}
                    #   }
                    val.x1 = arg
                    

                for i in range(10):
                    t = threading.Thread(target=task,args=(i,))
                    t.start()
            b. 栈 
                后进先出的数据结构
                
            c. 偏函数 
                保留已知参数 
                
            d. 全局变量,flask程序启动只有一份数据
                _request_ctx_stack = LocalStack()
                _app_ctx_stack = LocalStack()
                current_app = LocalProxy(_find_app)
                request = LocalProxy(partial(_lookup_req_object, 'request'))
                session = LocalProxy(partial(_lookup_req_object, 'session'))
                g = LocalProxy(partial(_lookup_app_object, 'g'))

        正文:图
        
        
重点总结:
    1. flask路由:装饰器   *****
    2. flask的session,默认写在浏览器cookie中。 ***
    3. websocket协议       *****
    4. flask请求上下文管理 *****
    

作业:
    请求上下文类关系图
View Code

未完待续...

原文地址:https://www.cnblogs.com/xiao987334176/p/9761130.html