day93:flask:Cookie&Session&请求钩子&捕获错误&上下文&Flask-Script

目录

1.HTTP的会话控制

2.Cookie

3.Session

4.请求钩子

5.捕获错误

6.上下文:context

7.Flask-Script

1.HTTP的会话控制

1.什么是会话控制?

所谓的会话,就是客户端浏览器和服务端网站之间一次完整的交互过程.

会话的开始是在用户通过浏览器第一次访问服务端网站开始.

会话的结束时在用户通过关闭浏览器以后,与服务端断开.

所谓的会话控制,就是在客户端浏览器和服务端网站之间,进行多次http请求响应之间,记录、跟踪和识别用户的信息而已。

2.会话控制出现的原因

因为 http 是一种无状态协议,浏览器请求服务器是无状态的。

无状态:指一次用户请求时,浏览器、服务器无法知道之前这个用户做过什么,每次请求都是一次新的请求。

无状态原因:浏览器与服务器是使用 socket 套接字进行通信的,服务器将请求结果返回给浏览器之后,会关闭当前的 socket 连接,而且服务器也会在处理页面完毕之后销毁页面对象。

3.会话控制的应用场景

有时需要保持下来用户浏览的状态,比如用户是否登录过,浏览过哪些商品等

实现状态保持主要有两种类型:

  • 在客户端存储信息使用url Cookietoken令牌[jwt.csrf,oauth]

  • 在服务器端存储信息使用Session

2.Cookie

1.Cookie是由服务器端生成,发送给客户端浏览器,浏览器会将Cookie的key/value保存,下次请求同一网站时就发送该Cookie给服务器(前提是浏览器设置为启用cookie)。Cookie的key/value可以由服务器端自己定义。

2.使用场景: 登录状态, 浏览历史, 网站足迹,购物车 [不登录也可以使用购物车]

3.Cookie是存储在浏览器中的一段纯文本信息,建议不要存储敏感信息如密码,因为电脑上的浏览器可能被其它人使用

4.Cookie基于域名安全,不同域名的Cookie是不能互相访问的

   如访问luffy.com时向浏览器中写了Cookie信息,使用同一浏览器访问baidu.com时,无法访问到luffy.com写的Cookie信息

5.浏览器的同源策略针对cookie也有限制作用.

6.当浏览器请求某网站时,会将本网站下所有Cookie信息提交给服务器,所以在request中可以读取Cookie信息

1.设置cookie

设置cookie需要通过flask的Response响应对象来进行设置,由响应对象会提供了方法set_cookie给我们可以快速设置cookie信息。

@app.route("/set_cookie")
def set_cookie():
    """设置cookie"""
    response = make_response("ok")
    # 格式:response.set_cookie(key="变量名",value="变量值",max_age="有效时间/秒")
    response.set_cookie("username","xiaoming",100)
    """如果cookie没有设置过期时间,则默认过期为会话结束过期"""
    """cookie在客户端中保存时,用一个站点下同变量名的cookie会覆盖"""
    response.set_cookie("age","100")

    return response

2.获取cookie

@app.route("/get_cookie")
def get_cookie():
    """获取cookie"""
    print(request.cookies)
    print(request.cookies.get("username"))
    print(request.cookies.get("age"))
    """打印效果:
    {'username': 'xiaoming'}
    """
    return ""

3.删除cookie

@app.route("/del_cookie")
def del_cookie():
    """删除cookie"""
    response = make_response("ok")
    # 把对应名称的cookie设置为过期时间(0s),则可以实现删除cookie
    response.set_cookie("username","",0)
    return response

3.Session

session相关配置项文档://dormousehole.readthedocs.io/en/latest/config.html?highlight=session_cookie_path

1.对于敏感、重要的信息,建议要存储在服务器端,不能存储在浏览器中,比如用户名、余额、等级、验证码等信息

2.在服务器端进行状态保持的方案就是Session

3.Session依赖于Cookie,session的ID一般默认通过cookie来保存到客户端。

4.flask中的session需要加密,所以使用session之前必须配置SECRET_KEY选项,否则报错.

5.session的有效期默认是会话期,会话结束了,session就废弃了。

6.如果将来希望session的生命周期延长,可以通过修改cookie中的sessionID来完成配置。

1.设置session

from flask import Flask, make_response, request,session

app = Flask(__name__)

class Config():
    # flask中的session需要加密,所以使用session之前必须配置SECRET_KEY选项,否则报错.
    SECRET_KEY = "123456asdadad"
    DEBUG = True

app.config.from_object(Config)

