Python全栈(七)Flask框架之12.Cookie、Session、上下文和钩子函数

君子之交淡如水,小人之交甘若醴。
君子之间的交情,并不因利益驱使而怎样,小人之间的交往,却多因利益驱使,利益过后,人与人如过眼云烟。君子之间,不因无利益不互相关心,小人之间,却多因利益关系而互相勾结。

一、Cookie的使用

1.Cookie的基本概念

在网站中,http请求是无状态的,也就是说即使第一次和服务器连接后并且登录成功后,第二次请求服务器依然不能知道当前请求是哪个用户。
cookie的出现就是为了解决这个问题,类型为小型文本文件,是某些网站为了辨别用户身份,进行Session跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息。
第一次登录后服务器返回一些数据(cookie)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求的时候,就会自动将上次请求存储的cookie数据自动传送给服务器,服务器通过浏览器携带的数据来判断用户。
cookie存储的数据量有限,不同的浏览器有不同的存储大小,但一般不超过4KB,因此使用cookie只能存储一些小量的数据。

2.Flask中使用Cookie

在Flask中操作cookie,是通过Response对象来操作,可以在response返回之前,通过response.set_cookie()方法来设置,这个方法有以下几个参数:

  • key
    cookie的键
  • value
    cookie的键对应的值。
  • max_age
    cookie的过期时间,如果不设置,则浏览器关闭后就会自动过期。
  • expires
    过期时间,时间戳的形式(1970到现在的时间)。
  • domain
    该cookie在哪个域名中有效,一般设置子域名,比如cms.example.com。
  • path
    该cookie在哪个路径下有效,即当前主域名。

cookie简单测试:
flask_app.py如下:

from flask import Flask, Response

app = Flask(__name__)
app.config['TEMPLATE_AUTO_RELOAD'] = True


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


@app.route('/cookietest/')
def cookietest():
    res = Response('Hello World!!')
    res.set_cookie('username', value='corley', max_age=3)
    return res


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

显示:

flask cookie simple use

二、Flask-session的介绍和使用

1.Session的基本概念

session和cookie的作用有点类似,都是为了存储用户相关的信息;
session可以类比字典,每个键对应着指定的值。
不同的是,cookie存储在本地浏览器,session是一个思路、一个概念、一个服务器存储授权信息的解决方案,不同的服务器、不同的框架、不同的语言有不同的实现。虽然实现不一样,但是目的都是为了在服务器更方便地存储数据。
session是为了解决cookie存储数据不安全的问题。

在现实中,cookie和session是可以结合使用的,有两种方式:

  • 存储在服务端
    通过cookie存储一个session_id,具体的数据则是保存在session中。如果用户已经登录,则服务器会在cookie中保存一个session_id,下次再次请求的时候,会携带该session_id,服务器根据session_id在session库中获取用户的session数据,就能判断该用户到底是谁,以及之前保存的一些状态信息,这称为server side session
    数据存储在服务器会更加安全,不容易被窃取,包括Django在内的很多框架都是采用的这种形式。
    但存储在服务器也有一定的弊端,就是会占用一定的服务器资源,但现在服务器存储量一般较大,存储session信息是不成问题的。
  • 存储在客户端
    将session数据加密,然后存储在cookie中,这种称为client side session
    flask采用的就是这种方式,但是也可以替换成其他形式。

2.Flask中使用Session

Flask中中使用session需要先通过from flask import session语句导入,再添加key和value。
Flask中的session机制是将session信息加密,然后存储在cookie中,因此需要通过配置信息app.config['SECRET_KEY']设置加密密钥。

session的基本使用测试如下:

from flask import Flask, session
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)   # os.urandom()生成指定字节数的随机字符串


@app.route('/')
def index():
    session['username'] = 'Corley'
    session['user_id'] = 1
    return '这是首页'


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

显示:
flask session use
显然,上传了session的一些信息;
其中,os.urandom()用于生成指定字节数的随机字符串,用于表示密钥;
同时可以看到,到期时间是浏览器会话结束时。

可以指定会话到期时间,如下:

from flask import Flask, session
from datetime import timedelta
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
# 自定义过期时间
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=2)


@app.route('/')
def index():
    session['username'] = 'Corley'
    session['user_id'] = 1
    # 设置持久化
    session.permanent = True
    return '这是首页'


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

