flask之请求对象、响应对象、cookies和session
一、请求对象
1、Flask 的 request 基于 Werkzeug
代表 HTTP 请求。request 对象包含许多方法和属性,用于处理请求数据。
https://werkzeug.palletsprojects.com/en/stable/wrappers/
Werkzeug 是一个用于 WSGI 应用的全面且强大的 Python 工具库。它在 Python Web 开发中广受欢迎,并常常与 Flask(一个流行的微框架)一起使用。Werkzeug 提供了构建、部署和管理 Web 应用程序所需的许多底层功能。
主要特性
-
WSGI 工具:Werkzeug 提供了用于构建 WSGI(Web Server Gateway Interface)应用的多种实用工具和实用程序,帮助开发者更轻松地处理请求和响应的基本操作。
-
HTTP 请求与响应处理:Werkzeug 提供对 HTTP 请求和响应对象的强大封装,使得处理 HTTP 请求(如 GET、POST 参数、文件上传)和响应(如设置状态码、cookie、内容类型等)更加简单方便。
-
路由:Werkzeug 提供了灵活的 URL 路由系统,允许定义和解析 URL 模式,这对构建复杂的 Web 应用非常有帮助。
-
调试和开发服务器:Werkzeug 包含一个用于开发的内置调试器和轻量级 WSGI 服务器,支持自动代码重新加载和详细的错误日志信息,帮助开发过程中快速发现和定位问题。
-
Sessions(会话管理):Werkzeug 提供了基本的会话管理支持,允许开发者在客户端和服务器之间维护用户会话状态。
-
安全性功能:Werkzeug 提供了一些基本的安全性功能,如请求数据的 escaping,帮助防范常见的 Web 安全问题。
-
Middleware(中间件):Werkzeug 支持中间件模式,可以在请求和响应生命周期的不同阶段插入自定义逻辑,为 WSGI 应用开发提供更多扩展可能。
-
实用工具:包括 URL 解析、cookie 处理、数据结构管理(如多字典对象)等丰富的工具。
2、request 常用的方法和属性(27):
请求数据
-
request.args: 获取查询字符串参数,返回一个MultiDict。 -
request.form: 获取表单数据,返回一个MultiDict。 -
request.files: 获取上传的文件,返回一个MultiDict。文件以FileStorage对象的形式存储。-
ImmutableMultiDict([('myfile1', <FileStorage: '3.webp' ('image/webp')>), ('myfile2', <FileStorage: '1.webp' ('image/webp')>)])
-
-
request.json: 如果请求包含 JSON 数据,则以字典形式返回,否则返回None。 -
request.data: 以字节的形式返回请求体数据。 -
request.values: 包含form和args中的数据,form数据优先。-
CombinedMultiDict([ImmutableMultiDict([('shengao', '180')]), ImmutableMultiDict([('name', 'zjz'), ('age', '17')])])
-
请求环境
request.method: 返回请求方法(如GET,POST,PUT,DELETE)。request.url: 返回完整请求的 URL。request.base_url: 返回不包含查询字符串的 URL。request.url_root: 返回不包含请求路径的 URL(只包含协议和主机名)。request.host: 返回请求的主机名。request.path: 返回 URL 的路径部分。request.full_path: 返回 URL 的路径和查询字符串。request.headers: 返回请求头的字典。request.cookies: 返回请求中的 cookies。
用户与文件
request.remote_addr: 返回发起请求的远程地址。request.user_agent: 返回请求头中的User-Agent信息。
请求上下文标识
request.blueprint: 返回当前请求的蓝图名。request.endpoint: 返回当前请求的端点。request.view_args: 返回与当前请求路径匹配的视图参数。
请求属性与方法内省
request.is_secure: 如果请求是通过 HTTPS 发起的,返回True。request.get_data(): 以字节形式获取请求体,同request.data。request.get_json(): 以 JSON 格式解析请求体,同request.json,支持额外参数如force和silent。request.get_cookie(): 获取指定名称的 Cookie 值。
其他
request.scheme: 返回请求使用的协议(http或https)。request.mimetype: 返回请求体的 MIME 类型。request.content_type: 返回请求头的Content-Type。
3、简易代码
from flask import Flask, request, render_template, redirect, jsonify, make_response
app = Flask(__name__, template_folder='temp')
app.debug = True
@app.route('/index', methods=['GET', 'POST'])
def index():
# request.method 请求的方法
print(request.method)
# request.args get请求提交的数据
print(request.args)
# request.form post请求提交的数据
print(request.form.get('name'))
# request.values post和get提交的数据总和
print(request.values)
# request.cookies 客户端所带的cookie
# print(request.cookies)
# request.headers 请求头
# print(request.headers)
# request.path 不带域名,请求路径
# print(request.path)
# request.full_path 不带域名,带参数的请求路径
# print(request.full_path)
# print(request.base_url)
# request.url 带域名带参数的请求路径
# request.base_url 带域名请求路径
# request.url_root 域名
# request.host_url 域名
# request.host 服务端地址
# print(request.host)
# request.files
print(request.files)
obj = request.files.get('myfile1')
obj.save('./' + obj.filename)
obj = make_response(jsonify({'name': 'lqz', 'age': 19}))
obj.set_cookie('name', 'lqz')
return obj
if __name__ == '__main__':
app.run(port=5001)
二、响应对象
1、响应四件套
- return '字符串'
- return render_template('index.html',name=lqz,age=19)
- return redirect(url_for(别名))
- return jsonify(字典,列表)
三、cookie 和 session
1、引入cookie
需要导入 make_response 模块,实例化出一个对象,使用点set_cookie方法
from flask import Flask, request, jsonify, make_response
app = Flask(__name__, template_folder='temp')
app.debug = True
@app.route('/index', methods=['GET', 'POST'])
def index():
obj = make_response(jsonify({'name': '张经智', 'age': 19, 'hobby': 'coding'}))
obj.set_cookie('age', value='19', expires='Thu, 31-Dec-2025 23:59:59 GMT', path='/indexdsf')
return obj
if __name__ == '__main__':
app.run(port=5001)
注意:
设置过期时间:
expires
- 定义:
expires是一个具体的日期时间字符串,表示 cookie 过期的确切时间。 - 格式:一般使用 UTC 时间格式,例如
Expires=Tue, 31-Dec-2024 23:59:59 GMT。 - 示例:如果设置
expires=60,它不会如预期工作。这是因为expires希望的是一个具体的时间格式,单纯提供一个数字(如60)并不能被解析成有效的日期时间。
max_age
- 定义:
max_age是一个以秒为单位的整数,表示从设置 cookie 开始到 cookie 过期的时间长度。 - 示例:
max_age=60意味着这个 cookie 在创建后会在 60 秒后过期。 - 生效机制:当设置
max_age时,cookie 会根据当前时间加上max_age的值来计算过期时间,确保在这段时间内 cookie 仍然有效。
/ 全网站可见,/index 只在index站点可见
2、curl 查看cookie信息
-i, --include Include protocol response headers in the output

