flask之请求对象、响应对象、cookies和session

一、请求对象

1、Flask 的 request 基于 Werkzeug 

代表 HTTP 请求。request 对象包含许多方法和属性,用于处理请求数据。

https://werkzeug.palletsprojects.com/en/stable/wrappers/

Werkzeug 是一个用于 WSGI 应用的全面且强大的 Python 工具库。它在 Python Web 开发中广受欢迎,并常常与 Flask(一个流行的微框架)一起使用。Werkzeug 提供了构建、部署和管理 Web 应用程序所需的许多底层功能。

主要特性

  1. WSGI 工具:Werkzeug 提供了用于构建 WSGI(Web Server Gateway Interface)应用的多种实用工具和实用程序,帮助开发者更轻松地处理请求和响应的基本操作。

  2. HTTP 请求与响应处理:Werkzeug 提供对 HTTP 请求和响应对象的强大封装,使得处理 HTTP 请求(如 GET、POST 参数、文件上传)和响应(如设置状态码、cookie、内容类型等)更加简单方便。

  3. 路由:Werkzeug 提供了灵活的 URL 路由系统,允许定义和解析 URL 模式,这对构建复杂的 Web 应用非常有帮助。

  4. 调试和开发服务器:Werkzeug 包含一个用于开发的内置调试器和轻量级 WSGI 服务器,支持自动代码重新加载和详细的错误日志信息,帮助开发过程中快速发现和定位问题。

  5. Sessions(会话管理):Werkzeug 提供了基本的会话管理支持,允许开发者在客户端和服务器之间维护用户会话状态。

  6. 安全性功能:Werkzeug 提供了一些基本的安全性功能,如请求数据的 escaping,帮助防范常见的 Web 安全问题。

  7. Middleware(中间件):Werkzeug 支持中间件模式,可以在请求和响应生命周期的不同阶段插入自定义逻辑,为 WSGI 应用开发提供更多扩展可能。

  8. 实用工具:包括 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: 返回请求方法(如 GETPOSTPUTDELETE)。
  • 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。这个密钥是至关重要的,因为它用于以下目的:

存储流程:

  1. 序列化数据

    • 将 session 中的字典数据序列化为字符串。Flask 默认使用其 securecookie 模块,以 JSON 格式存储会话数据。
  2. 生成签名

    • 使用 app.secret_key 生成一个哈希签名。Flask 的签名过程通常基于 HMAC Hash−basedMessageAuthenticationCode,这是一种使用加密哈希函数(如 SHA-256)结合一个密钥来生成签名的方式。
  3. 构建 cookie 值

    • 将序列化的数据与生成的签名组合成一个单个字符串,格式通常类似于 data.signature
  4. 发送给客户端

    • 上述组合字符串被存储为一个 cookie,并发送到客户端浏览器。浏览器在每次请求时都会将此 cookie 返回给服务器。

解析流程:

  1. 接收请求

    • 客户端浏览器在发送请求时将之前存储的 session cookie 包含在请求头中。
  2. 拆分数据和签名

    • Flask 接收到请求后,会从 cookie 中提取出存储的字符串,并将其拆分成数据部分和签名部分。
  3. 验证签名

    • 使用 app.secret_key 重新计算接收到的数据的签名,然后将计算出的签名与接收到的签名进行匹配。
    • 如果签名匹配,则说明数据未被篡改,验证成功。
    • 如果签名不匹配,说明数据有可能被篡改,此时 Flask 会拒绝加载该 session 数据。
  4. 还原数据

    • 一旦签名验证通过,Flask 会使用相同的序列化机制(通常是 JSON)将数据部分反序列化回一个 Python 字典对象,并将其加载到 session 中以供请求处理时使用。

3、在 Flask 中,session 的行为类似于一个字典,你可以使用类似字典的方法和语法来操作它。具体来说,session 提供以下功能:

  1. 键值存取

    • 你可以使用键来存储和检索数据,例如 session['username'] = 'John' 和 username = session.get('username')
  2. 键检查

    • 可以使用 'key' in session 来检查某个键是否存在,例如 if 'username' in session:
  3. 删除键

    • 使用 del session['key'] 或 session.pop('key', None) 来删除某个键的数据。
  4. 字典方法使用

    • 支持一些标准的字典方法,比如 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、save_session

'''
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、open_session

'''
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扩展

# 之前用过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()

  

 

posted @ 2024-10-31 15:41  凡人半睁眼  阅读(4)  评论(0编辑  收藏  举报