路由

FastHTML 应用中的行为由路由定义。其语法与出色的 FastAPI 大致相同(如果你在创建 JSON 服务,那么应该使用 FastAPI 而不是这个。FastHTML 主要用于制作 HTML Web 应用,而不是 API)。

待完善

我们尚未为 FastHTML 的所有路由功能编写完整的文档——在我们添加之前,了解所有可用功能的最佳方式是查阅测试用例

请注意,你需要包含参数的类型,以便 FastHTML 知道要向你的函数传递什么。在这里,我们只期望一个字符串。

from fasthtml.common import *
app = FastHTML()

@app.get('/user/{nm}')
def get_nm(nm:str): return f"Good day to you, {nm}!"

通常你会将此代码保存到一个文件(例如 main.py)中,然后使用 uvicorn 来运行它:

uvicorn main:app

但是,为了测试,我们可以使用 Starlette 的 TestClient 来尝试一下。

from starlette.testclient import TestClient
client = TestClient(app)
r = client.get('/user/Jeremy')
r
<Response [200 OK]>

TestClient 在底层使用 httpx,因此它返回一个 httpx.Response 对象,该对象有一个包含我们响应体的 text 属性。

r.text
'Good day to you, Jeremy!'

在前面的例子中,函数名(get_nm)实际上并不重要——例如,我们本可以称之为 _,因为我们从未直接调用它。它只是通过 HTTP 被调用。事实上,在使用这种风格的路由时,我们经常将函数命名为 _,因为这样就少了一件需要担心的事情:命名。

创建路由的另一种方法是使用 app.route,在这种情况下,你需要将函数名设为你想要的 HTTP 方法。由于这是一种非常常见的模式,你可能会想给 app.route 起一个更短的名字——我们通常使用 rt

rt = app.route

@rt('/')
def post(): return "Going postal!"

client.post('/').text
'Going postal!'

特定于路由的功能

FastHTML 支持使用自定义装饰器为路由添加特定功能。这允许你为单个路由实现身份验证、授权、中间件或其他自定义行为。

这是一个基本身份验证装饰器的示例:

from functools import wraps

def basic_auth(f):
    @wraps(f)
    async def wrapper(req, *args, **kwargs):
        token = req.headers.get("Authorization")
        if token == 'abc123':
            return await f(req, *args, **kwargs)
        return Response('Not Authorized', status_code=401)
    return wrapper

@app.get("/protected")
@basic_auth
async def protected(req):
    return "Protected Content"

client.get('/protected', headers={'Authorization': 'abc123'}).text
'Protected Content'

该装饰器在路由函数执行前拦截请求。如果装饰器允许请求继续,它会调用原始的路由函数,并将请求及任何其他参数传递给它。

这种方法的一个关键优势是能够对不同的路由应用不同的行为。你还可以在单个路由上堆叠多个装饰器以组合功能。

def app_beforeware():
    print('App level beforeware')

app = FastHTML(before=Beforeware(app_beforeware))
client = TestClient(app)

def route_beforeware(f):
    @wraps(f)
    async def decorator(*args, **kwargs):
        print('Route level beforeware')
        return await f(*args, **kwargs)
    return decorator
    
def second_route_beforeware(f):
    @wraps(f)
    async def decorator(*args, **kwargs):
        print('Second route level beforeware')
        return await f(*args, **kwargs)
    return decorator

@app.get("/users")
@route_beforeware
@second_route_beforeware
async def users():
    return "Users Page"

client.get('/users').text
App level beforeware
Route level beforeware
Second route level beforeware
'Users Page'

这种灵活性允许对路由行为进行精细控制,使你能够根据需要定制每个端点的功能。虽然应用级别的“前件”(beforeware)对于全局操作仍然有用,但装饰器为特定于路由的定制提供了强大的工具。

组合路由

有时一个 FastHTML 项目会变得非常庞大,以至于将所有路由都放在 main.py 中会变得难以管理。或者,我们安装了一个基于 FastHTML 或 Starlette 的包,需要我们添加它的路由。

首先,让我们创建一个 books.py 模块,用来表示所有与书籍相关的视图。

# books.py
books_app, rt = fast_app()

books = ['A Guide to FastHTML', 'FastHTML Cookbook', 'FastHTML in 24 Hours']

@rt("/", name="list")
def get():
    return Titled("Books", *[P(book) for book in books])

让我们在主模块中挂载它。

from books import books_app

1app, rt = fast_app(routes=[Mount("/books", books_app, name="books")])

@rt("/")
def get():
    return Titled("Dashboard",
2        P(A(href="/books")("Books")),
        Hr(),
3        P(A(link=uri("books:list"))("Books")),
    )

serve()
1
我们使用 starlette.Mount 将路由添加到我们的路由列表中。我们提供名称 books,以便于发现和管理链接。更多相关信息请参阅此注解列表的第 2 项和第 3 项。
2
这个指向书籍列表视图的示例链接是手动创建的。虽然目的明确,但这使得将来更改链接模式变得更加困难。
3
这个示例链接使用了为书籍路由命名的 URL。这种方法的优点是它使管理大量链接变得更加容易。