Day5-中间件与请求处理

昨天搞定了异步优化,今天来解决一些实际问题。Day4的API虽然性能不错,但还缺少一些企业级应用必备的功能。

现在的问题

  • 前端无法访问API(跨域问题)
  • 没有请求日志,出问题难以排查
  • 错误信息格式不统一
  • 缺少统一的请求处理机制

解决思路

用中间件来解决这些问题。中间件就像给API加上"门卫",每个请求都要经过这些门卫的检查和处理。

分三步走:

  1. CORS中间件 - 解决跨域问题
  2. 日志中间件 - 记录请求信息
  3. 异常处理器 - 统一错误格式

步骤1:CORS中间件

什么是CORS?

CORS(跨域资源共享)是浏览器的安全机制。默认情况下,浏览器只允许同一个域名下的网页访问API。

开发时经常遇到这个问题:

  • 前端运行在 http://localhost:3000
  • 后端运行在 http://localhost:8000

这就是跨域访问,浏览器会直接阻止。CORS中间件就是告诉浏览器哪些外部地址可以访问我们的API。

添加CORS中间件

先解决最常见的跨域问题:

# v5_middleware/main.py
"""
博客系统v5.0 - 中间件版本
添加CORS、日志等中间件支持
"""from fastapi import FastAPI, HTTPException, Depends, status
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy.ext.asyncio import AsyncSession
from typing import List, Optional
import logging# 导入Day4的模块
import crud
from database import get_async_db, create_tables
from schemas import UserRegister, UserResponse, UserLogin, PostCreate, PostResponse# 配置日志
logging.basicConfig(level=logging.INFO,format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)app = FastAPI(title="博客系统API v5.0",description="7天FastAPI学习系列 - Day5中间件版本",version="5.0.0"
)# 添加CORS中间件 - 解决前端跨域问题
app.add_middleware(CORSMiddleware,allow_origins=["http://localhost:3000",    # React开发服务器"http://127.0.0.1:3000",   # 本地访问"http://localhost:5173",   # Vite开发服务器"http://127.0.0.1:5173"    # Vite本地访问],allow_credentials=True,  # 允许携带认证信息(cookies等)allow_methods=["*"],     # 允许所有HTTP方法allow_headers=["*"],     # 允许所有请求头
)logger.info("CORS中间件已配置,支持前端跨域访问")# 全局变量:当前用户(Day7会用JWT替换)
current_user_id: Optional[int] = None# 应用启动时创建数据表
@app.on_event("startup")
async def startup_event():"""应用启动时异步创建数据表"""await create_tables()logger.info("数据库表创建完成")

现在前端就可以正常访问我们的API了。

测试CORS效果

使用curl命令来测试CORS配置是否正确:

1. 测试基本API连接
# 测试根路由
curl -H "Origin: http://localhost:3000" -v http://localhost:8000/# 预期响应头应包含:
# Access-Control-Allow-Origin: http://localhost:3000
# Access-Control-Allow-Credentials: true
2. 测试预检请求(OPTIONS)
# 测试POST请求的预检
curl -H "Origin: http://localhost:3000" \-H "Access-Control-Request-Method: POST" \-H "Access-Control-Request-Headers: Content-Type" \-X OPTIONS -v http://localhost:8000/users/register# 预期响应头应包含:
# Access-Control-Allow-Origin: http://localhost:3000
# Access-Control-Allow-Methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT
# Access-Control-Allow-Headers: accept, accept-encoding, authorization, content-type, dnt, origin, user-agent, x-csrftoken, x-requested-with
3. 测试用户注册(跨域POST请求)
# 测试用户注册
curl -H "Origin: http://localhost:3000" \-H "Content-Type: application/json" \-X POST \-d '{"username": "红发香克斯", "email": "xiangkesi@example.com", "password": "TestPass136!"}' \-v http://localhost:8000/users/register# 成功响应示例:
# {
#   "id": 1,
#   "username": "红发香克斯",
#   "email": "xiangkesi@example.com",
#   "created_at": "2025-08-26T10:00:00"
# }
4. 测试用户登录(跨域POST请求)
# 测试用户登录
curl -H "Origin: http://localhost:3000" \-H "Content-Type: application/json" \-X POST \-d '{"account":"红发香克斯","password": "TestPass136!"}' \-v http://localhost:8000/users/login# 成功响应示例:
# {
#   "message": "登录成功",
#   "user": {
#     "id": 1,
#     "username": "红发香克斯",
#     "email": "xiangkesi@example.com",
#      "created_at": "2025-08-26T10:02:00"
#   }
# }
5. 关键CORS响应头说明

在curl的-v输出中,注意观察这些响应头:

  • Access-Control-Allow-Origin: 允许访问的源地址
  • Access-Control-Allow-Methods: 允许的HTTP方法
  • Access-Control-Allow-Headers: 允许的请求头
  • Access-Control-Allow-Credentials: 是否允许携带认证信息
6. 测试不同源的访问
# 测试未配置的源(应该被拒绝)
curl -H "Origin: http://evil-site.com" -v http://localhost:8000/# 测试配置的源(应该被允许)
curl -H "Origin: http://localhost:5173" -v http://localhost:8000/

如果CORS配置正确,你应该看到:

  • 配置的源返回相应的Access-Control-Allow-Origin
  • 未配置的源不会返回CORS相关头部
  • 所有跨域请求都能正常处理

步骤2:日志中间件

为什么需要日志?

日志在API开发中很重要,可以帮我们:

  • 排查问题 - 出错时知道是哪个请求出的问题
  • 性能监控 - 哪些API响应慢,需要优化
  • 用户行为分析 - 哪些功能使用频率高
  • 安全监控 - 发现异常的访问模式

添加请求日志中间件

# 继续在main.py中添加
import time
from fastapi import Request@app.middleware("http")
async def log_requests(request: Request, call_next):"""请求日志中间件记录每个请求的详细信息和处理时间"""start_time = time.time()# 记录请求开始logger.info("请求开始: %s %s - 客户端: %s",request.method, request.url, request.client.host if request.client else 'unknown')# 处理请求response = await call_next(request)# 计算处理时间process_time = time.time() - start_time# 记录请求结束status_text = "成功" if response.status_code < 400 else "失败"logger.info("请求完成(%s): %s %s - 状态码: %d - 耗时: %.4f秒",status_text,request.method, request.url, response.status_code, process_time)# 在响应头中添加处理时间(方便前端监控)response.headers["X-Process-Time"] = str(process_time)# 如果响应时间过长,记录警告if process_time > 1:logger.warning("慢请求警告: %s %s 耗时 %.4f秒,建议优化",request.method, request.url, process_time)return response

添加更详细的日志记录

为特定的操作添加更详细的日志:

# 在API函数中添加业务日志
@app.post("/users/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
async def register_user(user_data: UserRegister, db: AsyncSession = Depends(get_async_db)):"""用户注册 - 添加详细日志"""logger.info(f"用户注册请求: 用户名={user_data.username}, 邮箱={user_data.email}")try:db_user = await crud.create_user(db, username=user_data.username,email=user_data.email,password=user_data.password)logger.info(f"用户注册成功: ID={db_user.id}, 用户名={db_user.username}")return UserResponse(id=db_user.id,username=db_user.username,email=db_user.email,created_at=db_user.created_at)except ValueError as e:logger.warning(f"用户注册失败: {str(e)} - 用户名={user_data.username}")raise HTTPException(status_code=400, detail=str(e))except Exception as e:logger.error(f"用户注册异常: {str(e)} - 用户名={user_data.username}")raise HTTPException(status_code=500, detail=f"创建用户失败: {str(e)}")@app.post("/users/login")
async def login_user(login_data: UserLogin, db: AsyncSession = Depends(get_async_db)):"""用户登录 - 添加详细日志"""logger.info(f"用户登录请求: 账号={login_data.account}")global current_user_iduser = await crud.authenticate_user(db, login_data.account, login_data.password)if not user:logger.warning(f"登录失败: 账号或密码错误 - 账号={login_data.account}")raise HTTPException(status_code=401, detail="用户名或密码错误")current_user_id = user.idlogger.info(f"用户登录成功: ID={user.id}, 用户名={user.username}")return {"message": "登录成功","user": UserResponse(id=user.id,username=user.username,email=user.email,created_at=user.created_at)}

现在启动服务器,你会看到详细的日志输出:

uvicorn main:app --reload --host 0.0.0.0 --port 8000

控制台输出类似:

INFO:     Started reloader process [21957] using WatchFiles
2025-08-26 17:43:00,350 - main - INFO - CORS中间件已配置,支持前端跨域访问
INFO:     Started server process [21959]
INFO:     Waiting for application startup.
2025-08-26 17:43:00,369 - main - INFO - 数据库表创建完成
INFO:     Application startup complete.
2025-08-26 17:43:26,120 - main - INFO - 请求开始: POST http://localhost:8000/users/login - 客户端: 127.0.0.1
2025-08-26 17:43:26,122 - main - INFO - 用户登录请求: 账户=洛克斯
2025-08-26 17:43:26,131 - main - INFO - 用户登录成功: ID=5, 用户名=洛克斯
2025-08-26 17:43:26,131 - main - INFO - 请求完成(成功): POST http://localhost:8000/users/login - 状态码: 200 - 耗时: 0.0117秒
INFO:     127.0.0.1:48842 - "POST /users/login HTTP/1.1" 200 OK

步骤3:异常处理器

为什么需要统一异常处理?

Day4中的错误处理比较简单,不同的错误可能返回不同格式的信息。统一异常处理可以让所有错误都有标准的格式和处理方式。

注意:异常处理器不是中间件,它们是FastAPI的异常处理机制,只在发生异常时才会被触发。

添加全局异常处理器

# 在main.py中添加异常处理
from fastapi import Request
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request: Request, exc: StarletteHTTPException):"""HTTP异常处理器统一处理所有HTTP异常,返回标准格式"""logger.error("HTTP异常: %d - %s - 请求: %s %s",exc.status_code,  exc.detail,       request.method,  request.url       )return JSONResponse(status_code=exc.status_code,content={"error": True,"status_code": exc.status_code,"message": exc.detail,"path": str(request.url),"timestamp": time.time()})@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):"""数据验证异常处理器处理Pydantic模型验证错误"""error_messages = [error['msg'] for error in exc.errors()]logger.warning("数据验证失败: %s - 请求: %s %s",error_messages,request.method, request.url)return JSONResponse(status_code=422,content={"error": True,"status_code": 422,"message": "数据验证失败","details": exc.errors(),"path": str(request.url),"timestamp": time.time()})@app.exception_handler(Exception)
async def general_exception_handler(request: Request, exc: Exception):"""通用异常处理器处理所有未捕获的异常"""logger.error("未处理异常:%s: %s - 请求:%s %s",type(exc).__name__, str(exc), request.method, request.url,exc_info=True        )return JSONResponse(status_code=500,content={"error": True,"status_code": 500,"message": "服务器内部错误","path": str(request.url),"timestamp": time.time()})

