Flask 请求与响应新手实战指南
在 Web 开发中,请求(Request) 代表用户"要什么",响应(Response) 代表服务器"给什么"。理解这二者的交互是掌握 Flask 的基石。本文将手把手带你从零开始,深入剖析 Flask 中请求与响应的每一个细节,并给出可直接落地的代码范例。
WEB项目地址:演示地址
① 开发环境搭建与首个 Flask 应用启动
创建项目与安装依赖
打开终端,执行以下命令:
bash
# 创建项目文件夹
mkdir flask_request_demo
cd flask_request_demo
# 安装 Flask
pip install flask
编写最小启动应用
在项目根目录下创建 app.py 文件,写入以下代码:
python
from flask import Flask
# 创建 Flask 核心对象
app = Flask(__name__)
# 定义根路由
@app.route('/')
def hello():
return 'Hello, Flask! 请求与响应实战开始!'
if __name__ == '__main__':
# host='0.0.0.0' 允许局域网访问,port 指定端口
app.run(host='0.0.0.0', port=5000, debug=True)
运行 python app.py,在浏览器访问 http://127.0.0.1:5000,看到欢迎语即代表环境就绪。
💡 新手提示 :
debug=True开启后,修改代码保存即可自动重启服务,无需手动终止重新运行。
② 解析 HTTP 请求对象的核心属性与方法
在 Flask 中,所有来自客户端的信息都封装在全局对象 request 中。使用前需要从 flask 导入。
常用核心属性一览表
| 属性/方法 | 作用 | 使用场景 |
|---|---|---|
request.method |
获取 HTTP 方法(GET/POST/PUT 等) | 判断请求类型 |
request.url |
完整的请求 URL | 日志记录、重定向来源 |
request.path |
URL 中的路径部分(不含域名) | 路由匹配 |
request.args |
获取 URL 查询字符串参数(?key=value) |
GET 请求参数 |
request.form |
获取 POST 请求的表单数据 | 登录、注册表单 |
request.json / request.get_json() |
获取 JSON 格式的请求体 | API 接口开发 |
request.files |
获取上传的文件对象 | 头像、附件上传 |
request.headers |
获取请求头信息(字典形式) | 认证 Token、User-Agent |
request.cookies |
获取客户端携带的 Cookie | 记住登录状态 |
request.data |
获取原始请求体字节数据 | 处理非表单、非 JSON 的原始数据 |
实战演示:打印请求详情
创建 demo_request.py:
python
from flask import Flask, request
app = Flask(__name__)
@app.route('/debug', methods=['GET', 'POST'])
def debug_request():
# 1. 获取请求方法
method = request.method
# 2. 获取完整 URL 和路径
full_url = request.url
path = request.path
# 3. 获取请求头中的 User-Agent(浏览器信息)
user_agent = request.headers.get('User-Agent')
# 4. 获取客户端 IP
client_ip = request.remote_addr
# 5. 获取查询参数(GET 传参)
args = request.args.to_dict() # 转换为普通字典
return f"""
<h3>请求调试信息</h3>
<p>方法: {method}</p>
<p>路径: {path}</p>
<p>完整 URL: {full_url}</p>
<p>User-Agent: {user_agent}</p>
<p>客户端 IP: {client_ip}</p>
<p>查询参数: {args}</p>
"""
访问 http://127.0.0.1:5000/debug?name=zhang&age=25,你会看到页面动态展示了所有请求细节。
⚠️ 重要 :
request在视图函数外部无法使用,它只在当前请求上下文中生效。
③ 构建多样化响应内容与状态码设置
视图函数的返回值就是响应对象。Flask 支持多种响应构建方式。
方式一:直接返回字符串(最常用)
python
@app.route('/text')
def return_text():
# 默认状态码 200,Content-Type 为 text/html
return '<h1>这是一段 HTML 文本</h1>'
方式二:返回元组(带状态码和响应头)
python
@app.route('/created')
def return_created():
# 返回 (内容, 状态码, 响应头字典)
return '用户创建成功', 201, {'Location': '/users/1'}
方式三:使用 make_response() 构建复杂响应
当需要操作 Cookie 或自定义响应头时,推荐使用 make_response:
python
from flask import make_response
@app.route('/custom_response')
def custom_response():
# 1. 创建响应对象
resp = make_response('<h2>这是一个自定义响应</h2>')
# 2. 设置状态码(默认为 200)
resp.status_code = 202
# 3. 设置自定义响应头
resp.headers['X-Server-Name'] = 'Flask-Demo'
resp.headers['Cache-Control'] = 'no-cache'
# 4. 设置 Cookie(有效期 7 天)
resp.set_cookie('user_preference', 'dark_mode', max_age=7*24*3600)
return resp
常见的 HTTP 状态码
| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 200 | OK(成功) | 请求处理成功 |
| 201 | Created(已创建) | 新增数据成功(如注册) |
| 204 | No Content(无内容) | 删除操作成功 |
| 301/302 | 重定向 | 页面跳转 |
| 400 | Bad Request(错误请求) | 参数校验不通过 |
| 401 | Unauthorized(未授权) | 未登录访问需权限页面 |
| 404 | Not Found(未找到) | 资源不存在 |
| 500 | Internal Server Error(内部错误) | 服务器代码异常 |
返回 JSON 数据(前后端分离必备)
使用 Flask 内置的 jsonify() 函数,自动设置正确的 Content-Type: application/json:
python
from flask import jsonify
@app.route('/api/user/<int:uid>')
def api_user(uid):
user_data = {
'id': uid,
'name': '王小明',
'email': 'xm.wang@example.com'
}
return jsonify(user_data) # 自动转为 JSON 字符串并返回
④ 处理表单数据与文件上传的完整流程
4.1 处理普通表单数据(POST 请求)
先创建一个包含表单的 HTML 页面(放在 templates/form.html):
html
<!DOCTYPE html>
<html>
<body>
<h2>用户注册</h2>
<form action="/register" method="POST">
<label>用户名:</label><input type="text" name="username"><br>
<label>密码:</label><input type="password" name="password"><br>
<label>年龄:</label><input type="number" name="age"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
后端处理逻辑:
python
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'GET':
# GET 请求:显示表单页面
return render_template('form.html')
# POST 请求:处理表单提交
# 使用 .get() 安全获取,避免键不存在时报错
username = request.form.get('username', '游客')
password = request.form.get('password', '')
age = request.form.get('age', 0)
# 简单校验
if not username or len(username) < 3:
return '用户名至少 3 个字符', 400
return f'注册成功!欢迎 {username},年龄 {age}'
4.2 文件上传实战
创建一个文件上传表单(templates/upload.html):
html
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="avatar">
<input type="submit" value="上传头像">
</form>
后端上传处理(安全注意事项见代码注释):
python
import os
from werkzeug.utils import secure_filename
# 配置上传文件夹和允许的扩展名
UPLOAD_FOLDER = 'uploads'
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 2 * 1024 * 1024 # 限制文件最大为 2MB
# 确保上传目录存在
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'GET':
return render_template('upload.html')
# 检查是否有文件被上传
if 'avatar' not in request.files:
return '没有找到文件字段', 400
file = request.files['avatar']
# 如果用户未选择文件,file.filename 为空字符串
if file.filename == '':
return '未选择任何文件', 400
# 校验文件扩展名
if not allowed_file(file.filename):
return '仅支持 png, jpg, jpeg, gif 格式', 400
# 安全处理文件名(防止恶意路径攻击)
safe_filename = secure_filename(file.filename)
# 保存文件到 uploads 文件夹
save_path = os.path.join(app.config['UPLOAD_FOLDER'], safe_filename)
file.save(save_path)
return f'文件上传成功!保存为:{save_path}'
🔒 安全核心 :
secure_filename()会过滤掉../等危险字符,务必使用!同时必须限制MAX_CONTENT_LENGTH防止超大文件耗尽服务器磁盘。
⑤ 实现动态路由参数提取与类型转换
动态路由参数是 URL 的一部分(如 /user/1001 中的 1001),它们通过视图函数的形参 直接传入,与查询字符串(?key=val)有本质区别。
路由参数与查询字符串对比
| 对比项 | 路由参数(Path Params) | 查询字符串(Query String) |
|---|---|---|
| 位置 | 写在 URL 路径中 | 写在 ? 之后 |
| 示例 | /user/1001 |
/user?id=1001 |
| 获取方式 | 视图函数形参 def user(user_id) |
request.args.get('id') |
| 适用场景 | 资源定位(如 RESTful API) | 过滤、排序、分页 |
类型转换器详解
Flask 内置的转换器让参数自动变成指定类型,省去手动转换的麻烦。
python
@app.route('/user/<int:user_id>')
def get_user(user_id):
# user_id 已经是 int 类型,可以直接参与运算
return f'查询到用户 ID:{user_id},类型是 {type(user_id).__name__}'
@app.route('/product/<string:product_code>')
def get_product(product_code):
# 默认就是 string,可省略
return f'商品编码:{product_code}'
@app.route('/path/<path:sub_path>')
def get_sub_path(sub_path):
# path 可以匹配斜杠,如 /path/a/b/c 会得到 'a/b/c'
return f'子路径:{sub_path}'
@app.route('/float/<float:price>')
def get_price(price):
return f'价格:{price:.2f}'
同时使用路由参数和查询参数
python
@app.route('/orders/<int:order_id>')
def get_orders(order_id):
# 路由参数获取订单 ID
# 查询参数获取分页信息
page = request.args.get('page', default=1, type=int)
size = request.args.get('size', default=10, type=int)
return f'订单 {order_id} 的第 {page} 页,每页 {size} 条'
访问 /orders/888?page=3&size=5 会得到:订单 888 的第 3 页,每页 5 条。
⑥ 自定义错误页面与异常捕获机制
在生产环境中,默认的白色错误页面对用户极不友好。Flask 允许我们全局拦截并美化错误。
主动终止请求并抛出错误
使用 abort() 函数可以随时中断请求并返回指定状态码:
python
from flask import abort
@app.route('/dashboard')
def dashboard():
# 模拟检查登录状态(此处假设未登录)
is_logged_in = False
if not is_logged_in:
# 返回 401 并终止执行
abort(401, description='请先登录再访问仪表盘')
return '这是仪表盘'
全局捕获并自定义错误页面
通过 @app.errorhandler(状态码) 装饰器统一处理:
python
from flask import render_template
# 捕获 404 错误
@app.errorhandler(404)
def not_found_error(error):
# 返回自定义 HTML 页面,状态码必须明确为 404
return render_template('404.html'), 404
# 捕获 500 内部服务器错误
@app.errorhandler(500)
def internal_error(error):
return render_template('500.html'), 500
# 捕获 401 未授权
@app.errorhandler(401)
def unauthorized_error(error):
# 也可以返回 JSON 格式(适用于前后端分离)
return jsonify({'code': 401, 'msg': str(error)}), 401
捕获任意异常(兜底方案)
如果要捕获所有未预料的异常,可以这样做:
python
@app.errorhandler(Exception)
def handle_all_exceptions(error):
# 记录错误日志(此处省略日志代码)
return '系统维护中,请稍后再试', 500
⚠️ 注意 :
render_template()后必须加上状态码(如, 404),否则 Flask 会默认返回 200。
⑦ 使用重定向与会话管理优化用户交互
7.1 重定向(Redirect)
redirect() 用于将用户跳转到其他页面,url_for() 根据函数名反向生成 URL,二者常搭配使用。
python
from flask import redirect, url_for
@app.route('/')
def index():
# 重定向到 /login 页面
return redirect(url_for('login_page'))
@app.route('/login')
def login_page():
return '这里是登录页面,请登录'
url_for() 也支持传递动态路由参数:
python
@app.route('/profile/<int:uid>')
def profile(uid):
return f'用户 {uid} 的个人主页'
@app.route('/go_profile')
def go_profile():
# 生成 /profile/1001 的 URL
target_url = url_for('profile', uid=1001)
return redirect(target_url)
7.2 会话管理(Session)
Session 服务端存储用户数据的机制,在 Flask 中通过加密 Cookie 实现,使用前必须配置 SECRET_KEY。
基础用法
python
from flask import session
import os
# 配置秘钥(务必使用随机字符串,切勿硬编码在代码中提交到 Git)
app.secret_key = os.urandom(24) # 开发环境
# 生产环境应从环境变量读取:app.secret_key = os.getenv('SECRET_KEY')
实现"记住登录状态"完整示例
python
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
# 模拟验证(实际应从数据库查询)
if username == 'admin' and password == '123456':
# 将用户信息写入 Session
session['user_id'] = 10001
session['username'] = username
# 设置永久有效期(默认 31 天)
session.permanent = True
return redirect(url_for('dashboard'))
else:
return '用户名或密码错误', 400
# GET 请求展示登录页
return render_template('login.html')
@app.route('/dashboard')
def dashboard():
# 检查 Session 中是否有用户信息
if 'username' in session:
return f'欢迎回来,{session["username"]}'
else:
return redirect(url_for('login'))
@app.route('/logout')
def logout():
# 清除 Session 中的用户数据(移除特定键)
session.pop('username', None)
session.pop('user_id', None)
# 或者清除整个 Session:session.clear()
return redirect(url_for('login'))
🔐 安全提示:
SECRET_KEY必须足够复杂且保密,生产环境建议使用环境变量。- 永远不要在前端 Session 中存储密码等敏感信息。
⑧ 常见请求报错分析与快速排查技巧
报错 1:请求数据获取失败(BadRequestKeyError)
现象 :BadRequestKeyError: 'key_name' not found in request
原因 :直接使用索引方式获取参数(如 request.form['username']),但该键不存在。
解决方案 :统一使用 .get() 方法,并设置合理的默认值。
python
# ❌ 错误写法(键不存在会报错)
username = request.form['username']
# ✅ 正确写法(键不存在返回 None 或默认值)
username = request.form.get('username')
username = request.form.get('username', 'default_user')
报错 2:HTTP 405 方法不允许
现象 :Method Not Allowed. The method is not allowed for the requested URL.
原因 :路由未在 methods 参数中包含当前请求的方法。
解决方案:显式声明所有可能用到的方法。
python
# ❌ 只能处理 GET,无法处理 POST
@app.route('/submit')
def submit():
pass
# ✅ 明确允许 GET 和 POST
@app.route('/submit', methods=['GET', 'POST'])
def submit():
pass
报错 3:文件上传超出大小限制(RequestEntityTooLarge)
现象 :413 Request Entity Too Large
原因 :上传的文件超过了 MAX_CONTENT_LENGTH 配置值。
解决方案:
- 调大配置(视业务需求而定)
- 前端增加文件大小校验,提前拦截用户
python
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 调整为 16MB
报错 4:JSON 解析失败(BadRequest)
现象 :Failed to decode JSON object: Expecting value: line 1 column 1 (char 0)
原因 :客户端发送的请求体不是合法的 JSON 格式,或忘记设置 Content-Type: application/json。
解决方案 :使用 try...except 优雅处理,避免程序崩溃。
python
@app.route('/api/data', methods=['POST'])
def handle_json():
try:
data = request.get_json()
if data is None:
return jsonify({'error': '请求体不是 JSON 格式'}), 400
# 正常处理逻辑
return jsonify({'status': 'ok'})
except Exception as e:
return jsonify({'error': f'JSON 解析失败: {str(e)}'}), 400
快速调试三板斧
-
打印到终端 :在代码中插入
print(request.method, request.args, request.form),查看控制台输出。 -
使用 Postman/cURL 模拟请求 :
bashcurl -X POST http://localhost:5000/upload -F "avatar=@local_image.jpg" -
查看 Flask 内置的异常堆栈 :开启
debug=True后,访问出错页面会显示详细的彩色错误追踪,直接指出哪一行代码出错。
总结
本文通过 8 个章节,系统梳理了 Flask 中请求与响应的全链路知识:
- 请求对象 :掌握了
request的args、form、json、files等核心属性 - 响应构建 :学会了字符串、元组、
make_response、jsonify多种返回方式 - 动态路由:区分了路由参数与查询参数的差异,灵活使用类型转换器
- 文件与表单:落地了安全上传文件、表单校验的完整方案
- 错误与重定向:实现了优雅的自定义错误页面、登录跳转与会话保持
请求与响应是前后端交互的"语言"。掌握好它们,你就拥有了开发任何 Web 功能的基础能力。建议你亲手敲一遍本文的全部代码,体验从接收请求到返回响应的完整闭环。祝你学习顺利!🚀