@app.route("/set_session")
def set_session():
    """设置session"""
    """与cookie不同,session支持python基本数据类型作为值"""
    session["username"] = "xiaohuihui"
    session["info"] = {
        "age":11,
        "sex":True,
    }

    return "ok"


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

2.获取session

@app.route("/get_session")
def get_session():
    """获取session"""
    print( session.get("username") )
    print( session.get("info") )
    return "ok"

3.删除session

@app.route("/del_session")
def del_session():
    """删除session"""
    try:
        del session["username"]
        # session.clear() # 删除所有
    except:
        pass
    return "ok"

Tip:如何查看当前flask默认支持的所有配置项

from flask import Flask, make_response, request,session

app = Flask(__name__)

class Config():
    SECRET_KEY = "123456asdadad"
    DEBUG = True

app.config.from_object(Config)

# 查看当前flask默认支持的所有配置项
print(app.config)

# 下面是flask默认支持的所有配置项
"""
<Config {
 'DEBUG': False,
 'TESTING': False,
 'PROPAGATE_EXCEPTIONS': None,
 'PRESERVE_CONTEXT_ON_EXCEPTION': None,
 'SECRET_KEY': None,
 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(31),
 'USE_X_SENDFILE': False,
 'LOGGER_NAME': '__main__',
 'LOGGER_HANDLER_POLICY': 'always',
 'SERVER_NAME': None,
 'APPLICATION_ROOT': None,
 'SESSION_COOKIE_NAME': 'session',
 'SESSION_COOKIE_DOMAIN': None,
 'SESSION_COOKIE_PATH': None,
 'SESSION_COOKIE_HTTPONLY': True,
 'SESSION_COOKIE_SECURE': False,
 'SESSION_REFRESH_EACH_REQUEST': True,
 'MAX_CONTENT_LENGTH': None,
 'SEND_FILE_MAX_AGE_DEFAULT': datetime.timedelta(0, 43200),
 'TRAP_BAD_REQUEST_ERRORS': False,
 'TRAP_HTTP_EXCEPTIONS': False,
 'EXPLAIN_TEMPLATE_LOADING': False,
 'PREFERRED_URL_SCHEME': 'http',
 'JSON_AS_ASCII': True,
 'JSON_SORT_KEYS': True,
 'JSONIFY_PRETTYPRINT_REGULAR': True,
 'JSONIFY_MIMETYPE': 'application/json',
 'TEMPLATES_AUTO_RELOAD': None
"""

4.请求钩子

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

  • 在请求开始时,建立数据库连接;

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

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

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

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

  • before_first_request

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

  • before_request

    • 在每次请求前执行

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

  • after_request

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

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

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

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

  • teardown_request:

    • 在每次请求后执行

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

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

from flask import Flask,make_response

app = Flask(__name__)

@app.before_first_request
def first_request():
    print("1. 项目启动以后,首次被请求时,会自动执行[项目全局初始化工作]")

@app.before_request
def before_request():
    print("2. 每次客户端请求时,都会自动执行, 常用于记录访问日志,进行权限判断,身份识别,访问限流...")

@app.after_request
def after_request(response):
    print("4. 每次视图执行以后,会自动执行")
    # after_request执行以后,必须要返回结果给客户端!!
    return response

@app.teardown_request
def teardown_request(exc):
    print("5. after_request完成以后,如果有发生异常,在关闭DEBUG模式的情况下可以接受异常对象,进行异常的记录,异常通知")
    print(exc)

@app.route("/")
def set_session():
    print("3. 视图执行了.......")
    return "ok"


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

第1次请求时打印:

1. 项目启动以后,首次被请求时,会自动执行[项目全局初始化工作]
2. 每次客户端请求时,都会自动执行, 常用于记录访问日志,进行权限判断,身份识别,访问限流…
3. 视图执行了…….
4. 每次视图执行以后,会自动执行
5. after_request完成以后,如果有发生异常,在关闭DEBUG模式的情况下可以接受异常对象,进行异常的记录,异常通知
None

第2次请求时打印:

2. 每次客户端请求时,都会自动执行, 常用于记录访问日志,进行权限判断,身份识别,访问限流…
3. 视图执行了…….
4. 每次视图执行以后,会自动执行
5. after_request完成以后,如果有发生异常,在关闭DEBUG模式的情况下可以接受异常对象,进行异常的记录,异常通知
None

5.捕获错误

flask中内置了app.errorhander提供给我们捕获异常,实现一些在业务发生错误时的自定义处理。

1. 通过http状态码捕获异常信息

2. 通过异常类进行异常捕获

  • errorhandler 装饰器

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

  • 参数:

    • code/exception – HTTP的错误状态码或指定异常类

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

