【FastAPI】中间件

【FastAPI】中间件

  • 一、概述
  • 二、作用
    • 2.1 日志记录与监控
    • 2.2 身份验证与授权
    • 2.3 CORS(跨域资源共享)
    • 2.4 Gzip压缩
    • 2.5 会话管理
    • 2.6 自定义功能
    • 2.7 执行顺序
  • 三、 总结
  • 四、相关链接

一、概述

FastAPI的中间件提供了一种强大的机制,允许开发者在请求被处理之前以及响应返回给客户端之前对它们进行拦截和操作
这为实现诸如日志记录、身份验证、性能监控、错误处理等功能提供了极大的灵活性。

二、作用

2.1 日志记录与监控

中间件可以用来记录所有进入系统的HTTP请求及其对应的响应
例如,你可以创建一个中间件来测量每个请求的处理时间,并将这个信息作为自定义头部X-Process-Time添加到响应中。
这对于性能分析非常有用,因为它可以帮助你了解哪些请求消耗了较多的时间,从而优化系统性能。

import json
import logging
import os
import time
from fastapi import FastAPI, Request, Response
from starlette.middleware.base import BaseHTTPMiddleware

# 设置日志格式和级别
LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO').upper()
logging.basicConfig(
    level=LOG_LEVEL,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

class LoggingMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next) -> Response:
        start_time = time.perf_counter()
        response = await call_next(request)
        process_time = time.perf_counter() - start_time
        
        log_data = {
            "client_ip": request.client.host,
            "method": request.method,
            "path": request.url.path,
            "status_code": response.status_code,
            "process_time": f"{process_time:.4f}s",
            "timestamp": datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M:%S')
        }
        
        # 根据环境变量决定是否打印详细的日志内容
        if LOG_LEVEL == 'DEBUG':
            log_data["headers"] = dict(request.headers)
            log_data["query_params"] = dict(request.query_params)
        
        logging.info(json.dumps(log_data))
        
        return response

app = FastAPI()
app.add_middleware(LoggingMiddleware)

@app.get("/")
async def root():
    return {"message": "Hello World"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000, log_level=LOG_LEVEL.lower())

测试方法:

  • 启动应用:python main.py
  • 使用浏览器或Postman访问http://localhost:8000/
  • 查看命令行输出的日志信息,确认日志已正确记录。
2024-12-25 19:21:56,568 - INFO - {"client_ip": "127.0.0.1", "method": "GET", "path": "/", "status_code": 200, "process_time": "0.0003s", "timestamp": "2024-12-25 19:21:56"}
INFO:     127.0.0.1:53956 - "GET / HTTP/1.1" 200 OK
  1. 设置日志配置
    这里设置了日志记录器的基本配置。通过环境变量 LOG_LEVEL 来控制日志级别,默认为 INFO
    这意味着除非设置了更低的日志级别(如 DEBUG),否则不会输出调试信息。
    此外,还指定了日志格式,包括时间戳日志级别消息内容
LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO').upper()
logging.basicConfig(
    level=LOG_LEVEL,
    format='%(asctime)s - %(levelname)s - %(message)s'
)
  1. 自定义中间件 LoggingMiddleware
    中间件是用来处理请求和响应之间逻辑的理想场所。
    在这个例子中,LoggingMiddleware 被用来捕获每次请求的时间点,然后在请求完成后计算处理所需的时间,并将相关信息以 JSON 格式记录下来。这有助于监控 API 性能并诊断问题。
  • 开始计时:使用 time.perf_counter() 获取当前时间。
  • 调用下一个处理器:通过 await call_next(request) 将请求传递给下一个中间件或最终的路由处理器。
  • 结束计时:再次调用 time.perf_counter() 计算总耗时。
  • 构建日志数据:创建一个字典 log_data 包含客户端 IP、HTTP 方法、路径、状态码及处理时间等信息。
  • 条件性添加更多信息:如果日志级别为 DEBUG,还会额外添加请求头和查询参数到日志中,以便于更详细的调试。
  • 记录日志:最后,将构造好的日志数据转换成 JSON 字符串并通过 logging.info() 输出。
  1. 定义 FastAPI 应用程序
    这里创建了一个 FastAPI 实例 app 并注册了之前定义的 LoggingMiddleware。随后定义了一个 GET 方法对应的路由 /,当访问根路径时会返回一个 JSON 响应,其中包含了一条简单的欢迎信息 “Hello World”。
app = FastAPI()
app.add_middleware(LoggingMiddleware)

