控制器
Loco
是一个框架,它封装了 axum,提供了一种直接的方式来管理路由、中间件、身份验证以及更多开箱即用的功能。在任何时候,你都可以利用强大的 axum Router,并使用你自定义的中间件和路由对其进行扩展。
Controllers 和路由
添加一个 Controller
提供了一个方便的代码生成器,以简化创建连接到你的项目的 starter controller。此外,还会生成一个测试文件,以便轻松测试你的 controller。
生成一个 controller:
生成 controller 后,导航到 src/controllers
中创建的文件,以查看 controller 端点。你还可以查看测试(在 tests/requests 文件夹中)文档,以了解如何测试此 controller。
显示激活的路由
要查看所有已注册 controller 的列表,请执行以下命令:
此命令将为你提供系统中当前注册的 controller 的全面概述。
添加状态
你的应用程序上下文和状态保存在 AppContext
中,这是 Loco 为你提供和设置的。在某些情况下,你可能希望在应用程序启动时加载自定义数据、逻辑或实体,并在所有 controller 中可用。
你可以通过使用 Axum 的 Extension
来做到这一点。这是一个加载 LLM 模型的示例,这是一个耗时的任务,然后将其提供给 controller 端点,在那里它已经被加载,并且可以立即使用。
首先,在 src/app.rs
中添加一个生命周期钩子:
// 在 src/app.rs 中,在你的 Hooks trait 实现中重写 `after_routes` 钩子:
async
接下来,在你喜欢的任何地方使用这个状态扩展。这是一个 controller 端点的示例:
async
全局应用范围的状态
有时你可能需要可以在 controller、worker 和应用程序的其他区域之间共享的状态。
你可以查看示例 shared-global-state 应用程序,以了解如何集成 libvips
,这是一个基于 C 的图像处理库。libvips
对开发者提出了一个奇怪的要求:在每个应用程序进程中保持加载它的单个实例。我们通过保留一个 single lazy_static
field 来实现这一点,并在应用程序的不同位置引用它。
阅读以下内容,了解如何在应用程序的每个部分中完成此操作。
Controller 中的共享状态
你可以使用本文档中提供的解决方案。一个实时的例子在这里。
Worker 中的共享状态
Worker 在 app hooks 中被有意地逐字初始化。
这意味着你可以将它们塑造为“常规”的 Rust 结构体,该结构体将状态作为字段。然后在 perform 中引用该字段。
这是 worker 如何在 shared-global-state
示例中使用全局 vips
实例初始化的。
请注意,根据设计,在 controller 和 worker 之间共享状态没有意义,因为即使你最初可以选择在与 controller 相同的进程中运行 worker(并共享状态)——你也会希望在水平扩展时快速切换到由队列支持并在独立的 worker 进程中运行的适当的 worker,因此 worker 根据设计应该与 controller 没有共享状态,这对你有利。
Task 中的共享状态
Task 实际上没有共享状态的价值,因为它们的生命周期与任何 exec 的二进制文件类似。进程启动、引导、创建所有需要的资源(连接到数据库等)、执行 task 逻辑,然后
Controller 中的路由
Controller 定义了 Loco 路由功能。在下面的示例中,一个 controller 创建了一个 GET 端点和一个 POST 端点:
use ;
new
.add
.add
你还可以使用 prefix
函数为 controller 中的所有路由定义一个 prefix
。
发送响应
响应发送器位于 format
模块中。以下是从你的路由发送响应的几种方法:
// 保持返回 `Result<impl IntoResponse>` 的最佳实践,以便能够透明地交换返回类型
pub async json
// 使用 `render` 为更复杂的响应提供构建器接口。你仍然可以使用
// `json`、`html` 或 `text` 终止
render
.etag?
.json
内容类型感知的响应
你可以选择加入响应器机制,其中会检测格式类型并将其传递给你。
使用 Format
提取器来实现此目的:
pub async
自定义错误
这里有一个案例,你可能希望根据不同的格式进行不同的渲染,并且还希望根据你收到的错误类型进行不同的渲染。
pub async
在这里,我们还通过首先将工作流程包装在一个函数中并获取结果类型来“集中”我们的错误处理。
接下来,我们创建一个 2 级匹配来:
- 匹配结果类型
- 匹配格式类型
在我们缺乏处理知识的地方,我们只是按原样返回错误,并让框架渲染默认错误。
手动创建 Controller
1. 创建 Controller 文件
首先在路径 src/controllers
下创建一个新文件。例如,让我们创建一个名为 example.rs
的文件。
2. 在 mod.rs 中加载文件
确保你在 src/controllers
文件夹中的 mod.rs
文件中加载新创建的 controller 文件。
3. 在 App Hooks 中注册 Controller
在你的 App hook 实现(例如,App 结构体)中,将你的 controller 的 Routes
添加到 AppRoutes
:
// src/app.rs
;
中间件
Loco 开箱即用地提供了一组内置中间件。有些默认启用,而另一些则需要配置。中间件注册非常灵活,可以通过 *.yaml
环境配置或直接在代码中进行管理。
默认堆栈
你将获得所有已启用的中间件,运行以下命令
这是 development
模式下的堆栈:
)
)
)
)
)
示例:禁用所有中间件
采用任何已启用的中间件,并在相关字段中使用 enable: false
。如果 server
中的 middlewares:
部分缺失,请添加它。
server:
middlewares:
cors:
enable: false
catch_panic:
enable: false
etag:
enable: false
logger:
enable: false
request_id:
enable: false
fallback:
enable: false
结果:
)
)
)
)
)
)
)
)
)
)
)
你可以通过更改 server.ident
的值来控制 powered_by
中间件:
server:
ident: my-server #(或空字符串禁用)
示例:添加一个非默认中间件
让我们将 Remote IP 中间件添加到堆栈中。这只需通过配置即可完成:
server:
middlewares:
remote_ip:
enable: true
结果:
示例:更改已启用中间件的配置
让我们将请求体限制更改为 5mb
。当覆盖中间件配置时,请记住保留 enable: true
:
middlewares:
limit_payload:
body_limit: 5mb
结果:
)
)
)
)
)
身份验证
在 Loco
框架中,中间件在身份验证中起着至关重要的作用。Loco
支持各种身份验证方法,包括 JSON Web Token (JWT) 和 API Key 身份验证。本节概述如何在你的应用程序中配置和使用身份验证中间件。
JSON Web Token (JWT)
配置
默认情况下,Loco 对 JWT 使用 Bearer 身份验证。但是,你可以在配置文件中的 auth.jwt 部分自定义此行为。
- Bearer 身份验证: 将配置留空或显式设置为如下:
# 身份验证配置 auth: # JWT 身份验证 jwt: location: Bearer ...
- Cookie 身份验证: 配置从中提取 token 的位置并指定 cookie 名称:
# 身份验证配置 auth: # JWT 身份验证 jwt: location: from: Cookie name: token ...
- 查询参数身份验证: 指定查询参数的位置和名称:
# 身份验证配置 auth: # JWT 身份验证 jwt: location: from: Query name: token ...
用法
在你的 controller 参数中,使用 auth::JWT
进行身份验证。这将根据配置的设置触发身份验证验证。
use *;
async
此外,你可以通过将 auth::JWT 替换为 auth::ApiToken<users::Model>
来获取当前用户。
API Key
对于 API Key 身份验证,请使用 auth::ApiToken。此中间件根据用户数据库记录验证 API Key,并将相应的用户加载到身份验证参数中。
use *;
async
Catch Panic
此中间件捕获应用程序请求处理期间发生的 panic。当发生 panic 时,它会记录错误并返回内部服务器错误响应。此中间件有助于确保应用程序可以优雅地处理意外错误,而不会导致服务器崩溃。
要禁用中间件,请按如下方式编辑配置:
#...
middlewares:
catch_panic:
enable: false
Limit Payload
Limit Payload 中间件限制 HTTP 请求 payload 允许的最大大小。默认情况下,它已启用并配置为 2MB 限制。
你可以通过配置文件自定义或禁用此行为。
设置自定义限制
#...
middlewares:
limit_payload:
body_limit: 5mb
禁用 payload 大小限制
要完全删除限制,请将 body_limit
设置为 disable
:
#...
middlewares:
limit_payload:
body_limit: disable
用法
在你的 controller 参数中,使用 axum::body::Bytes
。
use *;
async
Timeout
对应用程序处理的请求应用超时。该中间件确保请求不会超出指定的超时时间运行,从而提高应用程序的整体性能和响应能力。
如果请求超过指定的超时时长,中间件将向客户端返回 408 Request Timeout
状态代码,表明请求处理时间过长。
要启用中间件,请按如下方式编辑配置:
#...
middlewares:
timeout_request:
enable: false
timeout: 5000
Logger
为 HTTP 请求提供日志记录功能。有关每个请求的详细信息,例如 HTTP 方法、URI、版本、用户代理和关联的请求 ID。此外,它还将应用程序的运行时环境集成到日志上下文中,从而允许特定于环境的日志记录(例如,“development”、“production”)。
要禁用中间件,请按如下方式编辑配置:
#...
middlewares:
logger:
enable: false
Fallback
当选择 SaaS starter(或任何非 API-first 的 starter)时,你将获得具有 Loco 欢迎屏幕 的默认回退行为。这是一种仅限开发模式,其中 404
请求会向你显示一个友好且友好的页面,告诉你发生了什么以及下一步该怎么做。
你可以在 development.yaml
文件中禁用或自定义此行为。你可以设置几个选项:
# 默认的预制欢迎屏幕
fallback:
enable: true
# 不同的预定义 404 页面
fallback:
enable: true
file: assets/404.html
# 一条消息,并将状态代码自定义为返回 200 而不是 404
fallback:
enable: true
code: 200
not_found: cannot find this resource
对于生产环境,建议禁用此功能。
# 禁用。你也可以完全删除 `fallback` 部分以禁用
fallback:
enable: false
Remote IP
当你的应用程序位于代理或负载均衡器(例如 Nginx、ELB 等)下时,它不会直接面向互联网,这就是为什么如果你想找出连接客户端 IP,你将获得一个套接字,该套接字指示的 IP 实际上是你的负载均衡器。
负载均衡器或代理负责处理针对真实客户端 IP 的套接字工作,然后通过代理回连将负载提供给你的应用程序。
这就是为什么当你的应用程序对获取真实客户端 IP 有具体的业务需求时,你需要使用事实上的标准代理和负载均衡器用于向你传递此信息的标头:X-Forwarded-For
标头。
Loco 提供了 remote_ip
部分来配置 RemoteIP
中间件:
server:
middleware:
# 当位于代理或负载均衡器后时,根据 `X-Forwarded-For` 计算远程 IP
# 使用 RemoteIP(..) 提取器获取远程 IP。
# 如果没有此中间件,你将获得代理 IP。
# 更多信息:https://github.com/rails/rails/blob/main/actionpack/lib/action_dispatch/middleware/remote_ip.rb
#
# 注意!仅在位于代理下时启用,否则可能会导致 IP 欺骗漏洞
# 相信我,如果你需要此中间件,你会知道的。
remote_ip:
enable: true
# # 替换默认的可信代理:
# trusted_proxies:
# - ip range 1
# - ip range 2 ..
# 生成唯一的请求 ID 并使用其他信息(例如请求处理的开始和完成、延迟、状态代码和其他请求详细信息)增强日志记录。
然后,使用 RemoteIP
提取器获取 IP:
pub async
当使用 RemoteIP
中间件时,请注意安全隐患与你当前的架构的对比(如文档和配置部分所述):如果你的应用程序不在代理下,你可能会容易受到 IP 欺骗漏洞的攻击,因为任何人都可以将标头设置为任意值,特别是,任何人都可以设置 X-Forwarded-For
标头。
默认情况下,此中间件未启用。通常,你 会知道 你是否需要此中间件,并且你将意识到在正确的架构中使用它的安全方面。如果你不确定——请不要使用它(保持 enable
为 false
)。
Secure Headers
Loco 带有默认的安全标头,由 secure_headers
中间件应用。这类似于 Rails 生态系统中使用 secure_headers 完成的操作。
在你的 server.middleware
YAML 部分中,你将默认找到 github
预设(这是 Github 和 Twitter 推荐的安全标头)。
server:
middleware:
# 设置安全标头
secure_headers:
preset: github
你还可以覆盖选定的标头:
server:
middleware:
# 设置安全标头
secure_headers:
preset: github
overrides:
foo: bar
或者从头开始:
server:
middleware:
# 设置安全标头
secure_headers:
preset: empty
overrides:
foo: bar
为了支持 htmx
,你可以添加以下覆盖,以允许一些内联运行脚本:
secure_headers:
preset: github
overrides:
# 这允许你使用 HTMX,并具有 unsafe-inline。在生产环境中移除或考虑
"Content-Security-Policy": "default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src 'unsafe-inline' 'self' https:; style-src 'self' https: 'unsafe-inline'"
Compression
Loco
利用 CompressionLayer 来实现 一键式
解决方案。
要启用基于 accept-encoding
请求标头的响应压缩,只需按如下方式编辑配置:
#...
middlewares:
compression:
enable: true
这样做将压缩每个响应并相应地设置 content-encoding
响应标头。
预压缩资源
Loco
利用 ServeDir::precompressed_gzip 来实现 一键式
解决方案,以提供预压缩的资源。
如果静态资源以 .gz
文件的形式存在于磁盘上,则 Loco
将提供它,而不是动态压缩它。
#...
middlewares:
...
static_assets:
...
precompressed: true
CORS
此中间件通过允许 HTTP 请求中可配置的来源、方法和标头来启用跨域资源共享 (CORS)。 它可以根据各种应用程序需求进行定制,支持宽松的 CORS 或中间件配置中定义的特定规则。
#...
middlewares:
...
cors:
enable: true
# 设置 [`Access-Control-Allow-Origin`][mdn] 标头的值
# allow_origins:
# - https://loco.rs
# 设置 [`Access-Control-Allow-Headers`][mdn] 标头的值
# allow_headers:
# - Content-Type
# 设置 [`Access-Control-Allow-Methods`][mdn] 标头的值
# allow_methods:
# - POST
# 以秒为单位设置 [`Access-Control-Max-Age`][mdn] 标头的值
# max_age: 3600
基于 Handler 和路由的中间件
Loco
还允许我们将 layers 应用于特定的 handler 或
路由。
有关基于 handler 和路由的中间件的更多信息,请参阅 middleware
文档。
基于 Handler 的中间件:
使用 layer
方法将 layer 应用于特定的 handler。
// src/controllers/auth.rs
基于路由的中间件:
使用 layer
方法将 layer 应用于特定的路由。
// src/main.rs
;
请求验证
JsonValidate
提取器通过与 validator crate 集成,简化了输入验证。以下是如何验证传入请求数据的示例:
定义你的验证规则
use debug_handler;
use *;
use Deserialize;
use Validate;
创建带有验证的 Handler
use debug_handler;
use *;
pub async
使用 JsonValidate
提取器,Loco 自动对 DataParams 结构执行验证:
- 如果验证通过,handler 将继续使用 params 执行。
- 如果验证失败,则返回 400 Bad Request 响应。
以 JSON 格式返回验证错误
如果你想以结构化的 JSON 格式返回验证错误,请使用 JsonValidateWithMessage
而不是 JsonValidate
。响应格式将如下所示:
分页
在许多场景中,当查询数据并向用户返回响应时,分页至关重要。在 Loco
中,我们提供了一种直接的方法来分页你的数据,并为你的 API 响应保持一致的分页响应模式。
我们假设你有一个 notes
实体和/或 scaffold(用你喜欢的任何实体替换它)。
使用分页
use *;
let res = fetch_page.await;
使用带过滤器的分页
use *;
let pagination_query = PaginationQuery ;
let condition = condition.contains;
let paginated_notes = paginate
.await?;
- 首先定义你要检索的实体。
- 创建你的查询条件(在本例中,过滤标题列中包含“loco”的行)。
- 定义分页参数。
- 调用 paginate 函数。
分页视图
在前面的示例中创建并获取 paginated_notes
后,你可以选择要从模型返回哪些字段,并在所有不同的数据响应中保持相同的分页响应。
在 Loco 视图中定义你返回给用户的数据。如果你不熟悉视图,请参阅文档以获取更多上下文。
在 src/view/notes
中创建一个 notes 视图文件,其中包含以下代码:
use ;
use ;
use crate notes;
测试
当测试 controller 时,目标是调用路由的 controller 端点并验证 HTTP 响应,包括状态代码、响应内容、标头等。
要初始化测试请求,请使用 use loco_rs::testing::prelude::*;
,它会准备你的应用程序路由器,提供请求实例和应用程序上下文。
在以下示例中,我们有一个 POST 端点,它返回在 POST 请求中发送的数据。
use *;
async
正如你所见,初始化测试请求并使用 request
实例调用 /example 端点。
请求返回一个 Response
实例,其中包含状态代码和响应测试。