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