• 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

    未完待续...

  • 相关阅读:
    (转)ubuntu 对拍和基本操作
    一个在线翻译LateX的网站
    51nod 1376: 最长递增子序列的数量(二维偏序+cdq分治)
    BZOJ1087: [SCOI2005]互不侵犯King(状态压缩动态规划)
    ZOJ Problem Set
    bzoj2301:[HAOI2011]Problem b(容斥+莫比乌斯反演+分块)
    BZOJ 4318 OSU!期望DP
    CodeForces 235B Let's Play Osu!(概率)
    博客界面美化
    A+B Problem
  • 原文地址:https://www.cnblogs.com/xiao987334176/p/9761130.html
Copyright © 2020-2023  润新知