Salvo 是一个基于 Rust 语言构建的异步、高性能、功能丰富的 Web 服务器框架。它设计简洁、模块化,易于使用和扩展,并提供了强大的路由、中间件、数据提取、WebSocket、TLS (支持 OpenSSL/Rustls 证书热重载)、HTTP/3、反向代理、请求超时控制等支持。

concurrency-limiter

此示例演示了如何使用 Salvo 的 concurrency-limiter 中间件来限制可以同时处理的并发请求数量。这对于控制资源密集型操作(如文件上传)的负载非常有用。

此示例设置了两个文件上传端点:

  1. /limited:此路径应用了 max_concurrency(1) 中间件,意味着一次只允许处理一个上传请求。后续的请求将排队等待,直到当前请求完成。
  2. /unlimit:此路径没有并发限制,可以同时处理任意数量的上传请求。

两个端点都使用同一个 upload 处理器,该处理器模拟了一个耗时 10 秒的操作(tokio::time::sleep),以清晰地展示并发限制的效果。index 处理器提供一个包含两个上传表单的 HTML 页面,分别指向这两个端点。

文件列表

  • Cargo.toml: 定义项目依赖,并通过 features = ["concurrency-limiter"] 启用了 Salvo 的并发限制器功能。
  • src/main.rs: 包含主要的应用程序逻辑:
    • 定义 index 处理器以提供 HTML 表单。
    • 定义 upload 处理器来处理文件上传并模拟延迟。
    • 设置路由器,为 /limited 路径应用 max_concurrency(1) 中间件。
    • 创建临时目录 temp 用于存储上传的文件。
    • 启动服务器监听 0.0.0.0:5800

csrf-session-store

此示例展示了如何使用 Salvo 的 CSRF (跨站请求伪造) 保护功能,并将 CSRF token 存储在会话 (Session) 中。它演示了多种不同的加密/签名算法来保护 token。

为了实现 CSRF 保护,该示例依赖于 Salvo 的 session 功能来存储每个客户端的 CSRF token。主要步骤包括:

  1. 会话设置: 配置 SessionHandler,使用 MemoryStore (内存存储) 和一个密钥来管理会话数据。
  2. CSRF Token 生成与验证:
    • 使用 FormFinder 指定从表单的哪个字段 (csrf_token) 提取 token。
    • 初始化多种 CSRF 保护中间件实例,每种使用不同的算法(Bcrypt, HMAC, AES-GCM, ChaCha20Poly1305),并将它们配置为使用会话存储 (bcrypt_session_csrf, hmac_session_csrf 等)。
    • 在 GET 请求处理器 (get_page) 中,通过 depot.csrf_token() 获取(或生成新的)CSRF token,并将其嵌入到返回的 HTML 表单中。
    • 在 POST 请求处理器 (post_page) 中,Salvo 的 CSRF 中间件会自动验证请求中提交的 token 是否与会话中存储的匹配。如果验证通过,处理器逻辑才会执行;否则,将返回错误。处理器随后会再次生成新的 token 用于下一次请求。
  3. 路由: 设置不同的路径 (/bcrypt/, /hmac/, /aes_gcm/, /ccp/),每个路径应用对应算法的 CSRF 中间件。所有这些路径都共享 get_pagepost_page 处理器。

此示例的核心在于演示如何结合会话管理来实现有状态的 CSRF 保护,并提供了多种安全级别的算法选项。

文件列表

  • Cargo.toml: 引入 salvo 依赖,并启用 csrfsession 特性。同时需要 serde 用于表单数据的序列化/反序列化。
  • src/main.rs:
    • 定义 home, get_page, post_page 处理器。
    • 配置 SessionHandler 和各种基于 Session 的 Csrf 中间件。
    • get_page 中生成并显示 CSRF token。
    • post_page 中解析表单数据(包含 csrf_token),CSRF 中间件隐式执行验证。
    • 设置路由,将不同的 CSRF 中间件应用到相应的路径。
    • 启动服务器。

webtransport

此示例演示了如何使用 Salvo 和 Quinn (一个 QUIC 协议实现) 来创建一个 WebTransport 服务器。WebTransport 是一种基于 HTTP/3 的现代双向通信协议,提供低延迟的数据传输,适用于实时应用。

