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
英文地址,請注明出處:http://en.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;從而提升與顧客互動時的解…