Route
FastAPI
初始化
# application.py 的 __init__()
self.router: routing.APIRouter = routing.APIRouter(
routes=routes,
redirect_slashes=redirect_slashes,
dependency_overrides_provider=self,
on_startup=on_startup,
on_shutdown=on_shutdown,
lifespan=lifespan,
default_response_class=default_response_class,
dependencies=dependencies,
callbacks=callbacks,
deprecated=deprecated,
include_in_schema=include_in_schema,
responses=responses,
generate_unique_id_function=generate_unique_id_function,
)
使用
- add_api_route
- api_route
- add_api_websocket_route
- include_router
StarLettce
# ---------------------------------------------------------
# 1. 构造器:初始化路由表、中间件、生命周期等
# ---------------------------------------------------------
class Router:
def __init__(
self,
routes: Sequence[BaseRoute] | None = None,
redirect_slashes: bool = True, # ➜ 是否自动补全 / 做 308 跳转
default: ASGIApp | None = None, # ➜ 没有任何路由匹配时的兜底 ASGI 应用
on_startup: Sequence[Callable[[], Any]] | None = None,
on_shutdown: Sequence[Callable[[], Any]] | None = None,
lifespan: Lifespan[Any] | None = None, # ➜ 推荐的新生命周期钩子
*,
middleware: Sequence[Middleware] | None = None,
) -> None:
""" 初始化成员变量 """
self.routes = [] if routes is None else list(routes)
self.redirect_slashes = redirect_slashes
self.default = self.not_found if default is None else default
self.on_startup = [] if on_startup is None else list(on_startup)
self.on_shutdown = [] if on_shutdown is None else list(on_shutdown)
# ➜ 旧版 on_startup / on_shutdown 已弃用
if on_startup or on_shutdown:
warnings.warn(
"The on_startup and on_shutdown parameters are deprecated, and they "
"will be removed on version 1.0. Use the lifespan parameter instead. "
"See more about it on https://www.starlette.io/lifespan/ .",
DeprecationWarning,
)
if lifespan:
warnings.warn(
"The `lifespan` parameter cannot be used with `on_startup` or "
"`on_shutdown`. Both `on_startup` and `on_shutdown` will be "
"ignored."
)
# ➜ 整理 lifespan:支持三种写法(@asynccontextmanager、旧 async 生成器、旧同步生成器)
if lifespan is None:
self.lifespan_context: Lifespan[Any] = _DefaultLifespan(self)
elif inspect.isasyncgenfunction(lifespan):
warnings.warn(
"async generator function lifespans are deprecated, "
"use an @contextlib.asynccontextmanager function instead",
DeprecationWarning,
)
self.lifespan_context = asynccontextmanager(lifespan)
elif inspect.isgeneratorfunction(lifespan):
warnings.warn(
"generator function lifespans are deprecated, use an @contextlib.asynccontextmanager function instead",
DeprecationWarning,
)
self.lifespan_context = _wrap_gen_lifespan_context(lifespan)
else:
self.lifespan_context = lifespan
# ➜ 构建洋葱式中间件栈(逆序包一层)
self.middleware_stack = self.app
if middleware:
for cls, args, kwargs in reversed(middleware):
self.middleware_stack = cls(self.middleware_stack, *args, **kwargs)
async def not_found(self, scope: Scope, receive: Receive, send: Send) -> None:
""" 兜底 404 / WebSocket 关闭 """
if scope["type"] == "websocket":
# ➜ WebSocket 直接发送关闭帧
websocket_close = WebSocketClose()
await websocket_close(scope, receive, send)
return
# ➜ HTTP 场景:在 Starlette 内部抛异常,外层统一异常处理器回 404
if "app" in scope:
raise HTTPException(status_code=404)
else:
response = PlainTextResponse("Not Found", status_code=404)
await response(scope, receive, send)
def url_path_for(self, name: str, /, **path_params: Any) -> URLPath:
""" 反向 URL 生成(url_for) """
for route in self.routes:
try:
return route.url_path_for(name, **path_params)
except NoMatchFound:
pass
raise NoMatchFound(name, path_params)
async def startup(self) -> None:
""" 旧版 startup 事件执行 """
for handler in self.on_startup:
if is_async_callable(handler):
await handler()
else:
handler()
async def shutdown(self) -> None:
""" 旧版 shutdown 事件执行 """
for handler in self.on_shutdown:
if is_async_callable(handler):
await handler()
else:
handler()
async def lifespan(self, scope: Scope, receive: Receive, send: Send) -> None:
""" ASGI lifespan 协议实现(startup / shutdown) """
started = False
app: Any = scope.get("app")
await receive() # ➜ 先收到 lifespan.startup
try:
# ➜ 进入自定义 lifespan,yield 之前是 startup,之后是 shutdown
async with self.lifespan_context(app) as maybe_state:
if maybe_state is not None:
if "state" not in scope:
raise RuntimeError('The server does not support "state" in the lifespan scope.')
scope["state"].update(maybe_state)
# ➜ 通知服务器 startup 完成
await send({"type": "lifespan.startup.complete"})
started = True
await receive() # ➜ 等待 lifespan.shutdown
except BaseException:
exc_text = traceback.format_exc()
if started:
await send({"type": "lifespan.shutdown.failed", "message": exc_text})
else:
await send({"type": "lifespan.startup.failed", "message": exc_text})
raise
else:
await send({"type": "lifespan.shutdown.complete"})
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
"""
ASGI 应用入口(__call__)
把请求交给中间件栈(洋葱最外层)
"""
await self.middleware_stack(scope, receive, send)
async def app(self, scope: Scope, receive: Receive, send: Send) -> None:
""" 真正的业务分发逻辑 """
assert scope["type"] in ("http", "websocket", "lifespan")
# ➜ 把当前 Router 存进 scope,可以在依赖里拿到
if "router" not in scope:
scope["router"] = self
# ➜ lifespan 类型直接走 lifespan 处理器
if scope["type"] == "lifespan":
await self.lifespan(scope, receive, send)
return
partial = None
# ➜ 遍历路由表,找 FULL 或 PARTIAL 匹配
for route in self.routes:
match, child_scope = route.matches(scope)
if match == Match.FULL:
scope.update(child_scope)
await route.handle(scope, receive, send)
return
elif match == Match.PARTIAL and partial is None:
partial = route
partial_scope = child_scope
# ➜ 如果没有 FULL 只有 PARTIAL(405 场景)
if partial is not None:
scope.update(partial_scope)
await partial.handle(scope, receive, send)
return
# ➜ 尝试追加或去掉末尾斜杠再匹配一次(redirect_slashes)
route_path = get_route_path(scope)
if scope["type"] == "http" and self.redirect_slashes and route_path != "/":
redirect_scope = dict(scope)
if route_path.endswith("/"):
redirect_scope["path"] = redirect_scope["path"].rstrip("/")
else:
redirect_scope["path"] = redirect_scope["path"] + "/"
for route in self.routes:
match, child_scope = route.matches(redirect_scope)
if match != Match.NONE:
redirect_url = URL(scope=redirect_scope)
response = RedirectResponse(url=str(redirect_url))
await response(scope, receive, send)
return
# ➜ 真的没有任何匹配,走到 default(404)
await self.default(scope, receive, send)
def __eq__(self, other: Any) -> bool:
""" 辅助方法:动态添加路由 / 中间挂载 """
return isinstance(other, Router) and self.routes == other.routes
def mount(self, path: str, app: ASGIApp, name: str | None = None) -> None:
""" 把另外一个 ASGI 应用挂在指定路径 """
route = Mount(path, app=app, name=name)
self.routes.append(route)
def host(self, host: str, app: ASGIApp, name: str | None = None) -> None:
""" 基于 Host 头的虚拟主机路由 """
route = Host(host, app=app, name=name)
self.routes.append(route)
def add_route(
self,
path: str,
endpoint: Callable[[Request], Awaitable[Response] | Response],
methods: Collection[str] | None = None,
name: str | None = None,
include_in_schema: bool = True,
) -> None:
""" 添加普通 HTTP 路由 """
route = Route(
path,
endpoint=endpoint,
methods=methods,
name=name,
include_in_schema=include_in_schema,
)
self.routes.append(route)
def add_websocket_route(
self,
path: str,
endpoint: Callable[[WebSocket], Awaitable[None]],
name: str | None = None,
) -> None:
""" 添加 WebSocket 路由 """
route = WebSocketRoute(path, endpoint=endpoint, name=name)
self.routes.append(route)
def route(
self,
path: str,
methods: Collection[str] | None = None,
name: str | None = None,
include_in_schema: bool = True,
) -> Callable:
""" 已弃用的装饰器语法糖(不推荐) """
warnings.warn(
"The `route` decorator is deprecated...",
DeprecationWarning,
)
def decorator(func: Callable) -> Callable:
self.add_route(
path,
func,
methods=methods,
name=name,
include_in_schema=include_in_schema,
)
return func
return decorator
def websocket_route(self, path: str, name: str | None = None) -> Callable:
""" websocket路由,废弃 """
warnings.warn(
"The `websocket_route` decorator is deprecated...",
DeprecationWarning,
)
def decorator(func: Callable) -> Callable:
self.add_websocket_route(path, func, name=name)
return func
return decorator
def add_event_handler(self, event_type: str, func: Callable[[], Any]) -> None:
""" 已弃用的事件钩子 """
assert event_type in ("startup", "shutdown")
if event_type == "startup":
self.on_startup.append(func)
else:
self.on_shutdown.append(func)
def on_event(self, event_type: str) -> Callable:
""" 已弃用的事件钩子 """
warnings.warn(
"The `on_event` decorator is deprecated...",
DeprecationWarning,
)
def decorator(func: Callable) -> Callable:
self.add_event_handler(event_type, func)
return func
return decorator
On this page
← Previous postDify框架源码赏析-工作准备