该示例实现了一个简单的 echo 服务器,可以通过 WebTransport 连接:

  1. 协议基础: 它依赖于 QUIC (HTTP/3) 协议,因此需要 TLS 加密。示例中使用自签名证书进行本地测试。
  2. 监听器: 使用 QuinnListener 来处理 QUIC 连接,并通过 join 与一个标准的 TcpListener (使用 Rustls 处理 TLS) 结合,允许服务器同时处理 HTTP/1.1 或 HTTP/2(用于提供静态文件和可能的备用连接)和 HTTP/3 (用于 WebTransport)。
  3. 连接处理:
    • connect 处理器通过 req.web_transport_mut().await 获取 WebTransport 会话。
    • 服务器在连接建立后立即主动打开一个双向流 (session.open_bi) 向客户端发送消息。
    • 服务器进入循环,等待并处理来自客户端的不同类型的交互:
      • 数据报 (Datagrams): session.accept_datagram() 接收不可靠的数据报,并在前面加上 "Response: " 后 echo 回去。
      • 单向流 (Unidirectional Streams): session.accept_uni() 接收客户端发起的单向流,读取所有数据,然后通过服务器端打开的对应单向流 (session.open_uni) 将数据分块 echo 回去。
      • 双向流 (Bidirectional Streams): session.accept_bi() 接收客户端发起的双向流,将其分割为读写两半,然后同样将接收到的数据分块 echo 回去。
  4. 静态文件服务: 使用 StaticDir 提供了一个简单的 HTML/JS 客户端页面 (static/client.html, static/client.js),用户可以通过浏览器访问此页面来与 WebTransport 服务器交互。

此示例展示了 Salvo 处理高级网络协议 (如 WebTransport) 的能力,以及如何管理其不同的通信模式(数据报、单向/双向流)。

文件列表

  • Cargo.toml:
    • 启用 salvoquinn (提供 WebTransport 和 HTTP/3 支持) 和 serve-static (提供静态文件服务) 特性。
    • 包含 anyhow (错误处理), bytes (数据处理), futures-util (异步流处理) 等辅助库。
  • src/main.rs:
    • 定义 connect 处理器,核心的 WebTransport 会话处理逻辑。
    • 定义 echo_streamsend_chunked 辅助函数处理流数据。
    • 配置 RustlsConfig 加载证书。
    • 设置 QuinnListenerTcpListener,并将它们绑定到端口 5800。
    • 设置路由,将 /counter 路径指向 connect 处理器,并将其他路径用于提供静态文件。
    • 启动服务器。
  • certs/: 包含用于本地测试的自签名证书 (cert.pem) 和私钥 (key.pem)。
  • static/:
    • client.html: 提供给用户的 WebTransport 客户端界面。
    • client.js, client.css: 客户端的 JavaScript 逻辑和样式。

catch-error

此示例演示了 Salvo 如何捕获和处理来自处理器 (Handler) 的不同类型的错误,包括标准库错误、anyhow::Erroreyre::Report 以及自定义错误类型。

Salvo 处理器可以返回 Result<T, E>。如果返回 Ok(T),则正常处理 T(通常 T 实现了 Writer trait 来写入响应)。如果返回 Err(E),Salvo 会尝试处理这个错误 E

  1. 内置错误处理: Salvo 对一些常见的错误类型(如 salvo::Error, std::io::Error 等)有内置的处理逻辑,通常会转换成合适的 HTTP 状态码和响应体。
  2. 通过 Writer Trait 处理: 如果错误类型 E 实现了 salvo::Writer trait,Salvo 会调用其 write 方法,允许你完全自定义错误对应的 HTTP 响应。示例中的 CustomError 就是这样处理的,它实现了 Writer trait,将响应状态码设置为 500 并写入自定义消息 "custom error"。
  3. 集成 anyhoweyre: 当启用 salvoanyhoweyre 特性时,Salvo 会自动为 anyhow::Erroreyre::Report 提供 Writer 实现。这意味着你可以直接在处理器中返回 Result<(), anyhow::Error>Result<(), eyre::Result>,Salvo 会捕获这些错误并生成一个通常包含错误信息的 500 Internal Server Error 响应。

此示例通过三个不同的处理器展示了这几种情况:

  • handle_anyhow: 返回 anyhow::anyhow!("handled anyhow error")
  • handle_eyre: 返回 eyre::Report::msg("handled eyre error")
  • handle_custom: 返回自定义的 Err(CustomError).

访问对应的路径 (/anyhow, /eyre, /custom) 会触发相应的错误,并观察 Salvo 如何根据错误类型生成响应。