显示:
flask session use set time
显然,此时session的过期时间为2个小时。
如果想自定义过期时间,需要进行两方面配置:
(1)设置持久化

session.permanent = True

设置了持久化后,默认的过期时间为1个月,如果想要自定义过期时间,还需要进行第二步配置。
(2)自定义过期时间

app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=2)

还可以获取session值,测试如下:

from flask import Flask, session
from datetime import timedelta
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=2)


@app.route('/')
def index():
    username = session.get('username')
    return '这是首页' + str(username)


@app.route('/login/')
def login():
    session['username'] = 'Corley'
    session['user_id'] = 1
    session.permanent = True
    return '登录页面'


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

显示:
flask session use get value
显然,获取到了session的信息。

可以删除session,如下:

from flask import Flask, session
from datetime import timedelta
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=2)


@app.route('/')
def index():
    username = session.get('username')
    return '这是首页' + str(username)


@app.route('/login/')
def login():
    session['username'] = 'Corley'
    session['user_id'] = 1
    session.permanent = True
    return '登录页面'

@app.route('/logout/')
def logout():
    # 删除session
    session.clear()
    return '退出登录'+ str(session.get('username'))

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

显示:
flask session use delete
显然,在访问http://127.0.0.1:5000/logout/后,session已经清空;
除了session.clear(),还可以指定key删除session,如删除username可以用session.pop('username')

三、Flask上下文

Flask项目中上下文包括两类:

  • 应用上下文
    用于记录和应用相关的数据,包括current_app和g。
  • 请求上下文
    用于记录和请求相关的数据,包括request和session。

请求上下文request和应用上下文current_app都是全局变量,所有请求都是共享的。
Flask中使用特殊的机制来保证每次请求的数据都是隔离的,即用户A请求所产生的数据不会影响到用户B的请求,所以直接导入request对象也不会被一些脏数据影响,并且不需要像Django一样在每个函数中使用request的时候传入request参数。
4类上下文对象如下:

  • request
    请求上下文对象,一般用来保存一些请求的变量,如method、args、form等。
  • session
    请求上下文对象,一般用来保存一些会话信息。
  • current_app
    应用上下文对象,返回当前的app。
  • g
    应用上下文对象,处理请求时用于临时存储的对象。

current_app使用示意如下:

from flask import Flask, current_app

app = Flask(__name__)


@app.route('/')
def index():
    return '这是首页'


@app.route('/login/')
def login():
    ca = current_app.name
    cfg = current_app.config['DEBUG']
    return '登录页面:' + ca + '-' + str(cfg)


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

显示:
flask context current_app use
显然,得到了当前所处的app,即初始化Flask对象时传入的参数__name__,也就是当前Python文件名;
除了获取当前app名称,还可以获取绑定到app中的配置参数。

注意:current_app只能在视图函数中使用,在视图函数之外使用会报错。

一般情况测试:
新建utils.py如下:

'''
工具文件
'''


def log_a(username):
    print("Log A %s" % username)


def log_b(username):
    print("Log B %s" % username)

主程序flask_context.py如下:

from flask import Flask, session
from utils import log_a, log_b
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)


@app.route('/')
def index():
    username = session.get('username')
    log_a(username)
    log_b(username)
    return '这是首页'


@app.route('/login/')
def login():
    session['username'] = 'Corley'
    session['user_id'] = 1
    return '登录页面'


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

运行后,先访问http://127.0.0.1:5000/,再访问http://127.0.0.1:5000/login/,再访问http://127.0.0.1:5000/,控制台打印如下:

127.0.0.1 - - [13/May/2020 11:57:59] "GET / HTTP/1.1" 200 -
Log A None
Log B None
127.0.0.1 - - [13/May/2020 11:58:14] "GET /login/ HTTP/1.1" 200 -
127.0.0.1 - - [13/May/2020 11:58:23] "GET / HTTP/1.1" 200 -
Log A Corley
Log B Corley

显然,在访问登录页面之后,控制台打印出了username信息。

此时使用g对象进行改进:
主程序文件修改如下:

from flask import Flask, current_app, g, session
from utils import log_a, log_b
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)


@app.route('/')
def index():
    g.username = session.get('username')
    log_a()
    log_b()
    return '这是首页'


