【Flask Web】开发实战——第二章学习笔记

2.1 请求响应循环

image

Flask工作流程

2.2 HTTP请求

2.2.1 请求报文

方法 说明 方法 说明
GET 获取资源 DELETE 删除资源
POST 传输数据 HEAD 获得报文首部
PUT 传输文件 OPTIONS 询问支持的方法

2.2.2 Request对象

属性 属性
path u'/hello' base_url http://helloflask.com/hello
full_path u'/hello?name=Grey' url http://helloflask.com/hello?name=Grey
host u'helloflask,com¹ url_root http://helloflask.com/
host_url http://helloflask.com/
属性/方法 说   明
args Werkzeug的ImmutableMultiDict对象。存储解析后的查询字符串,可通过字典方式获取键值。如果你想获取未解析的原生查询字符串,可以使用query_string属性
blueprint 当前蓝本的名称,关于蓝本的概念在本书第二部分会详细介绍
cookies 一个包含所有随请求提交的cookies的字典
data 包含字符串形式的请求数据
endpoint 与当前请求相匹配的端点值
files Werkzeug的MultiDict对象,包含所有上传文件,可以使用字典的形式获取文件。使用的键为文件input标签中的name属性值,对应的值为Werkzeug的FileStorage对象,可以调用save()方法并传入保存路径来保存文件
form Werkzeug的ImmutableMultiDict对象。与files类似,包含解析后的表单数据。表单字段值通过input标签的name属性值作为键获取
values Werkzeug的CombinedMultiDict对象,结合了args和form属性的值
get_data(cache=True,as_text=False,parse_from_data=False) 获取请求中的数据,默认读取为字节字符串(bytestring),将as_text设为True则返回值将是解码后的unicode字符串
get_json(self,force=False,silent=False,cache=True) 作为JSON解析并返回数据,如果MIME类型不是JSON,返回None(除非force设为True);解析出错则抛出Werkzeug提供的BadRequest异常(如果未开  启调试模式,则返回400错误响应,后面会详细介绍),如果silent设为True则返  回None;cache设置是否缓存解析后的JSON数据
headers 一个Werkzeug的EnvironHeaders对象,包含首部字段,可以以字典的形式操作
is_json 通过MIME类型判断是否为JSON数据,返回布尔值
json 包含解析后的JSON数据,内部调用get_json().可通过字典的方式获取键值
method 请求的HTTP方法
referrer 请求发起的源URL,即referer
scheme 请求的URL模式(http或https)
user_agent 用户代理(User Agent,UA),包含了用户的客户端类型,操作系统类型等信息

2.2.3 在Flask中处理请求

  1. 路由匹配

    1. app.url_map中存储了对应关系
  2. 设置监听的HTTP方法

    @app.route('/hello', methods=['GET',...])
    
  3. URL处理

转  换  器 说   明
string 不包含斜线的字符串(默认值)
int 整型
float 浮点数
path 包含斜线的字符串。static路由的URL规则中的filename变量就使用了这个转换器
any 匹配一系列给定值中的一个元素
uuid UUID字符串

将变量转换为相应的数据类型

@app.route('/color/<any(blue, white, red):color')

如果color部分替换成any转换器中设置的可选值外的任意字符都会返回一个404错误响应。

2.2.4 请求钩子