文件列表

  • Cargo.toml:
    • 启用 salvoanyhoweyre 特性以集成这两种错误处理库。
    • 包含 anyhoweyre 依赖。
  • src/main.rs:
    • 定义 CustomError 结构体。
    • CustomError 实现 Writer trait来自定义错误响应。
    • 定义 handle_anyhow, handle_eyre, handle_custom 三个处理器,分别返回不同类型的错误。
    • 设置路由,将路径映射到相应的错误处理器。
    • 启动服务器。

join-listeners

此示例演示了如何让 Salvo 服务器同时监听多个 TCP 地址和端口。

通过 TcpListenerjoin() 方法,可以将多个监听器配置组合在一起。当调用 bind().await 时,Salvo 会尝试绑定所有指定的地址和端口。服务器随后会接受来自任何一个监听器的连接。

在这个具体的例子中:

  • TcpListener::new("0.0.0.0:5800") 创建一个监听 5800 端口的配置。
  • .join(TcpListener::new("0.0.0.0:5801")) 将监听 5801 端口的配置加入。
  • bind().await 实际执行绑定操作,使服务器同时在 5800 和 5801 端口上接受连接。

访问 http://localhost:5800http://localhost:5801 都会路由到同一个 hello 处理器,并得到 "Hello World" 响应。

文件列表

  • Cargo.toml: 包含基本的 salvotokio 依赖。
  • src/main.rs:
    • 定义简单的 hello 处理器。
    • 使用 TcpListener::new(...).join(...) 配置多端口监听。
    • bind().await 绑定监听器。
    • 启动服务器,为所有监听的端口提供相同的路由服务。

oapi-todos

此示例展示了如何使用 Salvo 的 oapi (OpenAPI) 功能来构建一个带有交互式 API 文档的 TODO List RESTful API。

核心特性和步骤:

  1. 启用 OAPI: 在 Cargo.toml 中启用 salvooapi 特性。
  2. 定义数据模型: 创建 Todo 结构体,并使用 #[derive(Serialize, Deserialize, ToSchema)] 让它能被序列化/反序列化,并自动生成 OpenAPI Schema。可以使用 #[salvo(schema(example = ...))] 提供示例值。
  3. 定义 API 端点:
    • 使用 #[endpoint] 宏标记处理 HTTP 请求的函数。这个宏会自动处理请求解析、响应序列化,并为 OpenAPI 文档收集元数据。
    • 使用 oapi::extract::* 中的提取器(如 QueryParam, JsonBody, PathParam)来声明和获取请求参数/体。这些提取器会自动将参数信息添加到 OpenAPI 文档。
    • 可以在 #[endpoint] 中添加 tags(...), status_codes(...), parameters(...) 等属性来丰富 OpenAPI 文档信息。
  4. 实现业务逻辑: 编写端点函数的具体逻辑,如此示例中的 list_todos, create_todo, update_todo, delete_todo,它们操作一个内存中的 STORE (使用 tokio::sync::Mutex<Vec<Todo>>) 来模拟数据库。
  5. 生成 OpenAPI 文档:
    • 创建一个 OpenApi 实例,提供 API 的标题和版本。
    • 调用 .merge_router(&router) 将路由信息和从 #[endpoint] 宏收集到的元数据合并到 OpenAPI 文档对象中。
  6. 提供 API 文档 UI:
    • 将生成的 OpenApi 文档对象转换为一个路由,通常提供 JSON 格式的文档 (doc.into_router("/api-doc/openapi.json"))。
    • 添加 Salvo 提供的多种 API 文档 UI 路由:SwaggerUi, Scalar, RapiDoc, ReDoc。它们都指向之前生成的 JSON 文档路径。
  7. 组合路由并启动: 将业务 API 路由和文档 UI 路由组合起来,然后启动服务器。

访问 /swagger-ui, /scalar, /rapidoc, 或 /redoc 可以查看和交互式地测试 TODO API。

文件列表

  • Cargo.toml: 启用 salvooapi 特性,并包含 serde (数据序列化) 和 tokio (异步运行时) 依赖。
  • src/main.rs:
    • 定义 Todo 数据结构和内存 STORE
    • 使用 #[endpoint] 定义 CRUD 操作的处理器 (list_todos, create_todo, update_todo, delete_todo)。
    • 创建 Router 注册业务 API 路径。
    • 创建 OpenApi 实例并合并路由信息。
    • 添加 OpenAPI JSON 端点和多个 UI (Swagger, Scalar, RapiDoc, ReDoc) 的路由。
    • 定义 index 处理器提供一个简单的 HTML 页面链接到各个文档 UI。
    • 启动服务器。