@app.route('/login/')
def login():
    session['username'] = 'Corley'
    session['user_id'] = 1
    return '登录页面'


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

utils.py修改如下:

'''
工具文件
'''
from flask import g

def log_a():
    print("Log A %s" % g.username)


def log_b():
    print("Log B %s" % g.username)

运行之后,按之前的顺序访问,控制台打印:

127.0.0.1 - - [13/May/2020 12:04:34] "GET / HTTP/1.1" 200 -
Log A None
Log B None
127.0.0.1 - - [13/May/2020 12:04:42] "GET /login/ HTTP/1.1" 200 -
127.0.0.1 - - [13/May/2020 12:04:52] "GET / HTTP/1.1" 200 -
Log A Corley
Log B Corley

显然,达到了同样的效果,并且此时不需要再在log_a()log_b()函数中传递参数;
每次刷新页面后,g对象都会被重置,是临时存储的对象;
g对象一般用于解决频繁传参的问题。

可以在一个视图函数中实现调用另一个函数,如下:

from flask import Flask, current_app, g, session
from utils import log_a, log_b
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)


@app.route('/')
def index():
    g.username = session.get('username')
    log_a()
    log_b()
    hello()
    return '这是首页'


def hello():
    print('Hello %s' % g.username)


@app.route('/login/')
def login():
    session['username'] = 'Corley'
    session['user_id'] = 1
    return '登录页面'


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

按之前的顺序访问,控制台打印如下:

127.0.0.1 - - [13/May/2020 12:13:41] "GET / HTTP/1.1" 200 -
Log A None
Log B None
Hello None
127.0.0.1 - - [13/May/2020 12:13:48] "GET /login/ HTTP/1.1" 200 -
127.0.0.1 - - [13/May/2020 12:13:52] "GET / HTTP/1.1" 200 -
Log A Corley
Log B Corley
Hello Corley

四、常用的钩子函数

钩子函数是指在执行函数和目标函数之间挂载的函数,框架开发者给调用方提供一个point即挂载点,至于挂载什么函数由调用方决定, 从而大大提高了灵活性

1.before_first_request

第一次请求之前执行。
练习如下:

from flask import Flask

app = Flask(__name__)


@app.route('/')
def index():
    print('这是首页')
    return '这是首页'


@app.before_first_request
def handle_first_request():
    print('在第一次请求之前执行')


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

运行后,访问http://127.0.0.1:5000/,控制台打印:

127.0.0.1 - - [13/May/2020 12:39:01] "GET / HTTP/1.1" 200 -
在第一次请求之前执行
这是首页

2.before_request

在每次请求之前执行,通常可以用这个装饰器来给视图函数增加一些变量。

练习如下:

from flask import Flask

app = Flask(__name__)


@app.route('/')
def index():
    print('这是首页')
    return '这是首页'


@app.before_first_request
def handle_first_request():
    print('在第一次请求之前执行')


@app.before_request
def handle_before():
    print("在每一次请求之前执行")


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

运行后,多次访问http://127.0.0.1:5000/,控制台打印:

在第一次请求之前执行
在每一次请求之前执行
这是首页
127.0.0.1 - - [13/May/2020 12:41:36] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页
127.0.0.1 - - [13/May/2020 12:41:38] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页
127.0.0.1 - - [13/May/2020 12:41:39] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页
127.0.0.1 - - [13/May/2020 12:41:40] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [13/May/2020 12:41:41] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页

3.after_request

在每次请求之后执行。

练习如下:

from flask import Flask

app = Flask(__name__)


@app.route('/')
def index():
    print('这是首页')
    return '这是首页'


@app.before_first_request
def handle_first_request():
    print('在第一次请求之前执行')


@app.before_request
def handle_before():
    print("在每一次请求之前执行")


@app.after_request
def handle_after(response):
    print("在每一次请求之后执行")
    return response


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

运行后,多次访问http://127.0.0.1:5000/,控制台打印:

