【Flask Web】开发实战——第二章学习笔记
2.1 请求响应循环
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中处理请求
-
路由匹配
- app.url_map中存储了对应关系
-
设置监听的HTTP方法
@app.route('/hello', methods=['GET',...])
-
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
2.3.4 Cookie
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数据加密存储。
-
设置程序密钥
SECRET_KEY='xxx' # 写在.env文件中 ---------------------------------------- import os # ... app.secret_key = os.getenv('SECRET_KEY', 'default string') # getenv实现获取
-
模拟用户认证
使用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 重定向回上一个页面
-
获取上一个页面的URL
-
HTTP refer
return redirec (request.referrer or url_for( ' hello ' )) # or 来防止自动清除或修改了referrer字段
-
查询参数
''' 在URL中手动加入包含当前页面URL的查询参数,这个参数一般命名为next ''' return redirect(request.args.get('next', url_for('hello')))
-
复合使用
def redirect_back(default='hello', **kwargs): for target in request.args.get('next'), request.referer: if target: return redirect(target) return redirect(default, **kwargs)
-
-
对URL进行安全验证
-
创建了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发送异步请求
- AJAX指异步Javascript 和 XML
- 详细使用方法在后续的实践部分更新
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安全规范
- 注入攻击
- 攻击原理:将用户传入的数据作为参数使用字符串拼接的方式插入到查询中
- 主要防范方法
- ORM
- 验证输入类型
- 参数化查询
- 转义特殊字符
- XSS攻击 跨站脚本攻击
- 代码注入网站中,一旦访问就会被注入恶意代码
- 反射型XSS攻击和存储型XSS攻击
- 主要防范措施
-
HTML转义
from jinja2 import escape @app.route('/hello') def hello(): name = request.args.get('name') response = '<hl>Hello , %s!</hl>' % escape(name)
-
验证用户输入
-
- CSRF攻击 跨站请求伪造
- 攻击原理:利用用户在浏览器中保存的认证消息,向对应的站点发送伪造请求
- 防范措施:
- 正确使用HTTP方法:GET、POST
- CSRF令牌检验:Flask-SeaSurf 、CSRFProtect
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?