db-mongodb

此示例演示了如何在 Salvo 应用中集成 MongoDB 数据库,实现基本的 CRUD (创建、读取、更新、删除 - 此示例主要展示创建和读取) 操作。

主要步骤和概念:

  1. 添加依赖: 在 Cargo.toml 中添加 mongodb crate。
  2. 数据库连接:
    • main 函数中,使用 mongodb::Client::with_uri_str 连接到 MongoDB 实例。连接 URI 通常从环境变量读取或使用默认值。
    • 为了在处理器中方便地访问数据库客户端,示例使用了 std::sync::OnceLockClient 实例存储为全局静态变量 MONGODB_CLIENT。提供了一个辅助函数 get_mongodb_client() 来获取它。
  3. 定义数据模型: 创建 User 结构体,并使用 #[derive(Debug, Deserialize, Serialize)] 使其可以与 BSON (MongoDB 的文档格式) 进行相互转换。注意 _id 字段通常是 Option<ObjectId> 类型。
  4. 创建索引 (可选但推荐): 示例中定义了一个 create_username_index 函数,在启动时为 users 集合的 username 字段创建一个唯一索引,以确保用户名的唯一性并提高查询性能。
  5. 实现数据库操作处理器:
    • add_user:
      • 从请求体中解析 User 数据 (req.parse_json::<User>())。
      • 获取 MongoDB 集合 (client.database(DB_NAME).collection::<Document>(COLL_NAME))。注意这里使用了 Document 类型,也可以直接用 User 类型 если BSON 结构匹配。
      • User 数据转换为 BSON Document (doc!{ ... })。
      • 调用 coll_users.insert_one() 将文档插入数据库。
      • 根据结果向客户端返回成功或错误信息。
    • get_users:
      • 获取 User 类型的集合 (collection::<User>(COLL_NAME))。
      • 调用 coll_users.find(None, None) 查询所有文档,返回一个 Cursor
      • 使用 try_stream_ext::TryStreamExt (通过 cursor.try_next().await?) 迭代 Cursor,将结果收集到 Vec<User> 中。
      • 将用户列表以 JSON 格式返回 (res.render(Json(vec_users)))。
    • get_user:
      • 从 URL 路径参数中获取 username (req.param::<String>("username"))。
      • 调用 coll_users.find_one(doc! { "username": &username }, None) 按用户名查询单个文档。
      • 处理查询结果:如果找到用户,返回 JSON;如果未找到,返回提示信息;如果出错,返回错误信息。
  6. 路由设置: 创建 Router 并将相应的 HTTP 方法和路径 (/users, /users/{username}) 映射到对应的处理器。
  7. 错误处理: 示例定义了一个简单的 Error 枚举来包装 mongodb::error::Error,并为其实现了 Writer trait(尽管在此示例中 write 方法为空,实际应用中应提供更有意义的错误响应)。处理器返回 AppResult<T> (即 Result<T, Error>)。

文件列表

  • Cargo.toml: 添加 mongodb, serde, serde_json, thiserror (用于定义自定义错误) 依赖。futures 通常是 mongodb 的间接依赖,这里显式列出可能用于 TryStreamExt
  • src/main.rs:
    • 定义 User 模型和 Error 枚举。
    • 实现 ErrorWriter trait。
    • 初始化 MongoDB 连接并存储在 OnceLock 中。
    • 实现 add_user, get_users, get_user 处理器,包含数据库操作逻辑。
    • 实现 create_username_index 函数。
    • 设置路由。
    • 启动服务器。

websocket

此示例展示了如何在 Salvo 中处理 WebSocket 连接。WebSocket 提供了一种在客户端和服务器之间进行全双工通信的协议,常用于实时应用。