添加健康检查和根路由

完善一下基础路由,并添加健康检查:

# ===== 根路由 =====@app.get("/")
async def root():"""欢迎页面"""logger.info("访问根路由")return {"message": "欢迎使用博客系统API v5.0","version": "5.0.0","docs": "/docs","features": ["用户管理", "文章管理", "数据验证增强", "数据库持久化", "异步优化", "CORS支持","请求日志","异常处理"],"next_version": "Day6将添加依赖注入"}@app.get("/health")
async def health_check(db: AsyncSession = Depends(get_async_db)):"""健康检查接口"""try:# 检查数据库连接user_count = await crud.get_user_count(db)post_count = await crud.get_post_count(db)logger.info(f"健康检查通过: 用户数={user_count}, 文章数={post_count}")return {"status": "healthy","version": "5.0.0","users_count": user_count,"posts_count": post_count,"database": "SQLite with async support","middleware": "CORS、日志、异常处理","performance": "异步优化已启用"}except Exception as e:logger.error(f"健康检查失败: {str(e)}")raise HTTPException(status_code=503, detail="服务不可用")

测试异常处理效果

测试一下异常处理是否正常工作:

# 1. 测试正常请求
curl http://localhost:8000/# 2. 测试404错误
curl http://localhost:8000/none# 3. 测试数据验证错误
curl -X POST "http://localhost:8000/users/register" \-H "Content-Type: application/json" \-d '{"username": "","email": "dd-email","password": "123"}'# 4. 测试健康检查
curl http://localhost:8000/health

