Skip to content

HTTP 路由

Starlette 拥有一个简单但功能强大的请求路由系统。路由表被定义为路由列表,并在实例化应用程序时传递。

from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from starlette.routing import Route


async def homepage(request):
    return PlainTextResponse("Homepage")

async def about(request):
    return PlainTextResponse("About")


routes = [
    Route("/", endpoint=homepage),
    Route("/about", endpoint=about),
]

app = Starlette(routes=routes)

endpoint 参数可以是以下之一:

  • 一个常规函数或异步函数,它接受一个 request 单一参数,并且应该返回一个响应。
  • 一个实现 ASGI 接口的类,例如 Starlette 的 HTTPEndpoint 。

路径参数

路径可以使用 URI 模板样式来捕获路径组件。

Route('/users/{username}', user)

默认情况下,这将捕获字符,直至路径末尾或下一个 /

您可以使用转换器来修改所捕获的内容。可用的转换器为:

  • str 返回一个字符串,并且是默认值。
  • int 返回一个 Python 整数。
  • float 返回一个 Python 浮点数。
  • uuid 返回一个 Python uuid.UUID 实例。
  • path 返回路径的其余部分,包括任何额外的 / 字符。

转换器通过在其前加上冒号来使用,如下所示:

Route('/users/{user_id:int}', user)
Route('/floating-point/{number:float}', floating_point)
Route('/uploaded/{rest_of_path:path}', uploaded)

如果您需要一个未定义的不同转换器,您可以创建自己的。请参阅下面有关如何创建 datetime 转换器以及如何注册它的示例:

from datetime import datetime

from starlette.convertors import Convertor, register_url_convertor


class DateTimeConvertor(Convertor):
    regex = "[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(.[0-9]+)?"

    def convert(self, value: str) -> datetime:
        return datetime.strptime(value, "%Y-%m-%dT%H:%M:%S")

    def to_string(self, value: datetime) -> str:
        return value.strftime("%Y-%m-%dT%H:%M:%S")

register_url_convertor("datetime", DateTimeConvertor())

注册后,您将能够将其用作:

Route('/history/{date:datetime}', history)

路径参数在请求中可用,作为 request.path_params 字典。

async def user(request):
    user_id = request.path_params['user_id']
    ...

处理 HTTP 方法

路由还可以指定端点处理哪些 HTTP 方法:

Route('/users/{user_id:int}', user, methods=["GET", "POST"])

默认情况下,函数端点将仅接受 GET 请求,除非另有指定。

子底座安装布线

在大型应用程序中,您可能会发现您想要根据常见的路径前缀拆分路由表的部分内容。

routes = [
    Route('/', homepage),
    Mount('/users', routes=[
        Route('/', users, methods=['GET', 'POST']),
        Route('/{username}', user),
    ])
]

这种风格允许您在项目的不同部分定义路由表的不同子集。

from myproject import users, auth

routes = [
    Route('/', homepage),
    Mount('/users', routes=users.routes),
    Mount('/auth', routes=auth.routes),
]

您还可以使用挂载将子应用程序包含在您的 Starlette 应用程序中。例如……

# This is a standalone static files server:
app = StaticFiles(directory="static")

# This is a static files server mounted within a Starlette application,
# underneath the "/static" path.
routes = [
    ...
    Mount("/static", app=StaticFiles(directory="static"), name="static")
]

app = Starlette(routes=routes)

反向 URL 查找

您通常会希望能够为特定路由生成 URL,例如在需要返回重定向响应的情况下。

  • 签名: url_for(name, **path_params) -> URL

    routes = [ Route("/", 主页页面, name="homepage") ]

    我们可以使用以下内容来返回一个 URL……

    url = request.url_for("首页")

URL 查找可以包含路径参数……

routes = [
    Route("/users/{username}", user, name="user_detail")
]

# We can use the following to return a URL...
url = request.url_for("user_detail", username=...)

如果一个 Mount 包含一个 name ,那么底座应该使用 {prefix}:{name} 样式进行反向 URL 查找。