主要步骤:

  1. 启用 WebSocket: 在 Cargo.toml 中启用 salvowebsocket 特性。
  2. 升级请求: WebSocket 连接始于一个标准的 HTTP GET 请求,该请求包含特殊的头部(如 Upgrade: websocket, Connection: Upgrade)来请求协议升级。
  3. 处理器逻辑:
    • 创建一个处理器(如此示例中的 connect)。
    • 在该处理器中,使用 salvo::websocket::WebSocketUpgrade::new().upgrade(req, res, |mut ws| async move { ... }) 来处理升级请求。
    • upgrade 方法接收一个闭包,该闭包在 WebSocket 连接成功建立后执行。闭包接收一个 WebSocket 对象 ws 作为参数。
    • 在闭包的 async move 块中,你可以使用 ws.recv().await 来异步地接收来自客户端的消息(Message 类型),并使用 ws.send(msg).await 来异步地向客户端发送消息。
    • 你需要处理 recv() 可能返回的 None (表示连接已关闭) 或 Err (表示发生错误)。同样,send() 也可能返回 Err (通常表示客户端已断开连接)。
    • 示例中的闭包简单地将接收到的任何消息 echo 回客户端。
    • 示例还演示了如何在升级前从请求查询参数中解析数据 (req.parse_queries::<User>()),并将这些数据(如用户信息)传递到 WebSocket 处理闭包中使用。
  4. 路由: 将配置了 WebSocket 升级处理器的路径(如 /ws)添加到路由器。
  5. 客户端: 示例包含一个简单的 HTML 页面 (INDEX_HTML),其中使用 JavaScript 的 WebSocket API 连接到服务器的 /ws 端点。它还演示了如何在 URL 查询字符串中传递参数 (?id=123&name=chris)。

文件列表

  • Cargo.toml: 启用 salvowebsocket 特性。需要 serde 用于解析查询参数中的 User 结构。futures-util 可能被 salvo 内部或 WebSocket 处理需要。
  • src/main.rs:
    • 定义 User 结构体用于从查询参数解析数据。
    • 定义 connect 处理器,使用 WebSocketUpgrade 处理连接升级和后续的 WebSocket 消息收发(echo 逻辑)。
    • 定义 index 处理器提供包含客户端 JavaScript 的 HTML 页面。
    • 设置路由,将 /ws 指向 connect 处理器。
    • 启动服务器。

sse

此示例演示了如何使用 Salvo 实现 Server-Sent Events (SSE)。SSE 是一种允许服务器向客户端单向推送更新的标准 HTTP 协议,常用于实时通知或数据流。

核心概念:

  1. 启用 SSE: 在 Cargo.toml 中启用 salvosse 特性。
  2. 事件流: SSE 的核心是服务器向客户端发送一个事件流 (event stream)。这个流是一个遵循特定格式的文本响应 (Content-Type: text/event-stream),由一系列事件组成。
  3. 事件格式: 每个 SSE 事件通常由一个或多个字段组成,如 event: <event_name>, data: <event_data>, id: <event_id>, retry: <reconnection_time>。事件之间用空行分隔。Salvo 提供了 SseEvent 结构体来方便地构建这些事件。
  4. 创建事件流:
    • 你需要创建一个实现了 futures_util::Stream<Item = Result<SseEvent, E>> 的异步流,其中 E 是某种错误类型(如果是 infallible stream,可以用 std::convert::Infallible)。
    • 此示例中使用 tokio::time::interval 创建一个每秒触发一次的定时器。
    • tokio_stream::wrappers::IntervalStream 将定时器转换为一个异步流。
    • 使用 .map() 处理流中的每个项(定时器触发信号),递增一个计数器 counter,并调用 sse_counter(counter) 函数将计数器值包装成一个 SseEvent(具体是只包含 data 字段的事件)。
  5. 发送流:
    • 在处理器(如 handle_tick)中,获取到准备好的事件流 event_stream
    • 调用 salvo::sse::stream(res, event_stream)。这个函数会设置正确的响应头 (Content-Type: text/event-stream 等),并将事件流中的每个 SseEvent 格式化后写入响应体。
  6. 客户端: 客户端需要使用 JavaScript 的 EventSource API 来连接 SSE 端点并接收事件。本示例未包含客户端代码,但标准的 EventSource 用法即可。

此示例实现了一个简单的计数器服务,每秒向连接的客户端发送一个包含递增数字的 SSE 事件。

文件列表

  • Cargo.toml: 启用 salvosse 特性。需要 tokio-stream 用于将 tokio::time::interval 转换为流,以及 futures-util 来操作流 (StreamExt)。
  • src/main.rs:
    • 定义 sse_counter 函数,将 u64 转换为 Result<SseEvent, Infallible>
    • 定义 handle_tick 处理器:
      • 创建 IntervalStream 作为事件源。
      • 使用 .map() 将定时器信号转换为 SseEvent 结果流。
      • 调用 sse::stream() 将事件流发送给客户端。
    • 设置路由,将 /ticks 指向 handle_tick
    • 启动服务器。

oapi-hello