现在所有的错误都会返回统一格式的JSON响应,并且在日志中记录详细信息。

今日总结

完成了两个重要的中间件和一套异常处理器:

  1. CORS中间件 - 解决前端跨域访问问题
  2. 请求日志中间件 - 记录所有API请求和响应时间
  3. 异常处理器 - 统一错误响应格式

Day4 vs Day5 对比

方面Day4Day5
跨域支持无,前端无法访问CORS中间件,完美支持
请求日志详细的请求日志和性能监控
错误处理格式不统一统一的错误响应格式
问题排查困难有详细日志,容易排查
前端对接无法对接可以正常对接

中间件执行顺序

FastAPI中的中间件执行遵循洋葱模型(Onion Model):

  • 请求阶段:中间件按照添加的顺序执行
  • 响应阶段:中间件按照添加的相反顺序执行
  • 对于我们的两个中间件:
    • CORS中间件:先添加,在请求阶段先执行,在响应阶段后执行(内层)
    • 日志中间件:后添加,在请求阶段后执行,在响应阶段先执行(外层)

注意:异常处理器不是中间件,它们独立于中间件执行顺序,只在异常发生时触发。

推荐的添加顺序

# 1. 先添加CORS中间件(在请求阶段先执行)
app.add_middleware(CORSMiddleware, ...)# 2. 再添加日志中间件(在请求阶段后执行)
@app.middleware("http")
async def log_requests(...):# 3. 异常处理器(独立执行)
@app.exception_handler(...)