当需要对请求进行预处理 preprocessing和后处理 postprocessing,这时候可以用Flask提供的一些请求钩子 Hook,用来注册在请求处理的不同阶段执行的处理函数 (或称为回调函数,即Callback

请求钩子使用装饰器实现,通过程序实例app的调用,

钩   子 说   明
before_first_request 注册一个函数,在处理第一个请求前运行
before_request 注册一个函数,在处理每个请求前运行
after_request 注册一个函数,如果没有未处理的异常抛出,会在每个请求结束后运行
teardown_request 注册一个函数,即使有未处理的异常抛出,会在每个请求结束后运行。如果发生异常,会传入异常对象作为参数到注册的函数中
after_this_request 在视图函数内注册一个函数,会在这个请求结束后运行
@app.before_request
def do_something():
    pass

这部分修饰器可以用来用户认证、记录请求日志以及确保数据库连接在请求结束时被正确关闭。

一个简单的应用实例

from flask import Flask, request, jsonify

app = Flask(__name__)

# 假设的用户认证信息
valid_users = {"john": "password123"}

# before_request钩子:简单用户认证
@app.before_request
def check_authentication():
    # 检查特定路由是否需要认证
    if request.endpoint in ['protected']:
        auth = request.authorization
        if not auth or not (auth.username in valid_users and auth.password == valid_users[auth.username]):
            return jsonify({"message": "Authentication required"}), 401

# after_request钩子:记录请求日志
@app.after_request
def log_request(response):
    with open("request_log.txt", "a") as logfile:
        logfile.write(f"{request.method} {request.path} - {response.status_code}\n")
    return response

# teardown_request钩子:模拟关闭数据库连接
@app.teardown_request
def close_db_connection(exception=None):
    print("Closing database connection (if any).")
# 注意:此处仅为示例,实际应用中应替换为真实数据库连接关闭逻辑

app.teardown_request(close_db_connection)

# 示例路由
@app.route('/')
def hello_world():
    return 'Hello, World!'

@app.route('/protected')
def protected_route():
    return jsonify({"message": "Welcome to the protected route!"})

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

2.3 HTTP响应

2.3.1 响应报文

类   型 状态码 原因短语(用于解释状态码) 说   明
成功 200 OK 请求被正常处理
201 Created 请求被处理,并创建了一个新资源
204 No Content 请求处理成功,但无内容返回
重定向 301 Moved Permanently 永久重定向
302 Found 临时性重定向
304 Not Modified 请求的资源未被修改,重定向到缓存的资源
客户端错误 400 Bad Request 表示请求无效,即请求报文中存在错误
401 Unauthorized 类似403,表示请求的资源需要获取授权信息,在浏览器中会弹出认证弹窗
403 Forbidden 表示请求的资源被服务器拒绝访问
404 Not Found 表示服务器上无法找到请求的资源或URL无效
服务器端错误 500 Internal Server Error 服务器内部发生错误

2.3.2 在Flask中生成响应

响应在Flask中使用Response对象表示,响应报文中大部分内容由服务器处理,大多数情况下,我们只负责返回主题内容。

如果找到路由对应的资源,则调用对应的视图函数,返回值构成了响应报文的主题内容,正确返回时状态码默认为200。

Flask会调用make_response()方法将视图函数返回值转换为响应对象

@app.xxx
def xxx():
    ...
    # 视图函数最多可以返回由三个元素组成的元组:响应主体、状态码、首部字段
    return '<h1>hello</h1>'  # 普通响应
    return '<h1>hello, boy</h1>', 201  # 指定状态码
    return '', 302, {'Location':'http://www.example.com'}
    
# 重定向,默认状态码 302,如果要修改状态码,则第二个参数或关键字code
return redirect('http://xxx.com')
# 错误响应,abort()函数被调用是,该函数之后的代码不会被执行
abort(404)  # 返回404错误响应
    

2.3.3 响应格式

不同的相应数据需要设置不同的MIME类型,MIME类型在首部的Content-Type字段中定义

MIME 类型列表参照:https://www.iana.org/assignments

如果想使用其他MIME类型,使用Flask提供的 make_response() 方法生成响应对象,传入响应的主体作为参数

from flask import make_response

@app.route('/foo')
def foo():
    response = make_response('Hello, World!')
    response.mimetype = 'text/plain'
    return response

set_cookie()方法

from flask import Flask, make_response
...
@app.route('/set/<name>')
def set_cookie(name):
    response = make_response(redirect(url_for('hello')))
    response.set_cookie('name', name)  # 用法在这里
    return response

需要对敏感的Cookie内容进行加密,Flask提供了session对象来将Cookie数据加密存储。

  1. 设置程序密钥

    SECRET_KEY='xxx'  # 写在.env文件中
    
    ----------------------------------------
    
    import os
    # ...
    app.secret_key = os.getenv('SECRET_KEY', 'default string')
    # getenv实现获取
    
  2. 模拟用户认证

    使用session模拟用户的认证功能

    @app.route('/login')
    def login():
        session['logged_in'] = True  # 写入session
        return redirect(url_for('hello'))
    

    使用session对象添加cookie时,数据会使用程序的密钥进行签名,加密后的数据存储在一块名为session的Cookie里

    Content部分对应的加密处理后生成的session值。

    from flask import session
    
    @app.route('/logout')
    def logout():
        if 'logged_in' in session:
            session.pop['logged_in']
        return redirect(url_for('hello'))
    

2.4 Flask上下文

程序上下文 application context:记录了程序的状态和一些数据信息

请求上下文 request context:记录了包含请求额各种信息

2.4.1 上下文全局变量

视图函数里的request就是一个全局变量,这是因为Flask会在每个请求产生后自动激活当前请求的上下文,激活后,request被临时全局可访问,每个请求结束后,Flask就销毁对应的请求上下文。

在多线程服务器中每个request只在自身的线程内是全局的,Flask通过本地线程(thread local)技术将请求对象在特定的线程和请求中全局可访问。

变  量  名 上下文类别 说   明
current_app 程序上下文 指向处理请求的当前程序实例
g 程序上下文 替代Python的全局变量用法,确保仅在当前请求中可用。用于存储全局数据,每次请求都会重设
request 请求上下文 封装客户端发出的请求报文数据
session 请求上下文 用于记住请求之间的数据,通过签名的Cookie实现

对于current_app,程序也会有等多个程序实例的情况,为了能获取对应的程序实例,而不是固定的某一个程序实例,就需要使用current_app

对于g,存储在程序上下文中,如果在某个视图中查询得到了某个参数的值,且后续的每个视图都需要这个值,那么可以利用 [g.xxx](http://g.xxx) = yyy 这个是直接可写的。同时,g支持get(),pop(),setdefault()

2.4.2 激活上下文

在特定的情况下,Flask会自动激活程序上下文

  • flask run启动程序时
  • app.run()启用程序时
  • 执行@app.cli.command() 注册的flask命令
  • flask shell 运行Python Shell

同样依赖于上下文的还有 url_for(), jsonify() 等函数,前者依赖请求上下文,后者内部调用使用了current_app变量。

程序上下文对象 app.app_context() 获取,使用with类似于文件打开的形式,也可以显示地通过push,pop来进行,程序上下文通过 test_request_context()方法创建

>>> from app import app
>>> from flask import current_app
>>> with app.app_context():
    ... current_app.name  # 此时的current_app已经具备程序上下文信息
    
''' 第二种实现方案 '''
>>> app_ctx = app.app_context()
>>> app_ctx.push()
>>> current_app.name
'app'  # 这里已经实现了加载程序上下文
>>> app_ctx.pop()

''' 请求上下文的创建 '''
>>> from app import app
>>> from flask import request
>>> with app.test_request_context('/hello'):
...    request.method  # 针对特定路由构造请求上下文
'GET'

2.4.3 上下文钩子

Flask为上下文提供了一个teardown_appcontext钩子,在每个请求结束后销毁数据库连接

@app.teardown_appcontext
def teardown_db(exception):  # 该装饰器注册的回调函数需要接收异常对象作为参数
    ...                      # 当请求正常处理时参数值将是None,该函数的返回值将被忽略 
    db.close()

2.5 HTTP进阶

2.5.1 重定向回上一个页面

  1. 获取上一个页面的URL

    1. HTTP refer

      return redirec (request.referrer or url_for( ' hello ' ))  # or 来防止自动清除或修改了referrer字段
      
    2. 查询参数

      ''' 在URL中手动加入包含当前页面URL的查询参数,这个参数一般命名为next '''
      return redirect(request.args.get('next', url_for('hello')))
      
    3. 复合使用

      def redirect_back(default='hello', **kwargs):
          for target in request.args.get('next'), request.referer:
              if target:
                  return redirect(target)
          return redirect(default, **kwargs)  
      
  2. 对URL进行安全验证

    1. 创建了URL验证函数 is_safe_url()

      from urlparse import urlparse, urljoin
      from flask import request
      
      def is_safe_url(target):
          ref_url = urlparse(request.host_url)
          test_url = urlparse(urljoin(request.host_url, target))
          return test_url.scheme in ('http', 'https') and ref_url.netloc == test_url.netloc
      ''' netloc是获取url的域名部分,该验证是确保只有程序内部的URL可以跳转 '''    
      

2.5.2 使用AJAX发送异步请求

  1. AJAX指异步Javascript 和 XML
  2. 详细使用方法在后续的实践部分更新

2.5.3 HTTP服务器端推送

在聊天室等环境下我们需要服务器端的主动推送 server push。这一系列技术被合称为HTTP Server Push (HTTP 服务器端推送)

名   称 说   明
传统轮询 polling 在特定的时间间隔内,客户端使用AJAX技术不断向服务器发起HTTP请求,然后获取新的数据并更新页面
长轮询 和传统轮询类似,但是如果服务器端没有返回数据,那就保持连接一直开启,直到有数据时才返回。取回数据后再次发送另一个请求
Server-Sent Events(SSE) SSE通过HTML5中的EventSource API实现。SSE会在客户端和服务器端建立一个单向的通道,客户端监听来自服务器端的数据,而服务器端可以在任意时间发送数据,两者建立类似订阅/发布的通信模式

2.5.4 Web安全规范

  1. 注入攻击
    1. 攻击原理:将用户传入的数据作为参数使用字符串拼接的方式插入到查询中
    2. 主要防范方法
      1. ORM
      2. 验证输入类型
      3. 参数化查询
      4. 转义特殊字符
  2. XSS攻击 跨站脚本攻击
    1. 代码注入网站中,一旦访问就会被注入恶意代码
    2. 反射型XSS攻击和存储型XSS攻击
    3. 主要防范措施
      1. HTML转义

        from jinja2 import escape
        
        @app.route('/hello')
        def hello():
            name = request.args.get('name')
            response =  '<hl>Hello , %s!</hl>' % escape(name)
        
      2. 验证用户输入

  3. CSRF攻击 跨站请求伪造
    1. 攻击原理:利用用户在浏览器中保存的认证消息,向对应的站点发送伪造请求
    2. 防范措施:
      1. 正确使用HTTP方法:GET、POST
      2. CSRF令牌检验:Flask-SeaSurf 、CSRFProtect
posted @   yyzmyyds  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示