这是一个最简单的 Salvo OpenAPI (oapi) 示例,演示了如何为一个基本的 "Hello World" 端点自动生成 OpenAPI 文档。

核心步骤:

  1. 启用 OAPI: 在 Cargo.toml 中启用 salvooapi 特性。
  2. 定义端点: 使用 #[endpoint] 宏标记 hello 函数。
    • 函数接受一个可选的查询参数 name (类型 QueryParam<String, false>)。QueryParamsalvo::oapi::extract 提供的提取器,它会自动从请求中提取参数,并将其信息添加到 OpenAPI 文档。false 表示该参数是可选的。
    • 函数返回一个 String#[endpoint] 宏会确保返回值被正确处理并添加到 OpenAPI 文档的响应部分。
  3. 生成文档:
    • 创建 Router 并注册 hello 端点到 /hello 路径。
    • 创建 OpenApi 实例,提供 API 名称和版本。
    • 调用 .merge_router(&router) 合并路由和端点元数据。
  4. 提供文档 UI:
    • OpenApi 对象转换为提供 OpenAPI JSON 的路由 (doc.into_router("/api-doc/openapi.json"))。
    • 添加 SwaggerUi 路由 (SwaggerUi::new("/api-doc/openapi.json").into_router("/swagger-ui")) 以提供交互式 UI。
  5. 启动服务器: 组合路由并启动。

访问 /swagger-ui 可以看到自动生成的文档,其中包含 /hello 端点,描述了其可选的 name 查询参数和返回的字符串响应。

文件列表

  • Cargo.toml: 启用 salvooapi 特性。
  • src/main.rs:
    • 使用 #[endpoint] 定义 hello 处理器,使用 QueryParam 提取参数。
    • 创建路由。
    • 创建 OpenApi 实例并合并路由。
    • 添加 OpenAPI JSON 和 Swagger UI 路由。
    • 启动服务器。

static-embed-file

此示例演示了如何使用 rust-embed crate 将静态文件(如 HTML, CSS, JS, 图像)直接嵌入到编译后的二进制文件中,并通过 Salvo 提供这些嵌入的文件服务。这对于创建单文件可执行分发很有用。

主要步骤:

  1. 添加依赖: 在 Cargo.toml 中添加 rust-embed crate 和 salvo (需要 serve-static 特性,虽然这里没直接用 StaticDir,但 EmbeddedFileExt 可能依赖相关功能)。
  2. 定义嵌入资源:
    • 创建一个结构体(如 Assets)。
    • 使用 #[derive(RustEmbed)] 宏标记该结构体。
    • 使用 #[folder = "static"] 属性指定包含要嵌入文件的目录(相对于 Cargo.toml 的路径)。rust-embed 会在编译时扫描此目录并将文件内容包含在内。
  3. 创建处理器:
    • 定义一个处理器(如 serve_file)。
    • 从请求路径中提取所需的文件名。示例中使用 "{**rest}" 捕获路径的剩余部分作为文件名。
    • 使用 Assets::get(&path) 尝试从嵌入的资源中获取文件。Assets::get() 返回一个 Option<rust_embed::EmbeddedFile>
    • 如果文件存在 (Some(file)):
      • 调用 file.render(req, res)EmbeddedFileExt trait(由 salvo::serve_static::EmbeddedFileExt 提供,需要 use 进来)为 EmbeddedFile 添加了 render 方法。这个方法会自动设置合适的 Content-Type 响应头(基于文件扩展名或 Mime Guess)并将文件内容写入响应体。
    • 如果文件不存在 (None):
      • 设置响应状态码为 StatusCode::NOT_FOUND
  4. 设置路由: 将捕获所有路径的路由 (Router::with_path("{**rest}")) 指向 serve_file 处理器。
  5. 启动服务器: 编译并运行。现在,请求服务器上对应于 static/ 目录下文件的路径(如 /test1.txt, /test2.txt)将返回嵌入的文件内容。

文件列表

  • Cargo.toml: 添加 rust-embedsalvo (带有 serve-static 特性) 依赖。
  • src/main.rs:
    • 定义 Assets 结构体,使用 #[derive(RustEmbed)]#[folder = "static"]
    • 导入 salvo::serve_static::EmbeddedFileExt
    • 定义 serve_file 处理器,使用 Assets::get() 获取嵌入文件,并使用 file.render() 发送响应。
    • 设置路由捕获所有路径并指向 serve_file
    • 启动服务器。
  • static/: 包含要嵌入到二进制文件中的静态文件。