flask之请求对象、响应对象、cookies和session
一、请求对象
1、Flask 的 request
基于 Werkzeug
代表 HTTP 请求。request
对象包含许多方法和属性,用于处理请求数据。
1 | 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、简易代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 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方法
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | ''' 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | ''' 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、通过
扩展1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | # 之前用过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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | 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() |