这样安排的好处:

  • 日志中间件先处理请求,能记录包括CORS处理在内的完整请求信息
  • 日志中间件后处理请求,记录响应信息,然后CORS中间件处理响应头
  • 异常处理器独立工作,统一处理所有异常

明天学习依赖注入系统,让代码更简洁和可维护。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/95902.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/95902.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【LeetCode热题100道笔记】反转链表

题目描述 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a;输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1] 示例 2&#xff1a;输入&#xff1a;head [1,2] 输出&#xff1a;[2,1] 示例 3&#xff1a;…

Oracle:select top 5

在Oracle数据库中实现SELECT TOP 5功能需采用特定语法&#xff0c;因其原生不支持TOP关键字。以下是两种主流实现方式&#xff1a;‌ROWNUM结合子查询‌先通过子查询排序数据&#xff0c;再在外层用ROWNUM限制行数&#xff1a;SELECT * FROM ( SELECT * FROM 表名 ORDER BY 排序…

Kubernetes(k8s) 增量更新 po

文章目录前言k8s 增量更新 po1. 导出要新建po 的控制器配置2. 配置详解3. 重新生效前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每天的运气都不会太差&#xff0c;实在…

基于stm32的车辆安全驾驶预警系统

若该文为原创文章&#xff0c;转载请注明原文出处。一、 项目背景与引言(一) 研究背景及意义道路交通安全是全球性的重大公共安全问题。据统计&#xff0c;绝大多数交通事故源于驾驶员的危险状态&#xff08;疲劳、分心、健康突发状况&#xff09;和危险驾驶行为&#xff08;超…

React学习教程,从入门到精通, React 新创建组件语法知识点及案例代码(11)

React 新创建组件语法知识点及案例代码 React 是由 Facebook 开发的一个用于构建用户界面的 JavaScript 库。随着 React 的不断发展&#xff0c;创建组件的方式也在不断演进。本文将详细介绍 React 中创建组件的最新语法&#xff0c;包括函数组件&#xff08;Functional Compo…

SQL Server全链路安全防护

SQL Server 的安全性是一个多层次、综合性的体系&#xff0c;旨在保护数据免受未授权访问、篡改和泄露。其核心安全机制可概括为以下几个方面&#xff1a;1. 身份验证&#xff08;Authentication&#xff09; Windows 身份验证&#xff1a; 使用 Windows 账户&#xff08;域/本…

如何利用Web3提升企业竞争力

在这个信息爆炸的时代&#xff0c;Web3技术以其独特的去中心化、透明性和用户主权特性&#xff0c;成为企业提升竞争力的新战场。本文将深入探讨企业如何把握Web3的浪潮&#xff0c;实现业务的飞跃。 1. 把握Web3的核心价值 Web3的核心在于去中心化、透明性和用户主权。这种模式…

HOW - 在浏览器下载一个 Excel 表格文件

文章目录一、技术方案二、前端具体实现代码分析转换逻辑注意事项一、技术方案 后台返回 base64 数据 {code: 0,data: "base64;...", }前端进行数据格式转化并下载成 Excel 文件 这篇文章主要介绍第二个步骤的实现。 二、前端具体实现 代码 src/utils/transform…

【Android】Room数据库的使用

三三要成为安卓糕手 引入 Room是一个抽象层&#xff0c;对SQLite进行了封装&#xff0c;简化了SQLite数据库的操作&#xff0c;让开发者能以更加对象化的方式进行数据库操作&#xff1b;Room解决了SQLite操作繁琐&#xff0c;容易产生错误的问题&#xff0c;让开发者能以更加对…

Next.js 介绍:为什么选择它来构建你的下一个 Web 应用?