127.0.0.1 - - [13/May/2020 12:51:05] "GET / HTTP/1.1" 200 -
在第一次请求之前执行
在每一次请求之前执行
这是首页
在每一次请求之后执行
在每一次请求之前执行
这是首页
在每一次请求之后执行
127.0.0.1 - - [13/May/2020 12:51:06] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页
在每一次请求之后执行
127.0.0.1 - - [13/May/2020 12:51:07] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页
在每一次请求之后执行
127.0.0.1 - - [13/May/2020 12:51:08] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页
在每一次请求之后执行
127.0.0.1 - - [13/May/2020 12:51:09] "GET / HTTP/1.1" 200 -

4.teardown_appcontext

不管是否有异常,注册的函数都会在每次请求之后执行。

练习如下:

from flask import Flask

app = Flask(__name__)


@app.route('/')
def index():
    1/0
    print('这是首页')
    return '这是首页'


@app.before_first_request
def handle_first_request():
    print('在第一次请求之前执行')


@app.before_request
def handle_before():
    print("在每一次请求之前执行")


@app.after_request
def handle_after(response):
    print("在每一次请求之后执行")
    return response


@app.teardown_appcontext
def handle_teardown(response):
    print('teardown被执行')
    return response


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

运行后,访问http://127.0.0.1:5000/,显示:
flask hook teardown_appcontext
控制台打印:

在第一次请求之前执行
在每一次请求之前执行
在每一次请求之后执行
teardown被执行
[2020-05-13 12:58:48,206] ERROR in app: Exception on / [GET]
Traceback (most recent call last):
  File "XXX\Test-gftU5mTd\lib\site-packages\flask\app.py", line 2447, in wsgi_app
    response = self.full_dispatch_request()
  File "XXX\Test-gftU5mTd\lib\site-packages\flask\app.py", line 1952, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "XXX\Test-gftU5mTd\lib\site-packages\flask\app.py", line 1821, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "XXX\Test-gftU5mTd\lib\site-packages\flask\_compat.py", line 39, in reraise
    raise value
  File "XXX\Test-gftU5mTd\lib\site-packages\flask\app.py", line 1950, in full_dispatch_request
    rv = self.dispatch_request()
  File "XXX\Test-gftU5mTd\lib\site-packages\flask\app.py", line 1936, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "XXXX\flask_hook.py", line 8, in index
    1/0
ZeroDivisionError: division by zero
127.0.0.1 - - [13/May/2020 12:58:48] "GET / HTTP/1.1" 500 -

显然, 在出现异常的情况下也执行了handle_teardown()函数;
虚造关闭Debug模式才能有明显的现象,在Debug模式下是不能执行handle_teardown()函数的。

5.context_processor

上下文处理器,返回的字典中的键可以在模板上下文中使用。

假如在多个视图函数中渲染模板时都需要传入同样的参数,flask_hook.py如下:

from flask import Flask, render_template

app = Flask(__name__)


@app.route('/')
def index():
    return render_template('index.html', username='Corley')


@app.route('/list/')
def list():
    return render_template('list.html', username='Corley')


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

在模板目录templates中新建index.html和list.html,index.html如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
    <p>这是首页</p>
    <p>{{ username }}</p>
</body>
</html>

list.html如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>列表</title>
</head>
<body>
    <p>这是列表</p>
    <p>{{ username }}</p>
</body>
</html>

显示:
flask hook nocontext_processor
显然,达到了效果,但是在每个视图函数中都要传入参数,很麻烦冗余,可以用context_processor装饰器定义钩子函数传递公共参数:

from flask import Flask, render_template

app = Flask(__name__)


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


@app.route('/list/')
def list():
    return render_template('list.html')


@app.context_processor
def context_process():
    return {'username': 'Corley'}


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

效果与之前一样;
此时在每个视图函数中不需要再传递参数,而是在context_processor装饰器定义的钩子函数中返回参数。

6.errorhandler

errorhandler接收状态码作为参数,可以自定义处理返回这个状态码的响应的方法。

测试如下:

from flask import Flask, render_template

app = Flask(__name__)


@app.route('/')
def index():
    1/0
    return render_template('index.html')


@app.route('/list/')
def list():
    return render_template('list.html')


@app.context_processor
def context_process():
    return {'username': 'Corley'}


@app.errorhandler(404)
def page_not_found(error):
    return render_template('404.html'), 404


@app.errorhandler(500)
def server_error(error):
    return '<p>服务器内部错误...</p>', 500


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

