Flask (使用 Quart) 服务器端代码
python
pip install quart pydantic
服务器端
python
from quart import Quart, jsonify, request
from pydantic import BaseModel, ValidationError, conint
import logging
import asyncio
import uuid
from datetime import datetime
# 创建 Quart 应用实例
app = Quart(__name__)
# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# 定义请求数据模型
class RequestModel(BaseModel):
count: conint(ge=1, le=1000) # count 必须在 1 到 1000 之间
@app.route('/data', methods=['GET', 'POST'])
async def get_data():
try:
if request.method == 'GET':
# 处理 GET 请求参数并进行验证
params = request.args
else:
# 处理 POST 请求参数并进行验证
if request.content_type == 'application/json':
params = await request.json
else:
params = await request.form
data = RequestModel(count=int(params.get('count', 100)))
# 异步操作 1,例如数据库查询
async def fetch_data_1():
await asyncio.sleep(1)
return {'data_1': list(range(data.count))}
# 异步操作 2,例如外部 API 调用
async def fetch_data_2():
await asyncio.sleep(1)
return {'data_2': list(range(data.count, data.count * 2))}
# 并行执行异步操作
result_1, result_2 = await asyncio.gather(fetch_data_1(), fetch_data_2())
# 构建响应数据
response_data = {
'message': 'Hello, this is async data!',
'result_1': result_1,
'result_2': result_2,
'metadata': {
'request_id': str(uuid.uuid4()),
'timestamp': datetime.utcnow().isoformat()
}
}
return jsonify(response_data)
except ValidationError as ve:
logger.warning(f"Validation error: {ve}")
return jsonify({'error': ve.errors()}), 400
except Exception as e:
logger.error(f"Server error: {e}")
return jsonify({'error': 'Internal Server Error'}), 500
# 定义错误处理程序
@app.errorhandler(404)
async def page_not_found(e):
return jsonify({'error': 'Not Found'}), 404
# 运行应用
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)
FastAPI 服务器端代码
python
pip install fastapi uvicorn pydantic
服务器端
python
from fastapi import FastAPI, HTTPException, Request, Form
from fastapi.responses import JSONResponse
from pydantic import BaseModel, conint
import logging
import asyncio
import uuid
from datetime import datetime
from typing import Union
# 创建 FastAPI 应用实例
app = FastAPI()
# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# 定义请求数据模型
class RequestModel(BaseModel):
count: conint(ge=1, le=1000) # count 必须在 1 到 1000 之间
@app.get("/data")
async def get_data(count: int = 100):
try:
# 验证请求参数
data = RequestModel(count=count)
# 异步操作 1,例如数据库查询
async def fetch_data_1():
await asyncio.sleep(1)
return {'data_1': list(range(data.count))}
# 异步操作 2,例如外部 API 调用
async def fetch_data_2():
await asyncio.sleep(1)
return {'data_2': list(range(data.count, data.count * 2))}
# 并行执行异步操作
result_1, result_2 = await asyncio.gather(fetch_data_1(), fetch_data_2())
# 构建响应数据
response_data = {
'message': 'Hello, this is async data!',
'result_1': result_1,
'result_2': result_2,
'metadata': {
'request_id': str(uuid.uuid4()),
'timestamp': datetime.utcnow().isoformat()
}
}
return JSONResponse(content=response_data)
except HTTPException as he:
logger.warning(f"Client error: {he.detail}")
raise he
except Exception as e:
logger.error(f"Server error: {e}")
raise HTTPException(status_code=500, detail="Internal Server Error")
@app.post("/data")
async def post_data(request: Request, count: Union[int, None] = Form(None)):
try:
if request.headers['content-type'] == 'application/json':
params = await request.json()
else:
params = await request.form()
if count is None:
count = int(params.get('count', 100))
data = RequestModel(count=count)
# 异步操作 1,例如数据库查询
async def fetch_data_1():
await asyncio.sleep(1)
return {'data_1': list(range(data.count))}
# 异步操作 2,例如外部 API 调用
async def fetch_data_2():
await asyncio.sleep(1)
return {'data_2': list(range(data.count, data.count * 2))}
# 并行执行异步操作
result_1, result_2 = await asyncio.gather(fetch_data_1(), fetch_data_2())
# 构建响应数据
response_data = {
'message': 'Hello, this is async data!',
'result_1': result_1,
'result_2': result_2,
'metadata': {
'request_id': str(uuid.uuid4()),
'timestamp': datetime.utcnow().isoformat()
}
}
return JSONResponse(content=response_data)
except HTTPException as he:
logger.warning(f"Client error: {he.detail}")
raise he
except Exception as e:
logger.error(f"Server error: {e}")
raise HTTPException(status_code=500, detail="Internal Server Error")
@app.exception_handler(404)
async def not_found_handler(request: Request, exc: HTTPException):
return JSONResponse(status_code=404, content={'error': 'Not Found'})
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000, log_level="info")
客户端代码 (使用 asyncio 和 aiohttp)
python
import aiohttp
import asyncio
import json
async def fetch_data(url, method='GET', data=None):
try:
async with aiohttp.ClientSession() as session:
if method == 'GET':
async with session.get(url) as response:
if response.status == 200:
return await response.json()
else:
print(f"Failed to fetch data: {response.status}")
return None
elif method == 'POST':
async with session.post(url, json=data) as response:
if response.status == 200:
return await response.json()
else:
print(f"Failed to fetch data: {response.status}")
return None
except aiohttp.ClientError as e:
print(f"HTTP request failed: {e}")
return None
except json.JSONDecodeError as e:
print(f"Failed to decode JSON: {e}")
return None
except Exception as e:
print(f"An error occurred: {e}")
return None
if __name__ == '__main__':
url_get_fastapi = 'http://127.0.0.1:8000/data?count=50' # 对应 FastAPI GET 服务
url_post_fastapi = 'http://127.0.0.1:8000/data' # 对应 FastAPI POST 服务
url_get_quart = 'http://127.0.0.1:5000/data?count=50' # 对应 Quart GET 服务
url_post_quart = 'http://127.0.0.1:5000/data' # 对应 Quart POST 服务
# FastAPI GET 请求
data = asyncio.run(fetch_data(url_get_fastapi))
if data:
print("FastAPI GET response:")
print(json.dumps(data, indent=4))
else:
print("Failed to fetch data with FastAPI GET.")
# FastAPI POST 请求
post_data = {"count": 50}
data = asyncio.run(fetch_data(url_post_fastapi, method='POST', data=post_data))
if data:
print("FastAPI POST response:")
print(json.dumps(data, indent=4))
else:
print("Failed to fetch data with FastAPI POST.")
# Quart GET 请求
data = asyncio.run(fetch_data(url_get_quart))
if data:
print("Quart GET response:")
print(json.dumps(data, indent=4))
else:
print("Failed to fetch data with Quart GET.")
# Quart POST 请求
data = asyncio.run(fetch_data(url_post_quart, method='POST', data=post_data
总结
在本示例中,我们展示了如何使用 Quart 和 FastAPI 构建异步 Web 服务器,并通过客户端异步获取数据。这些代码示例涵盖了多种请求方式和数据处理方法,适用于高并发和异步处理的场景。
1. Quart (Flask 的异步版本) 服务器端
-
请求处理:
- 支持 GET 和 POST 请求。
- 支持 application/json 和 application/x-www-form-urlencoded 内容类型。
- 使用 Pydantic 进行数据验证。
-
异步操作:
- 通过 asyncio.gather 并行执行异步操作,例如模拟的数据库查询和外部 API 调用。
-
错误处理:
- 处理 404 错误并返回 JSON 格式的错误消息。
- 捕获和记录服务器内部错误,返回 500 错误。
2. FastAPI 服务器端
- 请求处理:
- 支持 GET 和 POST 请求。
- 对于 GET 请求,直接使用查询参数。
- 对于 POST 请求,支持 application/json 和表单数据 (application/x-www-form-urlencoded)。
- 异步操作:
- 使用 asyncio.gather 并行执行异步操作,例如模拟的数据库查询和外部 API 调用。
- 错误处理:
- 捕获和处理 HTTP 异常,记录客户端和服务器错误。
- 处理 404 错误并返回 JSON 格式的错误消息。
3. 客户端代码 (使用 aiohttp)
- 功能:
- 异步地发送 GET 和 POST 请求到服务器。
- 支持处理 JSON 响应和错误。
- 使用示例:
- 演示如何分别请求 Quart 和 FastAPI 服务器。
- 打印响应数据,帮助验证服务器端的功能。
应用场景
这些代码示例适用于需要异步处理和高并发的应用场景,例如:
- 实时数据处理系统
- 高并发的 API 服务
- 需要与外部服务进行异步交互的应用
通过使用 Quart 和 FastAPI,我们可以有效地处理并发请求,提高应用的响应速度和处理能力。
补充
Pydantic 简介
pydantic 是一个数据验证和设置管理的 Python 库,提供了基于类型注解的数据验证功能。它主要用于定义和验证数据模型,确保数据符合预期的格式和约束。
主要组件
-
BaseModel:
- BaseModel 是 pydantic 中的一个基类,用于定义数据模型。它允许你使用类型注解来定义数据字段,并提供自动验证和数据转换的功能。
-
conint:
- conint 是 pydantic 提供的一个约束类型,用于限制整数值的范围。通过 conint,你可以定义一个整数字段,设置最小值和最大值约束。
示例代码
下面是一个简单的 pydantic 示例,展示了如何定义一个数据模型并验证数据:
python
from pydantic import BaseModel, conint, ValidationError
# 定义一个数据模型
class RequestModel(BaseModel):
count: conint(ge=1, le=1000) # count 字段必须是 1 到 1000 之间的整数
# 正确的数据示例
try:
valid_data = RequestModel(count=50)
print("Valid data:", valid_data)
except ValidationError as e:
print("Validation error:", e)
# 错误的数据示例(超出范围)
try:
invalid_data = RequestModel(count=1500)
except ValidationError as e:
print("Validation error:", e)