Vanson's Eternal Blog

FastApi框架源码赏析-路由模块

Python fastapi.png
Published on
/7 mins read/---

Route

FastAPI

fastapi的routing.py源码

初始化

# 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

starlette的routing.py源码

 
# ---------------------------------------------------------
# 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