@app.get("/")
async def root():
    return {"message": "Hello World"}
  1. 启动 Uvicorn 服务器
    这段代码确保只有当脚本作为主模块运行时才会启动 Uvicorn 服务器。
    Uvicorn 是一个用于运行 ASGI 应用的异步服务器,它将托管 FastAPI 应用程序,并监听指定的主机地址 (0.0.0.0) 和端口 (8000)。
    同时,也将日志级别传递给 Uvicorn,以保证一致性。
if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000, log_level=LOG_LEVEL.lower())

2.2 身份验证与授权

通过中间件,可以在请求到达路由处理器之前检查用户的身份验证状态。

  • 如果请求未通过身份验证,则可以直接返回401 Unauthorized响应;
  • 如果通过了身份验证但没有足够的权限访问特定资源,则可以返回403 Forbidden响应。

这种方式确保了只有经过适当授权的用户才能访问受保护的API端点。

from datetime import datetime, timedelta
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from pydantic import BaseModel
from typing import Optional
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.base import BaseHTTPMiddleware

SECRET_KEY = "your_secret_key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

class Token(BaseModel):
    access_token: str
    token_type: str

class TokenData(BaseModel):
    username: Optional[str] = None

fake_users_db = {
    "johndoe": {
        "username": "johndoe",
        "full_name": "John Doe",
        "email": "johndoe@example.com",
        "hashed_password": "fakehashedsecret",
        "disabled": False,
    }
}

def verify_password(plain_password, hashed_password):
    return plain_password == hashed_password

def get_user(db, username: str):
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)

def authenticate_user(fake_db, username: str, password: str):
    user = get_user(fake_db, username)
    if not user:
        return False
    if not verify_password(password, user.hashed_password):
        return False
    return user

def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=15)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

async def get_current_user(token: str = Depends(oauth2_scheme)):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        exp: int = payload.get("exp")
        if username is None or exp is None:
            raise credentials_exception
        if datetime.utcfromtimestamp(exp) < datetime.utcnow():
            raise credentials_exception
        token_data = TokenData(username=username)
    except JWTError:
        raise credentials_exception
    user = get_user(fake_users_db, username=token_data.username)
    if user is None:
        raise credentials_exception
    return user

class AuthMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next) -> Response:
        token = request.headers.get('Authorization')
        if token:
            try:
                await get_current_user(token.split()[1])  # Assuming 'Bearer <token>'
            except HTTPException as e:
                return JSONResponse(status_code=e.status_code, content={"detail": e.detail})
        else:
            return JSONResponse(status_code=401, content={"detail": "Unauthorized"})
        return await call_next(request)

app = FastAPI()
app.add_middleware(AuthMiddleware)

@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    user = authenticate_user(fake_users_db, form_data.username, form_data.password)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"},
        )
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user.username}, expires_delta=access_token_expires
    )
    return {"access_token": access_token, "token_type": "bearer"}

@app.get("/users/me", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_user)):
    return current_user

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

2.3 CORS(跨域资源共享)

为了使Web应用程序能够从不同的域名发出AJAX请求,你需要配置CORS策略。
FastAPI内置了CORSMiddleware,它允许你轻松地设置允许的来源、方法、头等参数,从而实现安全且灵活的跨域通信2。
下面是一个支持CORS配置的应用程序示例,允许来自特定源的请求,并处理预检请求(Preflight Requests)。

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

origins = [
    "http://localhost:3000",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
    expose_headers=["X-Total-Count"],
    max_age=86400
)

@app.get("/")
async def root():
    return {"message": "Hello World"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

2.4 Gzip压缩

对于需要提高传输效率的情况,可以使用Gzip中间件来压缩响应内容。这样不仅可以减少网络带宽占用,还能加快页面加载速度,特别是在移动设备上效果尤为明显2。

from fastapi import FastAPI
from fastapi.middleware.gzip import GZipMiddleware

app = FastAPI()

app.add_middleware(
    GZipMiddleware,
    minimum_size=1000,
    compresslevel=9
)

@app.get("/")
async def root():
    large_text = "a" * 1024  # 创建一个较大的字符串用于测试压缩效果
    return {"message": large_text}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

2.5 会话管理

借助于SessionMiddleware,你可以方便地管理用户的会话信息。
比如,在登录后存储用户的认证令牌或其他临时数据,这些数据可以通过cookie传递并在后续请求中读取2。

// A code block
var foo = 'bar';

2.6 自定义功能

除了上述提到的功能之外,中间件还可以用于执行其他任何你想要在每次请求或响应时运行的任务。例如,你可以编写一个中间件来解析并标准化查询字符串参数,或者根据某些条件修改请求体中的内容7。

// A code block
var foo = 'bar';

2.7 执行顺序

值得注意的是,当多个中间件同时存在于一个应用中时,它们按照“后进先出”的原则被执行。
也就是说,最后添加的那个中间件会在最前面被调用,并且最先完成其工作之后再交给下一个中间件继续处理。

// A code block
var foo = 'bar';

三、 总结

总之,FastAPI中的中间件是一个非常强大且灵活的工具,它使得开发者能够在不改变核心业务逻辑的前提下,轻松地向应用程序添加各种辅助性功能和服务
无论是为了增强安全性、改善用户体验还是简化开发流程,合理利用中间件都能带来显著的好处。

四、相关链接

【FastAPI】日志
【FastAPI】中间件
【FastAPI】简介
【FastAPI】BaseModel类

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

中文拼写检测纠正 Read, Listen, and See Leveraging Multimodal Information 论文

拼写纠正系列 NLP 中文拼写检测实现思路 NLP 中文拼写检测纠正算法整理 NLP 英文拼写算法&#xff0c;如果提升 100W 倍的性能&#xff1f; NLP 中文拼写检测纠正 Paper java 实现中英文拼写检查和错误纠正&#xff1f;可我只会写 CRUD 啊&#xff01; 一个提升英文单词拼…

vue2 elementui if导致的rules判断失效

优化目标 和 目标转化出价必填的 切换的时候还会隐藏掉 这时候的if语句会导致rules判断失效 我的办法是把判断拉到外面 别放在el-form-item里 <section v-if"unitForm.baseTarget OCPM && unitForm.cpaTargetOptions ! undefined && unitForm.cpaTa…

前端(Ajax)

1.客户端请求 向https://jsonplaceholder.typicode.com/users发送get请求 const xhr new XMLHttpRequest(); console.log(xhr.readyState); xhr.open(‘get’, ‘https://jsonplaceholder.typicode.com/users’) console.log(xhr.readyState); xhr.send(); console.log(xhr.…

uboot, s5pv210 , main_loop 分析(16)

main_loop 的代码如下&#xff1a; 4443 void main_loop (void)42 {41 #ifndef CONFIG_SYS_HUSH_PARSER E 40 ▎ static char lastcommand[CONFIG_SYS_CBSIZE] { 0, }; ■ Use of undeclared identifier CONFIG_SYS_CBSIZE39 ▎ int len;38 ▎ int rc 1;37 ▎ …

信号强劲,通信清晰:北斗三号多模对讲机TD70——专业通信解决方案

在边防海防等国家安全的关键领域&#xff0c;通信的稳定性和可靠性关乎着任务的成败和战士们的安全。北斗三号多模对讲机TD70&#xff0c;凭借其卓越的性能和全面的功能&#xff0c;成为了边防海防通信的利器&#xff0c;守护着国家安全的前沿哨兵。 一、三网融合&#xff0c;…

Arduino驱动DS18B20测量环境温度

DS18B20是一款高精度的单总线数字温度传感器&#xff0c;具体参数如下表所示&#xff1a; 参数名称 参数特性 测温范围 -55~125℃ 测量精度 在-10~85℃范围内的误差为0.5℃ 分辨率 9~12位数字信号&#xff0c;分辨率分别为0.5℃、0.25℃、0.125℃和0.0625℃ 通信方式 …

vector快慢指针+例题详解

1.快慢指针 例题 给定一个链表&#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;我们使用整数 pos 来表示链表尾连接到链表中的位置&#xff08;索引从…

C++--------效率和表示

C 效率和表示 效率 时间效率&#xff1a;在 C 中&#xff0c;不同的数据结构和算法有着各异的时间复杂度。例如&#xff0c;访问数组元素的时间复杂度是 O ( 1 ) O(1) O(1)&#xff0c;而遍历链表查找元素的时间复杂度最坏情况下是 O ( n ) O(n) O(n)。选择合适的算法与数据…

【Mac】终端改色-让用户名和主机名有颜色

效果图 配置zsh 1.打开终端&#xff0c;进入.zshrc配置 cd ~ vim .zshrc2.添加如下配置并保存 # 启用命令行颜色显示 export CLICOLOR1 ## 加载颜色支持 autoload -U colors && colors # 配置 zsh 提示符 PROMPT"%{$fg_bold[red]%}%n%{$reset_color%}%{$fg_bol…

模拟——郑益慧_笔记1_绪论

B站视频链接 模电是数电的基础&#xff1b;参考书&#xff1a; 模拟电子技术基础&#xff08;第四版&#xff09;华成英、童诗白主编&#xff0c;高等教育出版社&#xff1b;电子技术基础 模拟部分 康华光主编&#xff0c;高等教育出版社&#xff1b; 电子技术的发展史 电子…

YOLOv11模型改进-模块-引入多尺度大核注意力Multi-scale Large Kernel Attention

MLKA 的提出源于图像超分辨率任务的挑战性&#xff0c;该任务需重建低质量图像缺失的高频信息&#xff0c;但因 LR 与 HR 图像对应关系复杂&#xff0c;寻找像素相关性困难。此前模型扩展容量的方法增加了训练负担和数据收集成本&#xff0c;而采用的注意力机制无法同时获取局部…

【gym】给定的强化学习环境简介(二)

文章目录 环境介绍一 box2dbipedal_walkercar_dynamicscar_racinglunar_lander 二、 classic_controlacrobotCartPolecontinuous_mountain_carmountain_carpendulum 三、toy_textblackjackcliffwalkingfrozentaxi 四、mujocoAnt&#xff1a;HalfCheetah&#xff1a;Hopper&…

基于支付宝百宝箱构建自己的Agent的基本简易流程(Datawhale AI冬令营)

一&#xff0c;使用支付宝百宝箱 官网地址&#xff1a;百宝箱 (alipay.com) 二&#xff0c;应用构建 点击左上角的新建应用 然后按自己的需求选择对应的模块 以下是我的示例 点击确认之后&#xff0c;进入模型设置界面 按需设计便可以&#xff0c;以下是我的设计 当你写好…

攻防世界 - Web - Level 1 unseping

关注这个靶场的其它相关笔记&#xff1a;攻防世界&#xff08;XCTF&#xff09; —— 靶场笔记合集-CSDN博客 0x01&#xff1a;Write UP 本关是一个 PHP 代码审计关卡&#xff0c;考察的是 PHP 反序列化漏洞以及命令执行的一些绕过手段&#xff0c;下面笔者将带你一步步过关。…

Java进阶学习笔记|面向对象

第一章.类和对象 1.面向对象的介绍 1.面向过程:自己的事情自己干,代表语言C语言洗衣服:每一步自己要亲力亲为 -> 找个盆,放点水,找个搓衣板,搓搓搓 2.面向对象:自己的事情别人帮忙去干,代表语言Java语言 洗衣服:自己的事情别人干 -> 全自动洗衣机3.为啥要使用面向对…

前端性能优化之大文件上传

大文件上传是前端开发中常见的需求之一&#xff0c;特别是在需要处理较大的Excel表格数据、高清图片、视频或其他大型文件时。优化大文件上传不仅可以提升用户体验&#xff0c;还能有效减轻服务器负担。本文将深入探讨大文件上传的几种常见优化技术&#xff0c;包括文件切片与并…

数据结构之线性表之顺序表

定义&#xff1a; 由n&#xff08;n>0&#xff09;个数据特性相同的元素构成的有限序列称为线性表 简单来说n个相同数据类型的数据组wsw合在一起的这么一个集合就是一个线性表 线性表包括顺序表和链表 1. 顺序表&#xff08;我们所有的代码实现都用函数来封装&#xff09…

Matlab 和 R 语言的数组索引都是从 1 开始,并且是左闭右闭的

文章目录 一、前言二、主要内容三、小结 &#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 一、前言 在早期的计算机科学中&#xff0c;数组索引从 1 开始是很常见的。例如&#xff0c;Fortran 和 Pascal 等编程语言也采用了从 1 开始的索引。 这种索引…

突发!!!GitLab停止为中国大陆、港澳地区提供服务,60天内需迁移账号否则将被删除

GitLab停止为中国大陆、香港和澳门地区提供服务&#xff0c;要求用户在60天内迁移账号&#xff0c;否则将被删除。这一事件即将引起广泛的关注和讨论。以下是对该事件的扩展信息&#xff1a; 1. 背景介绍&#xff1a;GitLab是一家全球知名的软件开发平台&#xff0c;提供代码托…

瑞吉外卖项目学习笔记(八)修改菜品信息、批量启售/停售菜品

瑞吉外卖项目学习笔记(一)准备工作、员工登录功能实现 瑞吉外卖项目学习笔记(二)Swagger、logback、表单校验和参数打印功能的实现 瑞吉外卖项目学习笔记(三)过滤器实现登录校验、添加员工、分页查询员工信息 瑞吉外卖项目学习笔记(四)TableField(fill FieldFill.INSERT)公共字…