新建404.html如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>页面404</title>
</head>
<body>
    <p>页面跑到火星去了...</p>
</body>
</html>

显示:
flask hook errorhandler
显然,捕获到了404和500错误。

还可以在视图函数中用abort()方法主动抛出异常,如下:

from flask import Flask, render_template, abort

app = Flask(__name__)


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


@app.route('/list/')
def list():
    abort(404)
    return render_template('list.html')


@app.context_processor
def context_process():
    return {'username': 'Corley'}


@app.errorhandler(404)
def page_not_found(error):
    return render_template('404.html'), 404


@app.errorhandler(500)
def server_error(error):
    return '<p>服务器内部错误...</p>', 500


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

显示:
flask hook errorhandler abort
显然,此时再访问http://127.0.0.1:5000/list/,就会报错;
因为在list()视图函数中使用sort()方法抛出404错误,被钩子函数page_not_found()捕获到,因此访问会出现404错误。

五、Flask-Restful

1.Restful API规范

Restful API是用于在前端与后台进行通信的一套规范,使用这个规范可以让前后端开发变得更加简单。

协议

采用http或者https协议。

数据传输格式

数据之间传输的格式应该使用json形式,而不是xml。

url链接

url链接中不能有动词,只能有名词;
对于一些名词,如果出现复数,应该在后面加s。

HTTP请求的方法

方法 含义 举例
GET 从服务器上获取资源 /users/获取所有用户
/user/id/根据id获取一个用户
POST 在服务器上新创建一个资源 /user/新建一个用户
PUT 在服务器上更新资源(客户端提供所有改变后的数据) /user/id/更新某个id的用户的信息(需要提供用户的所有信息)
PATCH 在服务器上更新资源(客户端只提供需要改变的属性) /user/id/更新某个id的用户信息(只需要提供需要改变的信息)
DELETE 从服务器上删除资源 /user/id/删除一个用户

状态码

状态码 描述 含义
200 OK 服务器成功响应客户端的请求。
400 INVALID REQUEST 用户发出的请求有错误,服务器没有进行新建或修改数据的操作
401 Unauthorized 用户没有权限访问这个请求
403 Forbidden 因为某些原因禁止访问这个请求
404 NOT FOUND 用户发送的请求的url不存在
406 NOT Acceptable 用户请求不被服务器接收(比如服务器期望客户端发送某个字段,但是没有发送)
500 Internal server error 服务器内部错误,比如出现了bug

2.Flask-Restful插件

基本概念

Flask-Restful是Flask中专门用来写restful api的一个插件,使用它可以快速集成restful api功能。
在app的后台以及纯API的后台中,这个插件可以帮助我们节省很多时间;
在普通的网站中,这个插件显得有些鸡肋,因为在普通的网页开发中,是需要去渲染HTML代码的,而Flask-Restful在每个请求中都返回json格式的数据。

安装

Flask-Restful的环境要求:

  • Flask版本>=0.80.8
  • Python版本为2.6、2.7,或3.3及以上。

在虚拟环境中使用pip install flask-restful命令安装。

基本使用

使用Flask-Restful定义视图函数时,要继承自flask_restful.Resource类,再根据具体的请求的方法来定义相应的方法。
例如,如果期望客户端使用get方法发送请求,那么就定义一个get方法,类似于MethodView。
简单使用测试如下:

from flask import Flask
from flask_restful import Api, Resource

app = Flask(__name__)
api = Api(app)


class IndexView(Resource):
    def get(self):
        return {'username': 'Corley'}

    def post(self):
        return {'info': 'Login Successfully!!'}


api.add_resource(IndexView, '/', endpoint='index')

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

运行之后,使用Postman模拟请求如下:
flask restful simple use
显然,用get方法和post方法请求得到的结果是不一样的。
说明:

  • endpoint是用来给url_for()方法反转url的时候指定的;如果不设置endpoint参数,会使用视图的名字的小写作为endpoint的值。
  • add_resource()方法的第二个参数是视图函数的路由地址,这个地址与视图函数的route一样,可以传递参数;有一点不同的是,这个方法可以传递多个url来指定视图函数。
原创文章 132 获赞 1491 访问量 36万+

猜你喜欢

转载自blog.csdn.net/CUFEECR/article/details/106097655
今日推荐