3、引用 session
from flask import Flask, session, redirect, url_for, request, render_template_string
app = Flask(__name__)
app.secret_key = 'supersecretkey' # 用于加密 session 数据,必须设置一个安全的密钥
@app.route('/')
def index():
if 'username' in session:
return f'Logged in as {session["username"]} <br> <a href="/logout">Logout</a>'
return 'You are not logged in <br> <a href="/login">Login</a>'
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
session['username'] = request.form['username']
return redirect(url_for('index'))
# 提供一个简单的登录表单进行登录
return '''
<form method="post">
<p><input type=text name=username>
<p><input type=submit value=Login>
</form>
'''
@app.route('/logout')
def logout():
# 移除session中的用户数据
session.pop('username', None)
return redirect(url_for('index'))
if __name__ == '__main__':
app.run(debug=True, port=5001)
注:
1、login视图的细节
当第一次访问login页面的时候是get方法,提交提交form表单之后是post方法,
第二次刷新进去login视图走post判断,设置session,数据被存在浏览器的cookie中,url_for跳转到index页面
2、app.secret_key = ' '
Flask 在处理 session 加密和解密时会用到 app.secret_key。这个密钥是至关重要的,因为它用于以下目的:
存储流程:
-
序列化数据:
- 将
session中的字典数据序列化为字符串。Flask 默认使用其securecookie模块,以 JSON 格式存储会话数据。
- 将
-
生成签名:
- 使用
app.secret_key生成一个哈希签名。Flask 的签名过程通常基于 HMAC Hash−basedMessageAuthenticationCode,这是一种使用加密哈希函数(如 SHA-256)结合一个密钥来生成签名的方式。
- 使用
-
构建 cookie 值:
- 将序列化的数据与生成的签名组合成一个单个字符串,格式通常类似于
data.signature。
- 将序列化的数据与生成的签名组合成一个单个字符串,格式通常类似于
-
发送给客户端:
- 上述组合字符串被存储为一个 cookie,并发送到客户端浏览器。浏览器在每次请求时都会将此 cookie 返回给服务器。
解析流程:
-
接收请求:
- 客户端浏览器在发送请求时将之前存储的 session cookie 包含在请求头中。
-
拆分数据和签名:
- Flask 接收到请求后,会从 cookie 中提取出存储的字符串,并将其拆分成数据部分和签名部分。
-
验证签名:
- 使用
app.secret_key重新计算接收到的数据的签名,然后将计算出的签名与接收到的签名进行匹配。 - 如果签名匹配,则说明数据未被篡改,验证成功。
- 如果签名不匹配,说明数据有可能被篡改,此时 Flask 会拒绝加载该 session 数据。
- 使用
-
还原数据:
- 一旦签名验证通过,Flask 会使用相同的序列化机制(通常是 JSON)将数据部分反序列化回一个 Python 字典对象,并将其加载到
session中以供请求处理时使用。
- 一旦签名验证通过,Flask 会使用相同的序列化机制(通常是 JSON)将数据部分反序列化回一个 Python 字典对象,并将其加载到
3、在 Flask 中,session 的行为类似于一个字典,你可以使用类似字典的方法和语法来操作它。具体来说,session 提供以下功能:
-
键值存取:
- 你可以使用键来存储和检索数据,例如
session['username'] = 'John'和username = session.get('username')。
- 你可以使用键来存储和检索数据,例如
-
键检查:
- 可以使用
'key' in session来检查某个键是否存在,例如if 'username' in session:。
- 可以使用
-
删除键:
- 使用
del session['key']或session.pop('key', None)来删除某个键的数据。
- 使用
-
字典方法使用:
- 支持一些标准的字典方法,比如
session.keys()、session.items()等。
- 支持一些标准的字典方法,比如
4、session 源码
1、流程
1 app.session_interface 默认是某个类的对象,以后全局对象 session,就是SecureCookieSessionInterface()的对象
2 请求来了,会执行这个对象的: open_session方法
3 请求走了,会执行这个对象的:save_session方法
4 找出上面讲的--》读源码--》
app.run()---->run_simple(地址, 端口, self可调用对象)--->self 是谁?就是 app
请求来了,就会执行 self可调用对象()--->app()---->对象加括号---》触发---》类的__call__
请求来了,就会执行flask类的 __call__--->self.wsgi_app(environ, start_response)
def wsgi_app(self, environ: dict, start_response: t.Callable) -> t.Any:
ctx = self.request_context(environ)
try:
try:
ctx.push()
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.handle_exception(e)
except: # noqa: B001
error = sys.exc_info()[1]
raise
return response(environ, start_response)
finally:
if "werkzeug.debug.preserve_context" in environ:
environ["werkzeug.debug.preserve_context"](_cv_app.get())
environ["werkzeug.debug.preserve_context"](_cv_request.get())
if error is not None and self.should_ignore_error(error):
error = None
ctx.pop(error)
# 5 ctx.push()--->有如下代码
if self.session is None: # 请求刚来,是空的
#session_interface 就是SecureCookieSessionInterface类的对象
self.session = session_interface.open_session(self.app, self.request)
if self.session is None:
self.session = session_interface.make_null_session(self.app)
#6 SecureCookieSessionInterface类的open_session
from flask.sessions import SecureCookieSessionInterface
open_session步骤:
1 会去cookie中取出session对应的 三段式的字符串
2 解密,校验签名---》把这个数据--》放到 session对象中
save_session步骤:
1 从session取出数据
2 加密,签名---放到cookie中
3 返回给前端
关键部分:
app.run()---->run_simple(地址, 端口, self可调用对象)--->self 是谁?就是 app
请求来了,就会执行 self可调用对象()--->app()---->对象加括号---> 触发---> 类的__call__
请求来了,就会执行flask类的 __call__---> self.wsgi_app(environ, start_response)
2、
'''
1 视图函数中,咱们 session[name]=lqz
2 请求走了,会触发save_session
3 触发save_session时:
把session中的数据,加密签名得到三段字符串
放到cookie中,放到了浏览器中
'''
def save_session(
self, app: Flask, session: SessionMixin, response: Response
) -> None:
name = self.get_cookie_name(app)
domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app)
secure = self.get_cookie_secure(app)
samesite = self.get_cookie_samesite(app)
httponly = self.get_cookie_httponly(app)
# Add a "Vary: Cookie" header if the session was accessed at all.
if session.accessed:
response.vary.add("Cookie")
# If the session is modified to be empty, remove the cookie.
# If the session is empty, return without setting the cookie.
if not session:
if session.modified:
response.delete_cookie(
name,
domain=domain,
path=path,
secure=secure,
samesite=samesite,
httponly=httponly,
)
response.vary.add("Cookie")
return
if not self.should_set_cookie(app, session):
return
expires = self.get_expiration_time(app, session)
# 加密,签名---放到cookie中
val = self.get_signing_serializer(app).dumps(dict(session)) # type: ignore
response.set_cookie(
name,
val, # type: ignore
expires=expires,
httponly=httponly,
domain=domain,
path=path,
secure=secure,
samesite=samesite,
)
response.vary.add("Cookie")
3、
'''
1 请求来了,request中带着cookie(也有可能没有)
2 根据 session这个key,取出value,如果有,就是 我们当时生成的三段
3 字典=s.loads(value) 把内容验签,解密出来,转成了字典
4 把这个字典转到 session对象中
5 以后视图函数中 session[name] 就能取到当时你放的name
'''
def open_session(self, app: Flask, request: Request) -> SecureCookieSession | None:
s = self.get_signing_serializer(app)
if s is None:
return None
# val 就是那三段
val = request.cookies.get(self.get_cookie_name(app))
if not val:
# 如果没有带cookie,造个空session,返回
return self.session_class()
max_age = int(app.permanent_session_lifetime.total_seconds())
try:
data = s.loads(val, max_age=max_age)
# 解密,校验签名---》把这个数据--》放到 session对象中
return self.session_class(data)
except BadSignature:
return self.session_class()
5、自定义session的存储
1、通过扩展
# 之前用过flask的session---》加密后,放到了cookie中
# 我们想把session放到redis中 ---》django的session放到djagno-session表中
# 借助于第三方:flask-session 可以放在数据库中,文件中,redis中 以redis为例
pip install flask-session
##### 方式一
# from flask_session import RedisSessionInterface
# import redis
# conn = redis.Redis(host='127.0.0.1', port=6379)
# app.session_interface = RedisSessionInterface(conn, 'session')
##### 方式二:(继承第三方 ,通用方案:第三方提供的一个类,把app包裹一下,这个第三方就能用了)
from flask_session import Session
from redis import Redis
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_REDIS'] = Redis(host='127.0.0.1', port='6379')
# app.config['SESSION_KEY_PREFIX'] = 'lqz' # 如果不写,默认以:SESSION_COOKIE_NAME 作为key
# app.config.from_pyfile('./settings.py')
Session(app) # 核心跟第一种方式一模一样
# 两个点
- session的前缀如果不传,默认:config.setdefault('SESSION_KEY_PREFIX', 'session:')
- session的key理应该是 uuid,但是咱们还是三段式--》之前浏览器器中存在了
2、通过继承 SessionInterface
from flask import Flask, request, session
from flask.sessions import SessionMixin, SessionInterface
from werkzeug.datastructures import CallbackDict
import mysql.connector
import uuid
import pickle
import time
class MySQLSession(CallbackDict, SessionMixin):
def __init__(self, initial=None, session_id=None):
def on_update(self):
self.modified = True
CallbackDict.__init__(self, initial, on_update)
self.session_id = session_id
self.modified = False
class MySQLSessionInterface(SessionInterface):
def __init__(self, db_config):
self.db_config = db_config
def get_db_connection(self):
return mysql.connector.connect(**self.db_config)
def open_session(self, app, request):
session_id = request.cookies.get(app.session_cookie_name)
if not session_id:
session_id = str(uuid.uuid4())
return MySQLSession(session_id=session_id)
connection = self.get_db_connection()
cursor = connection.cursor()
cursor.execute('SELECT data FROM sessions WHERE session_id = %s', (session_id,))
result = cursor.fetchone()
connection.close()
if result is not None:
data = pickle.loads(result[0])
return MySQLSession(data, session_id=session_id)
return MySQLSession(session_id=session_id)
def save_session(self, app, session, response):
domain = self.get_cookie_domain(app)
if not session:
response.delete_cookie(app.session_cookie_name, domain=domain)
return
connection = self.get_db_connection()
cursor = connection.cursor()
session_data = pickle.dumps(dict(session))
expiration = int(time.time()) + app.permanent_session_lifetime.total_seconds()
cursor.execute(
'REPLACE INTO sessions (session_id, data, expiration) VALUES (%s, %s, %s)',
(session.session_id, session_data, expiration)
)
connection.commit()
connection.close()
response.set_cookie(app.session_cookie_name, session.session_id, httponly=True, domain=domain)
# Flask应用设置
app = Flask(__name__)
app.secret_key = 'your_secret_key'
app.config['PERMANENT_SESSION_LIFETIME'] = 3600 # 设置会话过期时间(秒)
db_config = {
'user': 'your_db_username',
'password': 'your_db_password',
'host': 'localhost',
'database': 'flask_session_db'
}
app.session_interface = MySQLSessionInterface(db_config=db_config)
@app.route('/')
def index():
session['username'] = 'test_user'
return f"Hello, {session.get('username', 'Guest')}!"
if __name__ == '__main__':
app.run()

浙公网安备 33010602011771号