Next.js 介绍&#xff1a;为什么选择它来构建你的下一个 Web 应用&#xff1f; 作者&#xff1a;码力无边你好&#xff0c;欢迎来到我们的 Next.js 专栏&#xff01;在接下来的 30 篇文章中&#xff0c;我们将一起踏上一段从入门到精通的旅程&#xff0c;深入探索这个强大而优雅…

开发环境 之 编辑器、编译器、IDE梳理

小生第一次学习编程时&#xff0c;懵懵搞不懂编辑器、编译器、IDE区别&#xff0c;虽然这对前期学习编程语言语法的影响不是很大&#xff0c;但是现在梳理一下&#xff0c;总归心里踏实些。 一、概念及区别 IDE是前面几者的集成&#xff0c;前面几个分别是IDE的子集。对比维度编…

高级RAG策略学习(六)——Contextual Chunk Headers(CCH)技术

Contextual Chunk Headers&#xff08;CCH&#xff09;技术深度解析 第一部分&#xff1a;理论基础与核心原理 一、核心定义&#xff1a;给 “文本块” 加 “上下文标签” Contextual Chunk Headers&#xff08;上下文块标题&#xff0c;简称 CCH&#xff09;本质是为文档拆分后…

人形机器人控制系统核心芯片从SoC到ASIC的进化路径

目录&#xff1a; 0 前言 1 人形机器人控制系统核心芯片选择ASIC而非SoC的理由 1.1 SoC的架构特征 1.2 ASIC的架构特征 1.3 SoC的优势&#xff08;继承软件生态&#xff09; 1.4 ASIC的优势&#xff08;硬件底层算法就是应用层算法&#xff09; 1.5 人形机器人控制系统核…

linux thread 线程一

thread线程是linux的重要概念。线程不能独立存在&#xff0c;必须在进程中存在。一个进程必须有一个线程&#xff0c;如果进程中没有创建新线程&#xff0c;进程启动后本身就有一个线程。使用getpid、getppid获取进程的进程ID和父进程ID。使用pthread_self获取到当前线程的ID。…

Arduino Nano33 BLESense Rev2【室内空气质量检测语音识别蓝牙调光台灯】

一、硬件介绍 1、产品特点 Arduino Nano 33 BLE Rev2&#xff0c;利用了nRF52840微控制器的先进功能。这款32位Arm Cortex-M4 CPU 64 MHz与MicroPython的兼容性增强了板子的灵活性&#xff0c;该开发板的突出特点是其蓝牙低功耗&#xff08;BLE&#xff09;功能&#xff0c;使…

【问题解决】mac笔记本遇到鼠标无法点击键盘可响应处理办法?(Command+Option+P+R)

背景 如题。鼠标无法点击&#xff0c;但可以移动。触控板能够波动&#xff0c;鼠标翻页能够work&#xff0c;但是点击后无法响应。 根因 电脑缓存问题 解决办法 重置PRAM&#xff1a; 确保电脑关机状态&#xff08;可以先sudo shutdown -t now)&#xff08;一定要确保&#xff…

23ai数据库通过SQLcl生成AWR报告

‌1. 查看现有快照SQL> awr list snap;SNAP_ID DBID BEGIN_INTERVAL_TIME END_INTERVAL_TIME FLUSH_LEVEL __________ _____________ __________________________________ __________________________________ ______________793 …

基于Django+Vue3+YOLO的智能气象检测系统

基于DjangoVue3YOLO的智能气象检测系统 项目简介 本项目是一个集成了人工智能深度学习技术的现代化气象检测系统&#xff0c;采用前后端分离架构&#xff0c;结合YOLO目标检测算法&#xff0c;实现了对气象现象的智能识别与分析。系统提供了完整的用户管理、实时检测、历史记录…

(4)什么时候引入Seata‘‘

非常好的问题&#xff01;这两个问题正是技术选型时需要重点考虑的。什么时候需要引入 Seata&#xff1f;需要引入 Seata 的场景&#xff1a;跨数据库的分布式事务// 订单服务&#xff08;MySQL&#xff09; 库存服务&#xff08;PostgreSQL&#xff09; 账户服务&#xff08…

苹果内部 AI聊天机器人“Asa”曝光,为零售员工打造专属A

MacRumors网站的亚伦佩里斯&#xff08;Aaron Perris&#xff09;透露&#xff0c;苹果正在内部测试一款名为“Asa”的AI聊天机器人。这款工具旨在赋能Apple Store零售员工&#xff0c;帮助他们快速掌握iPhone等产品的特色和差异化使用场景&#xff0c;从而提升与顾客互动时的解…