解析请求和处理响应
前言
本章整理请求解析(URL 参数、查询参数、表单、JSON、文件、请求头、Cookie)、request 全局对象,以及规范 API 响应内容。
解析 URL 参数
from markupsafe import escape
@app.route("/user/<username>")
def show_user_profile(username):
return f"User {escape(username)}"
# 指定 url 参数为整数类型
@app.route("/post/<int:post_id>")
def show_post(post_id):
return f"Post {post_id}"
# 指定 url 参数为路径
@app.route("/path/<path:subpath>")
def show_subpath(subpath):
return f"Subpath {escape(subpath)}"
URL 中的参数类型:
| 参数类型 | 描述 |
|---|---|
| string | 字符串类型,可以接收除 / 以外的字符 |
| int | 整型 |
| float | 浮点类型 |
| path | 类似 string,但可以接收 / |
| uuid | UUID 类型 |
| any | 备选值中的任何一个 |
any 用法:用户名只能是 zhangsan、lisi、wangwu 中的其中一个
@app.route("/user/<any(zhangsan,lisi,wangwu):name>")
def get_user(name):
return f"username: {name}"
默认参数:比如默认分页为 1,让 URL 更简洁
@app.get("/posts/<int:post_id>")
@app.get("/posts/<int:post_id>/<int:page>")
def get_posts(post_id, page=1):
return f"Post {post_id} page {page}"
查询参数
from flask import request
# http://127.0.0.1:5000/query?username=zhangsan&group=student
@app.get("/query")
def query_args():
username = request.args.get("username")
group = request.args.get("group")
return f"username: {username}, group: {group}"
表单参数
username = request.form["username"]
password = request.form["password"]
提示
request.form 是 MultiDict,用 request.form.get("key") 更安全(不存在时返回 None 而不是抛异常)。
JSON 请求体
from flask import Flask, jsonify, request
app = Flask(__name__)
@app.post("/json")
def post_json():
req: dict = request.get_json(force=False, silent=False)
username = req.get("username")
password = req.get("password")
return jsonify({"u": username, "p": password})
if __name__ == "__main__":
app.run(host="127.0.0.1", port=5000)
request.get_json() 参数:
silent=True:解析失败时返回None而不是抛异常。force=True:即使 Content-Type 不是application/json也尝试解析。
文件上传
from flask import request
@app.route("/upload", methods=["GET", "POST"])
def upload_file():
if request.method == "POST":
f = request.files["the_file"]
f.save("/var/www/uploads/uploaded_file.txt")
请求头参数
from flask import Flask, request
app = Flask(__name__)
@app.route("/headers")
def get_headers():
user_agent = request.headers.get("User-Agent")
content_type = request.headers.get("Content-Type")
return f"User-Agent: {user_agent}, Content-Type: {content_type}"
Cookie 参数
from flask import Flask, request
app = Flask(__name__)
@app.route("/cookies")
def get_cookies():
session_id = request.cookies.get("session_id")
return f"Session ID: {session_id}"
request 对象
在 Flask 项目中,获取客户端提交的数据可以通过全局线程安全对象 flask.request 实现。
常用属性:
| 属性 | 说明 |
|---|---|
args | 解析后的客户端查询参数 |
query_string | 未解析的查询参数(bytes) |
url | 客户端请求的完整 URL |
base_url | 不含查询字符串的 URL |
host_url | 类似 base_url,带 scheme 和 host |
host | 客户端请求时用的域名 |
remote_addr | 客户端 IP |
headers | 请求头 |
json | 客户端发送的 JSON 请求体 |
is_secure | 是否 HTTPS/WSS 请求 |
path | 请求 URL 的 path 部分 |
method | 请求方法 |
authorization | 请求头中的 Authorization |
服务端示例:
from http import HTTPStatus
from flask import Flask, Response, request
app = Flask(__name__)
@app.post("/")
def index():
print(f"args: {request.args}")
print(f"query_string: {request.query_string}")
print(f"form: {request.form}")
print(f"url: {request.url}")
print(f"base_url: {request.base_url}")
print(f"path: {request.path}")
print(f"method: {request.method}")
print(f"headers: {request.headers}")
print(f"cookies: {request.cookies}")
print(f"host: {request.host}")
print(f"host_url: {request.host_url}")
print(f"remote_addr: {request.remote_addr}")
print(f"user_agent: {request.user_agent}")
print(f"is_secure: {request.is_secure}")
print(f"json: {request.json}")
print(f"auth: {request.authorization}")
return Response(mimetype="text/plain", response="Hello World", status=HTTPStatus.OK)
if __name__ == "__main__":
app.run(host="127.0.0.1", port=8000, debug=False)
客户端示例:
import json
import requests
url = "http://127.0.0.1:8000"
req_body = {"k1": "v1"}
headers = {"Content-Type": "application/json"}
cookies = {"username": "zhangsan"}
resp = requests.post(
url=f"{url}/?q1=s1", data=json.dumps(req_body), headers=headers, cookies=cookies
)
print(resp.status_code, resp.text)
规范 API 响应内容
设计 API 返回内容时,通常需要与前端约定统一的响应体格式,方便反序列化和其它服务调用。常见格式:
{
"code": 200,
"data": {
"content": "this is /a/1"
},
"msg": "success"
}
code:状态码。简单系统可直接用 HTTP 状态码;大型系统可设计自定义状态码,如:
from enum import Enum
class BizStatus(Enum):
OK = 200
BadRequestA1 = 4001 # 请求参数异常-A情况
BadRequestA2 = 4002 # 请求参数异常-B情况
msg:对当前 code 的补充说明。data:响应体内容。
方案一:封装响应类
from http import HTTPStatus
from flask import Flask, jsonify, make_response, request
app = Flask(__name__)
class JsonResponse:
def __init__(self, code: HTTPStatus = HTTPStatus.OK, msg: str = "success", data=None):
self.code = code
self.msg = msg
self.data = data
def response(self):
resp = make_response(jsonify({
"code": self.code.value,
"msg": self.msg,
"data": self.data,
}), self.code.value)
resp.headers["Content-Type"] = "application/json"
return resp
@app.errorhandler(404)
def error_handler_not_found(error):
return JsonResponse(
code=HTTPStatus.NOT_FOUND,
msg=f"{request.method} {request.path} Not Found",
).response()
@app.errorhandler(Exception)
def error_handler_generic(error):
return JsonResponse(
code=HTTPStatus.INTERNAL_SERVER_ERROR,
msg=f"Internal Server Error. {request.method} {request.path}",
data={"error": str(error)},
).response()
@app.get("/a/1")
def apitest_a1():
return JsonResponse(
code=HTTPStatus.OK, msg="success", data={"content": "this is /a/1"}
).response()
方案二:直接继承 Response
import json
from datetime import datetime
from http import HTTPStatus
from flask import Flask, Response, request
app = Flask(__name__)
class CustomJSONEncoder(json.JSONEncoder):
"""datetime 不能直接序列化为 JSON,需要自定义编码器"""
def default(self, o):
if isinstance(o, datetime):
return o.isoformat()
return super().default(o)
class JsonResponse(Response):
def __init__(
self,
data: dict | None = None,
code: HTTPStatus = HTTPStatus.OK,
msg: str = "success",
):
headers = {"Content-Type": "application/json; charset=utf-8"}
response = json.dumps(
{"code": code.value, "msg": msg, "data": data},
cls=CustomJSONEncoder,
)
super().__init__(response=response, status=code.value, headers=headers)
class Success(JsonResponse):
def __init__(self, data: dict | None = None):
msg = f"{request.method} {request.path} success"
super().__init__(code=HTTPStatus.OK, msg=msg, data=data)
class Fail(JsonResponse):
def __init__(self, data: dict | None = None):
msg = f"Fail to {request.method} {request.path}"
super().__init__(code=HTTPStatus.INTERNAL_SERVER_ERROR, msg=msg, data=data)
class ResourceNotFound(JsonResponse):
def __init__(self, data: dict | None = None):
msg = f"{request.method} {request.path} not found"
super().__init__(code=HTTPStatus.NOT_FOUND, msg=msg, data=data)
@app.get("/")
def index():
return Success({"now": datetime.now(), "sth": "hahahaha"})
@app.errorhandler(404)
def error_handler_notfound(error):
return ResourceNotFound()
@app.errorhandler(Exception)
def error_handler_generic(error):
return Fail()
提示
两种方案二选一即可。方案二继承 Response 后可直接作为视图返回值,配合 CustomJSONEncoder 处理 datetime 等特殊类型。更完整的独立实现见 补充:规范API响应内容。