routes = [
    Mount("/users", name="users", routes=[
        Route("/", user, name="user_list"),
        Route("/{username}", user, name="user_detail")
    ])
]

# We can use the following to return URLs...
url = request.url_for("users:user_list")
url = request.url_for("users:user_detail", username=...)

安装的应用程序可能包含一个 path=... 参数。

routes = [
    ...
    Mount("/static", app=StaticFiles(directory="static"), name="static")
]

# We can use the following to return URLs...
url = request.url_for("static", path="/css/base.css")

对于没有 request 实例的情况,您可以针对应用程序进行反向查找,尽管这些查找只会返回 URL 路径。

url = app.url_path_for("user_detail", username=...)

基于主机的路由

如果您想根据 Host 标头为相同路径使用不同的路由。

请注意,在匹配时会从 Host 标头中删除端口。例如,即使 Host 标头包含或不包含除 3600example.org:5600example.org )之外的端口, Host (host='example.org:3600', ...) 也会被处理。因此,如果您在 url_for 中需要使用端口,则可以指定该端口。

有几种方法可将基于主机的路由连接到您的应用程序

site = Router()  # Use eg. `@site.route()` to configure this.
api = Router()  # Use eg. `@api.route()` to configure this.
news = Router()  # Use eg. `@news.route()` to configure this.

routes = [
    Host('api.example.org', api, name="site_api")
]

app = Starlette(routes=routes)

app.host('www.example.org', site, name="main_site")

news_host = Host('news.example.org', news)
app.router.routes.append(news_host)

URL 查询可以包含主机参数,就像路径参数一样

routes = [
    Host("{subdomain}.example.org", name="sub", app=Router(routes=[
        Mount("/users", name="users", routes=[
            Route("/", user, name="user_list"),
            Route("/{username}", user, name="user_detail")
        ])
    ]))
]
...
url = request.url_for("sub:users:user_detail", username=..., subdomain=...)
url = request.url_for("sub:users:user_list", subdomain=...)

路由优先级

传入路径按顺序与每个 Route 进行匹配。

在可能有多个路由与传入路径匹配的情况下,您应注意确保在一般情况之前列出更具体的路由。

例如:

# Don't do this: `/users/me` will never match incoming requests.
routes = [
    Route('/users/{username}', user),
    Route('/users/me', current_user),
]

# Do this: `/users/me` is tested first.
routes = [
    Route('/users/me', current_user),
    Route('/users/{username}', user),
]

使用路由器实例进行工作

如果您在低级别工作,您可能想要使用普通的 Router 实例,而不是创建 Starlette 应用程序。这为您提供了一个轻量级的 ASGI 应用程序,该应用程序仅提供应用程序路由,而不会将其包装在任何中间件中。

app = Router(routes=[
    Route('/', homepage),
    Mount('/users', routes=[
        Route('/', users, methods=['GET', 'POST']),
        Route('/{username}', user),
    ])
])

WebSocket 路由

在使用 WebSocket 端点时,您应该使用 WebSocketRoute 而不是通常的 Route

路径参数以及针对 WebSocketRoute 的反向 URL 查找的工作方式与 HTTP Route 相同,这在上面的 HTTP 路由部分可以找到。

from starlette.applications import Starlette
from starlette.routing import WebSocketRoute


async def websocket_index(websocket):
    await websocket.accept()
    await websocket.send_text("Hello, websocket!")
    await websocket.close()


async def websocket_user(websocket):
    name = websocket.path_params["name"]
    await websocket.accept()
    await websocket.send_text(f"Hello, {name}")
    await websocket.close()


routes = [
    WebSocketRoute("/", endpoint=websocket_index),
    WebSocketRoute("/{name}", endpoint=websocket_user),
]

app = Starlette(routes=routes)

endpoint 参数可以是以下之一:

  • 一个异步函数,它接受一个单一的 websocket 参数。
  • 一个实现 ASGI 接口的类,例如 Starlette 的 WebSocketEndpoint 。