• 02-http视图请求钩子异常抛出与捕获请求上下文与应用上下文终端命令脚本工具


    一、http视图请求钩子

    在请求和相应的过程中,有时候也会在交互过程中要做一些触发性的业务

    在一定时机内完成一些事情,在django里有一些钩子或者通过中间件完成一些业务

    flask也有这样的机制

    在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理,比如:

    • 在请求开始时,建立数据库连接,或者做一些数据的初始化;

    • 在请求开始时,根据需求进行权限校验;

    • 在请求结束时,指定数据的交互格式;

    为了让每个视图函数避免编写重复功能的代码,Flask提供了通用设置的功能,即请求钩子。

    请求钩子是通过装饰器的形式实现,Flask支持如下四种请求钩子:

    • before_first_request

      • 在处理第一个请求前执行[项目初始化时的钩子]

    • before_request

      • 在每次请求前执行

      • 如果在某修饰的函数中返回了一个响应,视图函数将不再被调用

    • after_request

      • 如果没有抛出错误,在每次请求后执行

      • 接受一个参数:视图函数作出的响应

      • 在此函数中可以对响应值在返回之前做最后一步修改处理

      • 需要将参数中的响应在此参数中进行返回

    • teardown_request:

      • 在每次请求后执行

      • 接受一个参数:错误信息,如果有相关错误抛出

      • 需要设置flask的配置DEBUG=False,teardown_request才会接受到异常对象。

    from flask import Flask,request
    
    # 初始化
    app = Flask(import_name=__name__)
    
    # 声明和加载配置
    class Config():
        DEBUG = True
    app.config.from_object(Config)
    
    @app.before_first_request
    def before_first_request():
        """
        这个钩子会在项目启动后第一次被用户访问时执行
        可以编写一些初始化项目的代码,例如,数据库初始化,加载一些可以延后引入的全局配置
        """
        print("----before_first_request----")
        print("系统初始化的时候,执行这个钩子方法")
        print("会在接收到第一个客户端请求时,执行这里的代码")
    
    @app.before_request
    def before_request():
        """
        这个钩子会在每次客户端访问视图的时候执行
        # 可以在请求之前进行用户的身份识别,以及对于本次访问的用户权限等进行判断。..
        """
        print("----before_request----")
        print("每一次接收到客户端请求时,执行这个钩子方法")
        print("一般可以用来判断权限,或者转换路由参数或者预处理客户端请求的数据")
    
    @app.after_request
    def after_request(response):
        print("----after_request----")
        print("在处理请求以后,执行这个钩子方法")
        print("一般可以用于记录会员/管理员的操作历史,浏览历史,清理收尾的工作")
        response.headers["Content-Type"] = "application/json"
        response.headers["Company"] = "python oldboy..."
        # 必须返回response参数
        return response
    
    
    @app.teardown_request
    def teardown_request(exc):
        print("----teardown_request----")
        print("在每一次请求以后,执行这个钩子方法")
        print("after_request完成以后如果有异常错误,则会传递错误异常对象到当前方法的参数中")
        # 在项目关闭了DEBUG模式以后,则异常信息就会被传递到exc中,我们可以记录异常信息到日志文件中
        print(exc)
    
    # 编写路由视图
    @app.route(rule='/')
    def index():
        print("-----------视图函数执行了---------------")
        return "<h1>hello world!</h1>"
    
    if __name__ == '__main__':
        # 运行flask
        app.run(host="0.0.0.0")
    • 在第1次请求时的打印:
    ----before_first_request----
    系统初始化的时候,执行这个钩子方法
    会在接收到第一个客户端请求时,执行这里的代码
    ----before_request----
    127.0.0.1 - - [04/Aug/2020 14:40:22] "GET / HTTP/1.1" 200 -
    每一次接收到客户端请求时,执行这个钩子方法
    一般可以用来判断权限,或者转换路由参数或者预处理客户端请求的数据
    -----------视图函数执行了---------------
    ----after_request----
    在处理请求以后,执行这个钩子方法
    一般可以用于记录会员/管理员的操作历史,浏览历史,清理收尾的工作
    ----teardown_request----
    在每一次请求以后,执行这个钩子方法
    如果有异常错误,则会传递错误异常对象到当前方法的参数中
    None
    • 在第2次请求时的打印:
    ----before_request----
    每一次接收到客户端请求时,执行这个钩子方法
    一般可以用来判断权限,或者转换路由参数或者预处理客户端请求的数据
    127.0.0.1 - - [04/Aug/2020 14:40:49] "GET / HTTP/1.1" 200 -
    -----------视图函数执行了---------------
    ----after_request----
    在处理请求以后,执行这个钩子方法
    一般可以用于记录会员/管理员的操作历史,浏览历史,清理收尾的工作
    ----teardown_request----
    在每一次请求以后,执行这个钩子方法
    如果有异常错误,则会传递错误异常对象到当前方法的参数中
    None

    二、异常抛出与捕获

    1、主动抛出HTTP异常

    • abort 方法

      • 抛出一个给定状态代码的 HTTPException 或者 指定响应,例如想要用一个页面未找到异常来终止请求,你可以调用 abort(404)

    • 参数:

      • code – HTTP的错误状态码

    abort一般在工作中, 也可以基于raise进行替代使用. 如果在flask中一般常用于权限等页面上错误的展示提示.

    from flask import Flask,request,abort
    app = Flask(__name__)
    
    @app.route("/")
    def set_session():
        if request.args.get("password") != "123456":
            abort(404)
        print("欢迎来到个人中心")
        return "ok"

    2、捕获错误

      • errorhandler 装饰器

        • 注册一个错误处理程序,当程序抛出指定错误状态码的时候,就会调用该装饰器所装饰的方法

      • 参数:

        • code_or_exception – HTTP的错误状态码或指定异常

      • 例如统一处理状态码为500的错误给用户友好的提示:

    from flask import Flask,request,abort
    app = Flask(__name__)
    
    @app.errorhandler("404")
    def error_404(exc):
        return "<h1 style='color:red'>Not Found</h1>"
    
    @app.route("/")
    def set_session():
        if request.args.get("password") != "123456":
            abort(404)
        print("欢迎来到个人中心")
        return "ok"

    三、请求上下文与应用上下文

    在flask里面还提供了一系列的变量,我们使用的request、app等其实都是由系统提供的变量

    在flask里提供了非常多的全局变量,全局变量使用时有一定的使用空间,是基于一定空间来使用的,或者叫做执行上下文。

    执行上下文:即语境,语意,在程序中可以理解为在代码执行到某一行时,根据之前代码所做的操作以及下文即将要执行的逻辑,可以决定在当前时刻下可以使用到的变量,或者可以完成的事情。

    Flask中上下文对象:相当于一个容器,保存了 Flask 程序运行过程中的一些信息[变量、函数、类与对象等信息]。

    Flask中有两种上下文,请求上下文(request context)和应用上下文(application context)。

     

    所谓的执行上下文就是在我们代码中,每一句代码运行中都是交给解析器去执行的

    解析器就相当于从上到下读取代码,每读一行会记录这个过程生成的一些变量、函数、类与对象或文件相关信息,这些信息积累到内存中时,就会形成类似字典的东西,我们称这个字典就是执行上下文

    通俗来讲就是代码执行到某一行或某一瞬间,解析器提供开发者能够调用的数据的集合,数据的集合就是执行上下文

    决定了当下可以调用的变量或能够完成的事情有哪些

    1. application 指的就是当你调用app = Flask(__name__)创建的这个对象app

    2. request 指的是每次http请求发生时,WSGI server(比如gunicorn)调用Flask.__call__()之后,在Flask对象内部创建的Request对象;

    3. application 表示用于响应WSGI请求的应用本身,request 表示每次http请求;

    4. application的生命周期大于request,一个application存活期间,可能发生多次http请求,所以,也就会有多个request

    1、请求上下文(request context)

    思考:在视图函数中,如何取到当前请求的相关数据?比如:请求地址,请求方式,cookie等等

    在 flask 中,可以直接在视图函数中使用 request 这个对象进行获取相关数据,而 request 就是请求上下文的对象,保存了当前本次请求的相关数据,请求上下文对象有:request、session

    • request

      • 封装了HTTP请求的内容,针对的是http请求。举例:user = request.args.get('user'),获取的是get请求的参数。

    • session

      • 用来记录请求会话中的信息,针对的是用户信息。举例:session['name'] = user.id,可以记录用户信息。还可以通过session.get('name')获取用户信息。

    请求上下文提供的变量/属性/方法/函数/类与对象,只能在视图中或者被视图调用的地方使用

     

    2、应用上下文(application context)

    它的字面意思是 应用上下文,但它不是一直存在的,它只是request context 中操作当前falsk应用对象 app 的代理(人),所谓local proxy。它的作用主要是帮助 request 获取当前的flask应用相关的信息,它是伴 request 而生,随 request 而灭的。

    应用上下文对象有:current_app,g

    current_app

    应用程序上下文,用于存储应用程序中的变量,可以通过current_app.name打印当前app的名称,也可以在current_app中存储一些变量,例如:

    • 应用的启动脚本是哪个文件,启动时指定了哪些参数

    • 加载了哪些配置文件,导入了哪些配置

    • 连接了哪个数据库

    • 有哪些可以调用的工具类、常量

    • 当前flask应用在哪个机器上,哪个IP上运行,内存多大

    from flask import Flask,current_app
    
    app = Flask(__name__)
    
    @app.route("/")
    def index():
        print(app)
        print(current_app)
        """
            打印结果
            <Flask 'main'>
            <Flask 'main'>
        """
        return "ok"
    
    if __name__ == '__main__':
        app.run(debug=True)

    会发现打印的结果一样,那为什么用current_app

    如果不用current_app,不知道每一个人使用flask对象叫什么名 ,有的人不叫app 叫application

    flask_app,这样的话,每个人创建Flask使用的名都不一样

    命名风格不一样,current_app就是一个通用的名称,在第三方写一些插件就用这个current_app

    from flask import Flask,request,session,current_app,g
    
    # 初始化
    app = Flask(import_name=__name__)
    
    # 声明和加载配置
    class Config():
        DEBUG = True
    app.config.from_object(Config)
    
    # 编写路由视图
    @app.route(rule='/')
    def index():
        # 应用上下文提供给我们使用的变量,也是只能在视图或者被视图调用的地方进行使用,
        # 但是应用上下文的所有数据来源于于app,每个视图中的应用上下文基本一样
        print(current_app.config)   # 获取当前项目的所有配置信息
        print(current_app.url_map)  # 获取当前项目的所有路由信息
    
        return "<h1>hello world!</h1>"
    
    if __name__ == '__main__':
        # 运行flask
        app.run(host="0.0.0.0")

    g变量

    g 作为 flask 程序全局的一个临时变量,充当者中间媒介的作用,我们可以通过它传递一些数据,g 保存的是当前请求的全局变量,不同的请求会有不同的全局变量,通过不同的thread id区别

    from flask import Flask,current_app,g
    
    app = Flask(__name__)
    
    def func2():
        print("func2")
        print(g.abc)
    
    def func1():
        print("func1")
        func2()
    
    @app.route("/")
    def index():
        # print(app)
        # print(current_app.config)
        g.abc = "视图中的数据"
        func1()
        return "ok"
    
    """
    由flask提供了2种不同的上下文对象给我们开发者获取项目或者客户端的信息
    这些对象不需要我们进行实例化,由flask内部创建的
    1. 请求上下文: request, session
    2. 应用上下文: current_app, g
    
    不管是请求或者应用上下文都只能使用在视图范围内或者能被视图调用的地方
    如果是视图以外地方使用,则会报错:
        RuntimeError: Working outside of application context.
    解决方案:
        with app.app_context():
            print(g)
    """
    
    if __name__ == '__main__':
        with app.app_context(): # 设置当前运行环境在应用上下文
            print(g)
        app.run(debug=True)
    from flask import Flask,request,session,current_app,g
    
    # 初始化
    app = Flask(import_name=__name__)
    
    # 声明和加载配置
    class Config():
        DEBUG = True
    app.config.from_object(Config)
    
    @app.before_request
    def before_request():
        g.name = "root"
    
    def get_two_func():
        name = g.name
        print("g.name=%s" % name)
    
    def get_one_func():
        get_two_func()
    
    # 编写路由视图
    @app.route(rule='/')
    def index():
        # 请求上下文提供的变量/属性/方法/函数/类与对象,只能在视图中或者被视图调用的地方使用
        # 请求上下文里面信息来源于每次客户端的请求,所以每个视图中请求上下文的信息都不一样
        # print(session)
    
        # 应用上下文提供给我们使用的变量,也是只能在视图或者被视图调用的地方进行使用,
        # 但是应用上下文的所有数据来源于于app,每个视图中的应用上下文基本一样
        print(current_app.config)   # 获取当前项目的所有配置信息
        print(current_app.url_map)  # 获取当前项目的所有路由信息
        get_one_func()
        return "<h1>hello world!</h1>"
    
    
    if __name__ == '__main__':
        # 运行flask
        app.run(host="0.0.0.0")

    两者区别:

    • 请求上下文:保存了客户端和服务器交互的数据,一般来自于客户端。

    • 应用上下文:flask 应用程序运行过程中,保存的一些配置信息,比如路由列表,程序名、数据库连接、应用信息等

    四、终端命令脚本工具 脚手架

    文档: https://flask-script.readthedocs.io/en/latest/

    这个模块的作用可以让我们通过终端来控制flask项目的运行,类似于django的manage.py

    安装命令:

    pip install flask-script

    集成 Flask-Script到flask应用中,创建一个主应用程序,一般我们叫manage.py/run.py/main.py都行。

    from flask import Flas 
    
    app = Flask(__name__)
    
    """使用flask_script启动项目"""
    from flask_script import Manager
    manage = Manager(app)
    
    @app.route('/')
    def index():
        return 'hello world'
    
    if __name__ == "__main__":
        manager.run()

    启动终端脚本运行项目的命令:

    # 端口和域名不写,默认为127.0.0.1:5000
    python run.py runserver
    
    # 进入终端
    python run.py shell
    
    # 通过-h设置启动域名,-p设置启动端口
    python run.py runserver -h127.0.0.1 -p8888

    能不能让他自动完成一些工具作为一个脚手架自动生成文件,自动生成代码

    1、自定义命令

    from flask import Flask
    from flask_script import Manager, Command, Option
    app = Flask(__name__)
    class Config():
        DEBUG = True
    app.config.from_object(Config)
    
    """基于flask_script创建自定义终端命令"""
    class HelloCommand(Command):
        """命令相关的注释"""

    # 添加参数
    # 通过执行 python main.py hello -n=hello option_list
    = [ Option("--name","-n",help="名称"), Option("--num","-m",help="数量"), ] def run(self,name,num): print("name=%s" % name) print(num) print("命令执行了!!!") # 注册终端脚本工具到app中 manager = Manager(app) manager.add_command("hello", HelloCommand) @app.route("/") def index(): return "ok" if __name__ == '__main__': manager.run()

    2、自定义命令自动生成子应用命令

    from flask import Flask
    from flask_script import Manager, Command, Option
    app = Flask(__name__)
    class Config():
        DEBUG = True
    app.config.from_object(Config)
    manager = Manager(app)
    import os
    class BluePrintCommand(Command):
        option_list = [
            Option("--name","-n",help="蓝图名称")
        ]
    
        def run(self,name=None):
            if name is None:
                print("蓝图名称不能为空!")
                return
            if not os.path.isdir(name):
                os.mkdir(name)
            open("%s/views.py" % name,"w")
            open("%s/models.py" % name,"w")
            with open("%s/urls.py" % name,"w") as f:
                f.write("""from . import views
    urlpatterns = [
    
    ]
    """)
    
    manager.add_command("blue", BluePrintCommand)
    
    @app.route("/")
    def index():
        return "ok"
    
    if __name__ == '__main__':
        manager.run()
  • 相关阅读:
    VMWare磁盘配置的问题终于解决了!!
    十种老板不可追随
    关于ASP无组件上传在2003下出错
    设计模式的有趣解释-追MM[转]
    "未能在给定的程序集中找到任何适合于指定的区域性(或非特定区域性)的资源"解决办法
    今天看到了DNN3.0.4,感觉挺不错的,确实有很大的改进!!!
    今天加入了博客园
    一个女孩写的经典程序!!! (转载)
    加了强名后经常出现错误“程序集清单定义与程序集引用不匹配”
    C#写一个URL编码转换GB23121的方法,然后可以取到天气预报
  • 原文地址:https://www.cnblogs.com/kongxiangqun/p/14007388.html
Copyright © 2020-2023  润新知