@app.errorhandler(500)
def internal_server_error(e):
    return '服务器搬家了'

2.捕获指定系统异常类

@app.errorhandler(ZeroDivisionError)
def zero_division_error(e):
    return '除数不能为0'

3.也可以捕获自定义异常类

from flask import Flask

app = Flask(__name__)

"""加载配置"""
class Config():
    DEBUG = True
app.config.from_object(Config)


"""捕获系统异常或者自定义异常"""
class APIError(Exception):
    pass

@app.route("/")
def index():
    raise APIError("api接口调用参数有误!")
    return "个人中心,视图执行了!!"

@app.errorhandler(APIError)
def error_apierror(e):
    return "错误: %s" % e

if __name__ == '__main__':
    app.run(host="localhost",port=8080)

6.上下文:context

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

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

1.current_app

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

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

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

  • 连接了哪个数据库

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

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

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")

2.g变量

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

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")

3.关于请求上下文和应用上下文的总结

由flask提供了2种不同的上下文对象给我们开发者获取项目或者客户端的信息

这些对象不需要我们进行实例化,由flask内部创建的
  1. 请求上下文: request, session
  2. 应用上下文: current_app, g

不管是请求上下文或者应用上下文都只能使用在视图范围内或者能被视图调用的地方

如果是视图以外地方使用,则会报错
RuntimeError: Working outside of application context.

解决方案:
with app.app_context():
  print(g)

'''
请求上下文提供的变量/属性/方法/函数/类与对象,只能在视图中或者被视图调用的地方使用
请求上下文里面信息来源于每次客户端的请求,所以每个视图中请求上下文的信息都不一样
'''

'''
应用上下文提供给我们使用的变量,也是只能在视图或者被视图调用的地方进行使用,
但是应用上下文的所有数据来源于于app,每个视图中的应用上下文基本一样
'''

7.Flask-Script

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

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

安装命令:

pip install flask-script

1.启动终端脚本运行项目

from flask import Flask
from flask_script import Manager

app = Flask(__name__)

class Config():
    DEBUG = True

app.config.from_object(Config)

# 注册终端脚本工具到app中
manager = Manager(app)

@app.route("/")
def index():
    return "ok"

if __name__ == '__main__':
    # 注意,这里不是app对象
    manager.run()

# 端口和域名不写,默认为127.0.0.1:5000
# python run.py runserver

# 通过-h设置启动域名,-p设置启动端口
# python run.py runserver -h127.0.0.1 -p8888

2.自定义终端命令

如果我们想自定义终端命令,必须要遵从以下三点

1. 引入Command命令基类
2. 创建命令类必须直接或间接继承Command,并在内部实现run方法,同时如果有自定义的其他参数,则必须实现__init__
3. 使用flask_script应用对象manage.add_command对命令类进行注册,并设置调用终端别名。

from flask import Flask
from flask_script import Manager, Command, Option # 1.引入command命令基类
app = Flask(__name__)
class Config():
    DEBUG = True
app.config.from_object(Config)

"""基于flask_script创建自定义终端命令"""
class HelloCommand(Command): # 2.1 创建命令类必须直接或间接继承Command
    """命令相关的注释"""
    option_list = [
        Option("--name","-n",help="名称"),
        Option("--num","-m",help="数量"),
    ]
    def run(self,name,num): # 2.2 在自定义终端命令内部实现run方法
        print("name=%s" % name)
        print(num)
        print("命令执行了!!!")

# 注册终端脚本工具到app中
manager = Manager(app)
manager.add_command("hello", HelloCommand) # 3.使用flask_script应用对象manage.add_command对命令类进行注册,并设置调用终端别名。

@app.route("/")
def index():
    return "ok"

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

# 运行该程序
# python run.py hello -n=hahaha -m=qiqiqi

运行结果:

3.自定义脚手架命令

from flask import Flask
from flask_script import Manager, Command, Option # 1.引入Command命令基类

app = Flask(__name__)

class Config():
    DEBUG = True
app.config.from_object(Config)

manager = Manager(app)
import os
class BluePrintCommand(Command): # 2.1 创建命令类直接或间接继承Command
    option_list = [
        Option("--name","-n",help="蓝图名称")
    ]

    def run(self,name=None): # 2.2 在命令类内部实现run方法
        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) # 3.使用应用对象.add_command对命令类进行注册,并设置调用终端别名

@app.route("/")
def index():
    return "ok"

if __name__ == '__main__':
    manager.run()
    
# 运行程序
# python run.py blue -n=users

运行结果: