Catch-All 路由

目录

介绍

✅ 什么是 Catch-All 路由?

Catch-All 路由 指的是:一个能匹配“任意路径”的通配型路由。

它一般会使用 路径参数 path 类型,比如:

@app.get("/{full_path:path}")
async def fallback_handler(full_path: str):
    return {"msg": f"You visited: {full_path}"}

这个 / 开头的 /{full_path:path},意思是: • 匹配任何以 / 开头的请求; • 把完整的路径作为字符串传入 full_path; • 哪怕路径是 /foo/bar/hello.jpg,它也能匹配!

所以叫 Catch-All,就像“最后一个网兜”,你没被其他精确路径命中,就会掉进去。

 URL 路由匹配机制的一部分,和代码有没有 try-catch 是两码事。

部分 含义
/ 所有路径的开头
{full_path} 这是一个路径参数,名字叫 full_path
:path 是这个参数的“类型转换器”

🔍 :path 是什么意思?

类型 描述
str 默认,只匹配不含斜杠 / 的一段
int 匹配整数
float 匹配浮点数
path ✅ 匹配多段路径(包括斜杠 /)

就意味着这个路由会匹配所有请求路径,无论路径有多少层!

也正因如此,它常被叫做 Catch-All(兜底)路由,用于前端 SPA 项目的前端路由支持。

@app.get("/api/user")
def user(): ...

@app.get("/api/task")
def task(): ...

@app.get("/{path:path}")
def fallback(path): ...

访问 /api/user 会进入 user(),访问 /api/task 会进入 task()。

但如果你访问:

• /api/does-not-exist → ❗ 不会 404!

• 会进入 fallback(path),path == “api/does-not-exist”

这就叫 Catch-All。

路由顺序

  1. 它是匹配所有路径的,如果注册早了,就会“截胡”所有后面的路由。

  2. FastAPI 按照注册顺序来判断匹配优先级,谁先注册,谁优先。

  3. 如果 Catch-All 早注册,那就没有“后面”了,你的 /api/v1/** 根本用不上!

🎯 问题核心:setup_admin(app) 是个「兜底路由(catch-all)」

SQLAdmin 或类似的后台管理插件,一般都会注册一个这样的路由:

@app.get("/{full_path:path}")
async def catch_all_route(full_path: str): ...

🚨 所有未被上面 include_router 明确匹配到的路径请求,都会被这个 catch-all 捕获处理。

• 本来这个应该走 user_router 的 CORS 预检流程;

但是如果这个路由在 setup_admin(app) 之后注册了,那么 catch-all 路由先注册,它就优先匹配了!

• 而 catch-all 路由并不会处理跨域(CORS OPTIONS 请求);

• 所以浏览器就报了 CORS 跨域错误 ⚠️

# 1. 注册所有业务接口
app.include_router(api_router1)
app.include_router(api_router2)

# 2. 注册 SQLAdmin(会用 catch-all)
setup_admin(app)

# 3. 注册前端静态资源路由(if any)
app.include_router(web_router.router)

路由找不到报CORS?

FastAPI 的中间件执行顺序 + catch-all 路由的作用范围 + CORS 的工作机制

app.add_middleware(
    CORSMiddleware,  # 跨域处理
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],  # 允许所有方法
    allow_headers=["*"],  # 允许所有头部
)

✅ 你已经注册了 CORSMiddleware,那为啥还报跨域?

上述代码:

所有经过 FastAPI 路由匹配成功的请求(包括 OPTIONS 预检请求)都会自动添加 Access-Control-Allow-* 相关响应头从而避免跨域错误!

❌ 但为什么“未命中的路由”还会报跨域?

这是核心重点:

🔥CORS 中间件只作用于:能被 FastAPI 接收到的请求

⚠️ 如果这个 Catch-All 路由没有处理 CORS,结果就是:

  1. 浏览器先发出 预检请求 OPTIONS /api/v1/xxx

  2. FastAPI 查找路由匹配不到(因为这个接口没注册或被覆盖);

  3. 匹配到你的 Catch-All;

  4. 但 Catch-All 没有注册 OPTIONS 方法;

  5. FastAPI 返回 405 或 404;

  6. CORSMiddleware 不生效(它只对合法路由生效);

  7. 浏览器就 报跨域错误

这一段已经告诉 FastAPI:我允许跨域请求,包括 OPTIONS 方法。

但!!!问题的关键不是你有没有加 allow_methods=[“*”],而是:

❗ 请求路径 /some/path 根本没注册任何路由!也就没有任何 handler 能接住 OPTIONS 请求!

你要知道:

FastAPI 的 CORS 中间件只会对 存在的路由 生效

❌ 所以:如果你的某个路径根本没被 app.include_router() 注册

比如请求的是 /aichat/pub_agent/expend/dict/,但你没有注册它,那么:

• FastAPI 连 OPTIONS 都不知道该交给谁处理

• 它直接走到了 404 或 405

• 然后 CORS middleware 根本不会处理它

• ✅ 最终结果:浏览器就看到一个没有 CORS headers 的响应,报 CORS error

[请求进来]
     
中间件先处理 (CORSMiddleware 会拦截 OPTIONS 请求)
     
如果请求路径存在  CORSMiddleware 会自动响应 OPTIONS 并添加 CORS headers 
     
如果请求路径不存在  中间件不处理FastAPI 抛出 404 

✔ 方法一:确认你的 catch-all 路由支持 OPTIONS 请求

@router.api_route("/{full_path:path}", methods=["GET", "POST", "OPTIONS"])
async def catch_all(full_path: str, request: Request):
    ...

⚠️ 即使你什么都不做,也得让它 return 一个空的响应,这样 CORSMiddleware 就会处理它。

✔ 方法二:手动添加一个兜底的 OPTIONS 响应

from fastapi.responses import Response

@router.options("/{full_path:path}")
async def options_handler(full_path: str):
    return Response()
原因 说明
CORS 中间件只对匹配成功的路由生效 OPTIONS 预检必须也要命中
Catch-All 如果不支持 OPTIONS,就会报错 浏览器就会认为是跨域失败
路由顺序、注册方式影响是否生效 后注册的 Catch-All 可能覆盖其他逻辑

点赞一下

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