From 0d5ba839a4e5f1169827be2f0e51caf4fc13b6b0 Mon Sep 17 00:00:00 2001 From: Hubert Shelley <46239302+hubertshelley@users.noreply.github.com> Date: Fri, 10 Jul 2026 20:46:09 +0800 Subject: [PATCH 1/3] =?UTF-8?q?docs(roadmap):=20=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E7=BA=AF=20Web=20=E6=A1=86=E6=9E=B6=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/skills/silent-middleware.md | 2 +- .claude/skills/silent-openapi.md | 8 +- .claude/skills/silent-project-scaffold.md | 6 +- .claude/skills/silent-route-handler.md | 26 +- .claude/skills/silent-worker.md | 29 +- PLAN.md | 203 +++++----- TODO.md | 91 ++--- docs/cloudflare-worker.md | 62 ++- docs/extractors-guide.md | 155 ++----- docs/extractors.md | 7 +- docs/requirements.md | 68 +++- docs/ws-runtime-agnostic.md | 2 +- readme.md | 182 +++++---- silent-openapi/Cargo.toml | 2 +- silent-openapi/IMPLEMENTATION_SUMMARY.md | 4 +- silent-openapi/README.md | 382 +++++------------- silent-openapi/src/lib.rs | 2 +- silent-openapi/src/ui_html.rs | 2 +- silent/Cargo.toml | 1 + silent/src/configs/mod.rs | 5 +- silent/src/extractor/types.rs | 2 +- .../middlewares/request_time_logger.rs | 8 +- silent/src/server/route_connection.rs | 2 +- 23 files changed, 506 insertions(+), 745 deletions(-) diff --git a/.claude/skills/silent-middleware.md b/.claude/skills/silent-middleware.md index 3a9b25c7..b061a363 100644 --- a/.claude/skills/silent-middleware.md +++ b/.claude/skills/silent-middleware.md @@ -144,7 +144,7 @@ pub struct AddHeaders; impl MiddleWareHandler for AddHeaders { async fn handle(&self, req: Request, next: &Next) -> Result { let mut res = next.call(req).await?; - res.headers_mut().insert("X-Powered-By", "Silent".parse().unwrap()); + res.headers_mut().insert("x-powered-by", "Silent".parse().unwrap()); Ok(res) } } diff --git a/.claude/skills/silent-openapi.md b/.claude/skills/silent-openapi.md index eb104dd8..40acae04 100644 --- a/.claude/skills/silent-openapi.md +++ b/.claude/skills/silent-openapi.md @@ -6,9 +6,9 @@ ```toml [dependencies] -silent = "2.15" -silent-openapi = "2.15" -silent-openapi-macros = "2.15" +silent = "2.16" +silent-openapi = "2.16" +silent-openapi-macros = "2.16" utoipa = { version = "5", features = ["preserve_order"] } serde = { version = "1", features = ["derive"] } serde_json = "1" @@ -158,7 +158,7 @@ async fn handler(req: Request, Json(body): Json) -> Result ## Swagger UI 配置选项 ```rust -use silent_openapi::handler::{SwaggerUiHandler, SwaggerUiOptions}; +use silent_openapi::{SwaggerUiHandler, SwaggerUiOptions}; let options = SwaggerUiOptions { try_it_out_enabled: true, // 启用 "Try it out" 交互式调试 diff --git a/.claude/skills/silent-project-scaffold.md b/.claude/skills/silent-project-scaffold.md index 5a7dce6b..9c466c4b 100644 --- a/.claude/skills/silent-project-scaffold.md +++ b/.claude/skills/silent-project-scaffold.md @@ -31,11 +31,9 @@ tracing = "0.1" 按需添加 features: - 需要 WebSocket:`silent = { version = "2", features = ["upgrade"] }` - 需要静态文件:`silent = { version = "2", features = ["static"] }` -- 需要所有功能:`silent = { version = "2", features = ["full"] }` -- 需要 gRPC:`silent = { version = "2", features = ["grpc"] }` - 需要 SSE:`silent = { version = "2", features = ["sse"] }` -- 需要模板:`silent = { version = "2", features = ["template"] }` -- 需要会话:`silent = { version = "2", features = ["session"] }` + +`full`、`grpc`、`template`、`session`、`scheduler`、`security` 和 `admin` 在 2.x 中仍可按需启用。`session`、`scheduler` 和 `admin` 属于兼容保留入口;`security`、`template`、`grpc` 的长期归属由 3.0 RFC 决定。新项目只启用实际需要的 feature,不把 `full` 当作默认项目骨架。 ## main.rs 模板 diff --git a/.claude/skills/silent-route-handler.md b/.claude/skills/silent-route-handler.md index ff83c608..08877004 100644 --- a/.claude/skills/silent-route-handler.md +++ b/.claude/skills/silent-route-handler.md @@ -38,7 +38,7 @@ let route = Route::new_root() ```rust Route::new("") // 字符串参数 Route::new("") // 整数参数(i64) -Route::new("") // 整数参数(同上) +Route::new("") // 整数参数(i32) Route::new("") // 通配符(匹配剩余所有路径段) ``` @@ -70,7 +70,7 @@ async fn json_handler(_req: Request) -> Result { ### 3. 使用提取器 ```rust -use serde::Deserialize; +use serde::{Deserialize, Serialize}; // Path 提取器 — 从路径参数中提取 async fn get_user(Path(id): Path) -> Result { @@ -101,7 +101,7 @@ async fn list(Query(p): Query) -> Result { // 请求: GET /list?page=1&size=10 // Json 提取器 — 从请求体 JSON 中提取 -#[derive(Deserialize)] +#[derive(Deserialize, Serialize)] struct CreateUser { name: String, email: String, @@ -173,7 +173,7 @@ let mut res = Response::json(&data); res.set_status(StatusCode::CREATED); // 设置响应头 -res.headers_mut().insert("X-Custom", "value".parse().unwrap()); +res.headers_mut().insert("x-custom", "value".parse().unwrap()); // 字符串和 &str 自动转为 Response async fn handler(_req: Request) -> Result<&'static str> { @@ -196,24 +196,24 @@ let route = Route::new("") .get(handler); ``` -## Configs 配置注入 +## State 注入 ```rust -// 在路由上设置配置 -let mut configs = Configs::default(); -configs.insert(DatabasePool::new()); - -let mut route = Route::new("").get(handler); -route.set_configs(Some(configs)); +// 在路由上注入应用级共享状态 +let route = Route::new_root() + .with_state(DatabasePool::new()) + .get(handler); -// 在处理器中获取配置 +// 在处理器中获取状态 async fn handler(req: Request) -> Result { - let pool = req.get_config::()?; + let pool = req.get_state::()?; // 使用 pool... Ok(Response::empty()) } ``` +`Configs`、`set_configs` 和 `get_config` 是 2.x 兼容入口,新代码不要使用;它们最早于 3.0 移除。 + ## 静态文件服务 ```rust diff --git a/.claude/skills/silent-worker.md b/.claude/skills/silent-worker.md index 5a7732c6..b10580e0 100644 --- a/.claude/skills/silent-worker.md +++ b/.claude/skills/silent-worker.md @@ -25,8 +25,8 @@ edition = "2024" crate-type = ["cdylib"] [dependencies] -silent = { version = "2.15", features = ["worker"] } -worker = "0.7" +silent = { version = "2.16", default-features = false, features = ["worker"] } +worker = "0.8" serde = { version = "1", features = ["derive"] } serde_json = "1" console_error_panic_hook = "0.1" @@ -76,21 +76,14 @@ use worker::{Context, Env, Request, Response, Result}; #[cfg(target_arch = "wasm32")] use crate::route::get_route; -#[cfg(target_arch = "wasm32")] -use silent::Configs; - #[cfg(target_arch = "wasm32")] #[worker::event(fetch)] pub async fn main(req: Request, env: Env, ctx: Context) -> Result { console_error_panic_hook::set_once(); - // 将 Env 和 Context 注入到 Configs - // 处理器通过 req.get_config::() 和 req.get_config::() 获取 - let mut cfg = Configs::default(); - cfg.insert(env); - cfg.insert(ctx); - - let wr = get_route().with_configs(cfg); + // 将 Env 和 Context 注入到 State + // 处理器通过 req.get_state::() 和 req.get_state::() 获取 + let wr = get_route().with_state(env).with_state(ctx); Ok(wr.call(req).await) } ``` @@ -122,7 +115,7 @@ async fn hello(_req: Request) -> silent::Result<&'static str> { ```rust async fn kv_get(req: Request) -> silent::Result { - let env = req.get_config::()?; + let env = req.get_state::()?; let key: String = req.get_path_params("key")?; let kv = env.kv("MY_KV").map_err(worker_err)?; @@ -137,7 +130,7 @@ async fn kv_get(req: Request) -> silent::Result { } async fn kv_put(mut req: Request) -> silent::Result { - let env = req.get_config::()?.clone(); + let env = req.get_state::()?.clone(); let key: String = req.get_path_params("key")?; let value = read_body_text(&mut req).await?; let kv = env.kv("MY_KV").map_err(worker_err)?; @@ -150,7 +143,7 @@ async fn kv_put(mut req: Request) -> silent::Result { ```rust async fn d1_query(req: Request) -> silent::Result { - let env = req.get_config::()?; + let env = req.get_state::()?; let d1 = env.d1("MY_DB").map_err(worker_err)?; let stmt = d1.prepare("SELECT id, name FROM users LIMIT 100"); let result = stmt.all().await.map_err(worker_err)?; @@ -163,7 +156,7 @@ async fn d1_query(req: Request) -> silent::Result { ```rust async fn r2_get(req: Request) -> silent::Result { - let env = req.get_config::()?; + let env = req.get_state::()?; let key: String = req.get_path_params("key")?; let bucket = env.bucket("MY_BUCKET").map_err(worker_err)?; @@ -236,8 +229,8 @@ wrangler deploy ## 关键注意事项 -- Worker 环境下 `Configs` 是只读的,跨请求不保持状态 +- Worker 环境下 `State` 只适合保存只读依赖,跨请求不保持可变状态 - 需要持久化请使用 KV / D1 / R2 / Durable Objects - JSON 请求体可直接使用 `req.json_parse::().await` - 获取路径参数使用 `req.get_path_params("key")?` -- 获取 Env 时如需可变操作需要 `.clone()`:`req.get_config::()?.clone()` +- 获取 Env 时如需可变操作需要 `.clone()`:`req.get_state::()?.clone()` diff --git a/PLAN.md b/PLAN.md index 918da845..92cbd8ff 100644 --- a/PLAN.md +++ b/PLAN.md @@ -1,105 +1,102 @@ # 项目规划 -## 愿景与目标 -- 提升 server 模块生产级能力:统一配置入口、连接保护、公平监听、可观测性、QUIC 落地。 -- 提供可运维化的默认配置与可观察行为,降低生产部署风险。 - -## 版本里程碑 - -### v2.13(已完成 ✅) - -- **M1:统一配置入口 + 连接保护** ✅ - - `ServerConfig` / `ConnectionLimits` 统一配置结构(`server/config.rs`) - - per-connection 超时、请求体大小限制(HTTP/1.1、HTTP/2、HTTP/3 统一) - - 令牌桶限流器(`RateLimiterConfig`) - - HTTP/3 分块参数可配置(`h3_chunk_size`、`h3_yield_bytes`) - -- **M2:监听公平性与错误退避** ✅ - - 多监听器 Round-robin 公平调度(`listener.rs` Listeners) - - 每个监听器独立 `BackoffState`,指数退避 + 限幅(50ms → 2s) - - `JoinSet` + `select!` 并发 accept,单监听器不阻塞全局 - - Accept 错误可观测(`record_accept_err`) - -- **M3:可观测性与 QUIC 生产化** ✅ - - metrics 埋点覆盖 accept/限流/handler/关停/QUIC 全链路(`metrics.rs`) - - tracing span 覆盖 HTTP/1.1/2/3 请求路径,携带 peer/method/uri - - Alt-Svc 中间件自动端口匹配(`quic/middleware.rs`) - - ALPN 配置支持 h3/h3-29(`QuicTransportConfig`) - - quinn transport 参数全部可配置(idle_timeout、max_streams、窗口等) - - WebTransport 帧/体积/速率限制(`ConnectionLimits`) - - 证书可重载(`ReloadableTlsListener`) - -- **测试覆盖率** ✅ - - 行覆盖率 89.01%,1587 个测试全部通过 - - 三阶段优化全部完成 - -### v2.14(已完成 ✅) - -- **常用中间件补充** ✅ - - RateLimiter 中间件(路由/API 级别限流) - - Compression 中间件(动态响应 gzip/brotli 压缩) - - RequestId 中间件(scru128 生成请求追踪 ID) - -- **OpenAPI 宏系统增强** ✅ - - 复杂请求/响应类型文档化 - - 枚举变体文档生成 - - 与提取器(Path、Query、Json 等)自动集成 - -- **依赖版本更新** ✅ - - scru128 非可选化、tokio 1.50、chrono 0.4.44 - -- **低覆盖率模块测试补全** ✅ - - +22 个测试,总计 1717 - -### v2.15(已完成 ✅) - -- **TestClient 集成测试工具** ✅ (#183) - - TestClient / TestRequest 请求构建器(支持全 HTTP 方法) - - TestResponse 响应包装器(status/headers/bytes/text/json) - - 链式断言方法(assert_status/assert_header/assert_body_contains) - - JSON/Form/Text 请求体支持 - -- **路由性能优化** ✅ (#184) - - freeze 模式预构建 Arc,消除请求级深拷贝 - - SpecialSeg 从 String 优化为 Box - - 大规模路由表 180x 性能提升 - -- **Cloudflare Worker 生态增强** ✅ (#185) - - WorkRoute 新增 with_configs() 方法 - - Context 与 Env 统一通过 Configs 注入 - - 完整的 KV/D1/R2 CRUD 示例 - - 错误状态码正确传递 - -- **Logger 中间件** ✅ (#186) - - 结构化 tracing 字段替代位置参数字符串 - - Instant 单调时钟计时 - - 安全获取客户端 IP - - 区分 4xx(WARN)/5xx(ERROR) 日志级别 - - RequestTimeLogger 标记 deprecated,将在 v2.17.0 移除 - -### v2.16 — 框架基础设施增强(已完成 ✅) - -- **State 提取器(替代 Configs)** ✅ -- **Tower 兼容层** ✅ -- **OpenAPI 完善** ✅ -- **错误处理增强** ✅ - -## 下一阶段规划 - -### v2.17 — 极限性能优化 - -目标:优化框架热路径性能,消除不必要的内存分配和动态分发,冲击 Web 框架性能榜单。 - -- **P0:热路径关键瓶颈消除** - - Handler HashMap clone 消除:`self.clone().get()` → 直接引用 - - RouteTree 连接级共享:启动时一次性构建 `Arc`,所有连接共享 - - 移除 `async_trait`:利用 Rust 1.85+ RPITIT 原生 async fn in trait - - HyperService Box::pin 消除:使用具名 Future 替代 Box - -- **P1:中间件与数据结构优化** - - 中间件链预构建:freeze 阶段预构建,消除每请求重建开销 - - Request 参数容器优化:HashMap → SmallVec 栈分配 - -- **P2:编译与运行时调优** - - Release profile 极致优化(LTO、codegen-units=1、panic=abort) - - tracing 编译时级别控制 +## 愿景与边界 + +Silent 是纯 Web 框架,目标是在保持接口清晰和高性能的前提下,提供可靠的 Web 协议基础。 + +核心职责: + +- HTTP/1.1、HTTP/2、HTTP/3 与服务端传输; +- 路由、中间件、请求、响应和通用提取器; +- WebSocket、SSE 与流式响应的协议基础; +- TestClient、静态资源、Cookie、TLS、QUIC 和稳定扩展入口。 + +以下能力不进入核心:认证与权限、生产会话存储、监控导出器、可靠任务调度、数据库集成、管理界面、MQTT 实现和项目脚手架。它们由独立仓库自行维护,Silent 只提供必要的公共接入接口。 + +## 版本与兼容原则 + +- 2.x 不删除现有公开入口、Cargo feature 或公开重导出类型。 +- 已迁出核心的旧能力在 2.x 只做必要修复和迁移提示,不继续扩大功能。 +- 独立替代项目和迁移指南完成后,才能安排弃用;破坏性移除集中到 3.0。 +- 独立项目只能依赖 Silent 公共接口,不得访问私有模块。 +- 新增标识统一使用 scru128;时间字段默认使用本地时间。 + +## 已完成里程碑 + +### v2.13 + +- 统一 `ServerConfig`、连接保护、监听公平性和错误退避。 +- 完善 HTTP/3、WebTransport、TLS 重载和服务端观测基础。 + +### v2.14 + +- 增加限流、压缩、RequestId 等常用中间件。 +- 增强 OpenAPI 宏系统并补齐低覆盖率模块测试。 + +### v2.15 + +- 增加 TestClient、路由冻结与性能优化、Cloudflare Worker 接入和 Logger 中间件。 +- `RequestTimeLogger` 已弃用,但在整个 2.x 保留,最早于 3.0 移除。 + +### v2.16 / v2.16.1 + +- 增加 `State` 提取器、Tower 兼容层、OpenAPI 完善和错误响应扩展。 +- 完成 PR #198 的热路径优化:路由树共享、中间件快速路径、未匹配零分配、发布配置和 tracing 编译控制。 +- `async_trait`、Hyper Future 装箱和公开参数容器调整因收益或兼容约束暂缓,不再作为当前 TODO。 + +## 当前与后续里程碑 + +### v2.16.2 — 路线校准与维护 + +关联 Issue:#222、#223。 + +- 统一 README、PLAN、TODO、需求整理和版本说明中的项目边界。 +- 修正过期路径、OpenAPI 版本组合和 2.x 移除承诺。 +- 说明生态项目由独立仓库维护,核心仓库不再追踪其交付进度。 +- 只合入已有维护更新和必要安全修复,不加入新路线功能。 + +完成门槛:文档描述一致,格式、构建、测试和依赖检查通过。 + +### v2.17 — 安全与扩展基础 + +按单一职责拆分为独立 TODO、分支和 PR: + +1. `fix/trusted-proxy-source-address`:默认不信任来源地址头,区分底层地址和可信客户端地址; +2. `feature/connection-service-context`:增加服务准备、连接上下文、取消信号和每个 Server 的独立配置; +3. `fix/websocket-session-supervisor`:统一监督收发和关闭,清理不可追踪的后台任务; +4. `fix/websocket-bounded-send-queue`:发送队列默认 64 条、满时等待,并提供拒绝、丢弃和关闭策略; +5. `feature/server-lifecycle-hooks`:提供监听成功、开始服务、开始关闭和关闭完成入口; +6. `feature/route-compile-diagnostics`:启动前检查重复路由、非法参数和路由遮蔽; +7. `feature/route-metadata-view`:提供规范化路由和只读路由目录; +8. `feature/extractor-error-mapping`:统一提取失败分类和自定义响应入口; +9. `feature/test-response-streaming`:增加不预读正文的流式测试能力; +10. `feature/realtime-cancellation`:统一 WebSocket、SSE 和普通流式响应的断开信号。 + +以上顺序固定,分别归入 #209、#210、#212、#213、#214 的对应核心能力,不建立覆盖整个 Issue 的大分支。 + +完成门槛:旧公开用法保持可用,外部组件无需访问私有模块,慢连接和伪造来源地址均有边界验证。 + +### v2.18 — Web 协议完整性 + +- #211:内容协商、请求体解压、条件请求、单范围请求、预压缩文件、缓存控制和 SPA 回退; +- #212:HEAD/OPTIONS/405、命名路由、URL 生成、SCRU128 路径参数和 Host 路由; +- #214:Bytes、Text、Json、Form 的共享缓存、独占流式请求体和公开提取器描述; +- #209:心跳、超时、SSE 断开、服务关闭和基础观测; +- #213:状态化测试会话、Cookie、重定向、multipart、SSE、WebSocket 和真实网络模式评估。 + +完成门槛:HTTP/1.1、HTTP/2、HTTP/3 行为一致,路由性能回退不超过正常测试噪声外的 3%。 + +### 3.0 — 兼容迁移 + +关联总追踪 Issue:#223。 + +- 在公开 RFC 中确定 admin、security、template 和 grpc 等现有可选能力的长期归属,不预先承诺迁出或移除。 +- session、scheduler 等迁出候选仅在替代能力、迁移指南、兼容验证和最后一个 2.x 弃用通知全部完成后移除旧入口。 + +## 开发规则 + +- 开发前核对本文件和 `docs/requirements.md`,并把当前单一任务写入 `TODO.md`。 +- 每个 TODO 从最新 `main` 创建独立分支,完成后通过 PR 合并。 +- 核心 PR 必须通过格式检查、全工作区检查、静态检查、全功能测试和依赖审计。 +- 不向已有公开字段的配置结构直接添加字段,不向可被外部穷举的公开枚举直接增加变体。 +- #223 只负责总路线;生态条目保留为长期方向,不作为 Silent 核心版本发布门槛。 diff --git a/TODO.md b/TODO.md index 8cb6807f..69c69b36 100644 --- a/TODO.md +++ b/TODO.md @@ -1,65 +1,26 @@ -# TODO(v2.17 极限性能优化) - -> 目标版本: v2.17 -> 状态: 开发中 - -## 上一阶段成果(v2.16 已完成 ✅) - -- State 提取器(替代 Configs) -- Tower 兼容层(hook_layer) -- OpenAPI 完善(Swagger UI 嵌入、宏增强、ReDoc) -- 错误处理增强(IntoResponse trait) - -## 待开发任务 - -### P0:热路径关键瓶颈消除 - -- [x] 1. Handler HashMap clone 消除 ✅ - - `handler_trait.rs` 中 `self.clone().get(&method)` → `self.get(&method)` 直接引用 - - 消除每请求的 HashMap 深拷贝开销 - - **效果:简单路由约 2x 提升** - -- [x] 2. RouteTree 连接级共享 ✅ - - `route_connection.rs` 中每连接调用 `convert_to_route_tree()` → 启动时一次性构建 `Arc` - - 所有连接共享同一份冻结的路由树(HTTP 和 QUIC 均适用) - -- [ ] 3. 移除 async_trait,使用原生 RPITIT - - 因 `dyn Handler` trait object 需求,boxing 是必需的,收益有限 - - 暂缓,待后续评估 - -- [ ] 4. HyperService Box::pin 消除 - - hyper Service trait 要求 `type Future = Pin>`,无法直接消除 - - 暂缓 - -### P1:中间件与数据结构优化 - -- [x] 5. 中间件链优化 ✅ - - 无中间件时快速路径直接调用 call_children,跳过 Next 链构建 - - Next::call 中 Arc clone 改为直接引用 - - **效果:带中间件路由约 2.2x 提升** - -- [x] 6. not_found_error 零分配 ✅ - - 使用 `SilentError::NotFound` 替代 `BusinessError` + String 分配 - - **效果:所有未匹配路径零字符串分配** - -- [ ] 7. Request 参数容器优化 - - HashMap → SmallVec 涉及公开 API 变化,暂缓 - -### P2:编译与运行时调优 - -- [x] 8. Release profile 优化 ✅ - - `opt-level = 3`, `lto = "fat"`, `codegen-units = 1`, `strip = "symbols"` - -- [x] 9. tracing 编译时级别控制 ✅ - - 添加 `no-tracing` feature(`tracing/max_level_off`),benchmark 时关闭 tracing - -## Benchmark 结果 - -| 测试项 | main 基线 | 优化后 | 提升 | -|--------|-----------|--------|------| -| simple route match | 107.57 ns | 54.35 ns | ~2x | -| route with middleware | 107.50 ns | 49.29 ns | ~2.2x | -| nested route match | 133.00 ns | 91.63 ns | 31% | -| 1000 sequential requests | 184.98 µs | 132.97 µs | 28% | -| deep nested 10 levels | 206.12 ns | 166.30 ns | 19% | -| deep nested with params | 299.80 ns | 253.78 ns | 15% | +# TODO(v2.16.2 路线校准) + +> 当前 Issue:#222 +> +> 当前分支:`feature/roadmap-sync` +> +> 状态:验证完成,待提交 PR + +## 当前任务 + +- [x] 更新 #223,说明生态条目保留未完成但不再由核心仓库追踪。 +- [x] 为 #208、#215–#221 补充迁出说明,并以 `not planned` 关闭。 +- [x] 重建 `PLAN.md`,归档已完成的性能专项并加入核心 Web 路线。 +- [x] 重写 `docs/requirements.md`,移除失效路径和核心内 MQTT 需求。 +- [x] 更新根 README,区分核心能力、2.x 兼容能力和独立生态项目。 +- [x] 修正 silent-openapi 的版本组合、示例、许可证和 OpenAPI 版本说明。 +- [x] 将 `Configs`、`RequestTimeLogger` 的移除承诺统一为最早 3.0。 +- [x] 完成格式、构建、测试和依赖检查。 +- [ ] 创建 PR 并关联 #222。 + +## 完成标准 + +- #209–#214、#222、#223 保持开放;#208、#215–#221 均带说明并以 `not planned` 关闭。 +- 公开文档不再把认证、会话存储、监控、调度、数据库、管理界面、MQTT 或 CLI 描述为核心交付。 +- 2.x 兼容说明与源码弃用提示一致。 +- 本次不实现 v2.17/v2.18 功能,不创建生态仓库或占位链接。 diff --git a/docs/cloudflare-worker.md b/docs/cloudflare-worker.md index 610fd2ae..5c1ff95d 100644 --- a/docs/cloudflare-worker.md +++ b/docs/cloudflare-worker.md @@ -34,60 +34,58 @@ Cloudflare Worker 集成与使用指南(Silent 路由) - 响应体通过 `http-body-util::BodyExt::collect` 聚合,兼容 Once/Chunks/Stream/Incoming/Boxed。 - 错误响应保留原始状态码(如 404、400),不再统一返回 500。 -WorkRoute 增强功能 +WorkRoute 状态注入 -`with_configs()` 方法 -- 用于将 Cloudflare Worker 的绑定(Env 或其子资源)注入到路由中 -- 处理器通过 `req.get_config::()` 获取注入的配置 +`with_state()` 方法 +- 用于将 Cloudflare Worker 的绑定(Env、Context 或其他只读句柄)注入到路由中 +- 处理器通过 `req.get_state::()` 获取注入的状态 - 推荐直接注入 `Env`,在处理器中按需获取 KV/D1/R2 等绑定 ```rust use silent::prelude::*; -use worker::Env; -let mut cfg = Configs::default(); -cfg.insert(env); // 注入整个 Env -let wr = WorkRoute::new(route).with_configs(cfg); +let wr = WorkRoute::new(route) + .with_state(env) + .with_state(ctx); ``` 处理器中获取绑定: ```rust async fn my_handler(req: Request) -> silent::Result { - let env = req.get_config::()?; - let kv = env.kv(“MY_KV”).map_err(worker_err)?; - let d1 = env.d1(“MY_DB”).map_err(worker_err)?; - let bucket = env.bucket(“MY_BUCKET”).map_err(worker_err)?; + let env = req.get_state::()?; + let kv = env.kv("MY_KV").map_err(worker_err)?; + let d1 = env.d1("MY_DB").map_err(worker_err)?; + let bucket = env.bucket("MY_BUCKET").map_err(worker_err)?; // ... } ``` Context 注入 -- 将 `worker::Context` 与 `Env` 一样注入到 `Configs` 中 -- 处理器通过 `req.get_config::()` 获取 +- 将 `worker::Context` 与 `Env` 一样注入到 `State` 中 +- 处理器通过 `req.get_state::()` 获取 - 适用于需要调度后台任务(`ctx.wait_until(fut)`)的场景 ```rust -let mut cfg = Configs::default(); -cfg.insert(env); -cfg.insert(ctx); // Context 也注入 Configs -let wr = WorkRoute::new(route).with_configs(cfg); +let wr = WorkRoute::new(route) + .with_state(env) + .with_state(ctx); // 处理器中使用 Context async fn my_handler(req: Request) -> silent::Result { - let ctx = req.get_config::()?; + let ctx = req.get_state::()?; ctx.wait_until(async { /* 后台任务 */ }); Ok(Response::empty()) } ``` -只读 Configs(重要) -- 由于 Wasm/Workers 的执行模型,实例的跨请求复用不可保证,且可能冷启动。处理器内对 `Configs` 的修改不会在后续请求中保持。 -- 将 `Configs` 视为只读配置的载体,仅在初始化阶段注入不可变参数(常量、开关、外部服务句柄等)。 +只读 State(重要) +- 由于 Wasm/Workers 的执行模型,实例的跨请求复用不可保证,且可能冷启动。处理器内对 `State` 的修改不会在后续请求中保持。 +- 将 `State` 视为只读依赖的载体,仅在初始化阶段注入不可变参数(常量、开关、外部服务句柄等)。 - 如需跨请求可变状态,请使用 Cloudflare 的持久化能力:KV、Durable Objects、D1、R2、Queues 等。 Env 与 Context 的使用 -- `Env`:用于获取绑定(KV/DO/D1/R2/Queues 等)。建议将只读句柄注入到 `Configs`,供路由/处理器读取。 -- `Context`:与 `Env` 一样通过 `Configs` 注入,处理器通过 `req.get_config::()` 获取。用于调度后台任务(`ctx.wait_until(fut)`),任务可在响应返回后继续执行。 +- `Env`:用于获取绑定(KV/DO/D1/R2/Queues 等)。建议通过 `with_state` 注入,供路由/处理器读取。 +- `Context`:与 `Env` 一样通过 `with_state` 注入,处理器通过 `req.get_state::()` 获取。用于调度后台任务(`ctx.wait_until(fut)`),任务可在响应返回后继续执行。 示例:注入 Env + Context 并访问 KV 绑定 ```rust @@ -98,12 +96,10 @@ use silent::prelude::*; pub async fn main(req: Request, env: Env, ctx: Context) -> Result { console_error_panic_hook::set_once(); - // 将 Env 和 Context 注入到 Configs - let mut cfg = Configs::default(); - cfg.insert(env); - cfg.insert(ctx); - - let wr = WorkRoute::new(get_route()).with_configs(cfg); + // 将 Env 和 Context 注入到 State + let wr = WorkRoute::new(get_route()) + .with_state(env) + .with_state(ctx); Ok(wr.call(req).await) } @@ -122,7 +118,7 @@ async fn hello(_req: silent::Request) -> silent::Result<&'static str> { /// KV 读取示例 async fn kv_get(req: silent::Request) -> silent::Result { - let env = req.get_config::()?; + let env = req.get_state::()?; let key: String = req.get_path_params("key")?; let kv = env.kv("MY_KV").map_err(|e| { silent::SilentError::business_error( @@ -142,7 +138,7 @@ async fn kv_get(req: silent::Request) -> silent::Result { /// KV 写入示例 async fn kv_put(mut req: silent::Request) -> silent::Result { - let env = req.get_config::()?.clone(); + let env = req.get_state::()?.clone(); let key: String = req.get_path_params("key")?; let value = read_body_text(&mut req).await?; let kv = env.kv("MY_KV").map_err(|e| { @@ -240,7 +236,7 @@ wrangler 将使用 `worker-build` 将 Rust 工程编译为 Wasm,并生成可 环境变量与机密 - 普通变量:在 `wrangler.toml` 的 `[vars]` 中定义 - 机密:`wrangler secret put MY_SECRET` -- 处理器中可通过 `Env` 获取,或在 `with_configs` 时注入只读配置(推荐仅注入只读句柄)。 +- 处理器中可通过 `Env` 获取,或使用 `with_state` 注入只读句柄。 常见问题 - Wasm/Workers 下请求生命周期短且实例不可预测:不要依赖进程内“全局可变状态”。 diff --git a/docs/extractors-guide.md b/docs/extractors-guide.md index ce3f99ab..2eed6dd2 100644 --- a/docs/extractors-guide.md +++ b/docs/extractors-guide.md @@ -139,7 +139,7 @@ struct LoginForm { password: String, } -async fn handler(Json(form): Json) -> Result { +async fn handler(Form(form): Form) -> Result { Ok(format!("用户登录: {}", form.username)) } ``` @@ -193,9 +193,9 @@ impl MiddleWareHandler for InjectUserId { } ``` -### 8. Configs - 配置萃取器 +### 8. State - 应用状态萃取器 -从请求配置中提取数据,通常用于全局配置。 +从应用级共享状态中提取数据。 ```rust #[derive(Clone)] @@ -204,141 +204,52 @@ struct AppConfig { version: String, } -async fn handler(Configs(config): Configs) -> Result { +async fn handler(State(config): State) -> Result { Ok(format!("应用: {} v{}", config.name, config.version)) } ``` -在路由中注入配置: +在路由中注入状态: ```rust let route = Route::new("api") - .with_config(AppConfig { + .with_state(AppConfig { name: "MyApp".to_string(), version: "1.0.0".to_string(), }) .append(Route::new("info").get(handler)); ``` -## 单个字段萃取器 +`Configs` 是弃用的兼容入口,在 2.x 中继续保留,最早于 3.0 移除;新代码应使用 `State`。 -单个字段萃取器允许您直接提取单个字段,而无需创建结构体。这对于只需要一两个参数的情况非常有用。 +## 直接从 Request 读取 -### QueryParam - 按名称提取查询参数 +不使用处理器参数萃取器时,可以通过当前公开的 `Request` 方法读取单个值: ```rust -async fn handler(mut req: Request) -> Result { - let name = query_param::(&mut req, "name").await.unwrap_or_default(); - let age = query_param::(&mut req, "age").await.unwrap_or(0); - - Ok(format!("姓名: {}, 年龄: {}", name, age)) -} -``` - -### PathParam - 按名称提取路径参数 - -```rust -async fn handler(mut req: Request) -> Result { - let id = path_param::(&mut req, "id").await.unwrap_or_default(); - Ok(format!("ID: {}", id)) -} -``` - -### HeaderParam - 按名称提取请求头 - -```rust -async fn handler(mut req: Request) -> Result { - let auth = header_param::(&mut req, "authorization") - .await - .unwrap_or_default(); - - Ok(format!("认证: {}", auth)) +#[derive(Deserialize)] +struct Search { + name: Option, + age: Option, } -``` - -### CookieParam - 按名称提取 Cookie -```rust async fn handler(mut req: Request) -> Result { - let session = cookie_param::(&mut req, "session") - .await - .unwrap_or_default(); + let id: i64 = req.get_path_params("id")?; + let search: Search = req.params_parse()?; + let authorization = req + .headers() + .get("authorization") + .and_then(|value| value.to_str().ok()) + .unwrap_or(""); - Ok(format!("会话: {}", session)) + Ok(format!( + "ID: {id}, 姓名: {}, 年龄: {}, 认证: {authorization}", + search.name.as_deref().unwrap_or(""), + search.age.unwrap_or_default(), + )) } ``` -### ConfigParam - 按类型提取配置 - -```rust -#[derive(Clone)] -struct DatabaseConfig { - url: String, -} - -async fn handler(mut req: Request) -> Result { - let config = config_param::(&mut req).await.unwrap(); - Ok(format!("数据库: {}", config.url)) -} -``` - -## 类型转换 - -所有萃取器都支持丰富的类型转换: - -### 基本类型 - -```rust -// 整数类型 -let id = query_param::(&mut req, "id").await.unwrap(); -let count = query_param::(&mut req, "count").await.unwrap(); - -// 浮点类型 -let price = query_param::(&mut req, "price").await.unwrap(); - -// 布尔类型 -let active = query_param::(&mut req, "active").await.unwrap(); - -// 字符串 -let name = query_param::(&mut req, "name").await.unwrap(); -``` - -### 枚举类型 - -```rust -#[derive(Deserialize)] -enum Role { - Admin, - User, - Guest, -} - -let role = query_param::(&mut req, "role").await.unwrap(); -``` - -### DateTime 类型 - -```rust -use chrono::{DateTime, Utc}; - -let created_at = query_param::>(&mut req, "created_at") - .await - .unwrap(); -``` - -### 自定义类型 - -只要实现了 `serde::Deserialize`,就可以用于萃取器: - -```rust -#[derive(Deserialize)] -struct Address { - street: String, - city: String, - zip_code: String, -} - -let address = query_param::
(&mut req, "address").await.unwrap(); -``` +JSON 和表单中的单个字段可使用 `json_field`、`form_field`;应用状态使用 `get_state`。Cookie 需要启用 `cookie` feature,并通过 Cookie 接口读取。 ## 多萃取器组合 @@ -447,8 +358,13 @@ async fn handler(AuthToken(token): AuthToken) -> Result { ```rust async fn handler(mut req: Request) -> Result { - match query_param::(&mut req, "required").await { - Ok(value) => Ok(format!("获取成功: {}", value)), + #[derive(Deserialize)] + struct RequiredQuery { + required: String, + } + + match req.params_parse::() { + Ok(query) => Ok(format!("获取成功: {}", query.required)), Err(_) => Ok("缺少必需参数".to_string()), } } @@ -458,7 +374,8 @@ async fn handler(mut req: Request) -> Result { ### 1. 选择合适的萃取器类型 -- **单个简单参数**:使用单个字段萃取器(QueryParam、PathParam 等) +- **单个路径参数**:使用 `Path` 或 `Request::get_path_params` +- **查询参数**:使用 `Query` 或 `Request::params_parse` - **相关参数组合**:使用结构体萃取器(Query、Path 等) - **复杂请求体**:使用 Json 或 Form - **可选参数**:使用 `Option` @@ -520,7 +437,7 @@ async fn handler(Path(id): Path) -> Result { |------|--------|------| | 类型安全 | ✅ 完整支持 | ✅ 完整支持 | | 零成本抽象 | ✅ | ✅ | -| 单个字段萃取 | ✅ | ✅ | +| 路径与查询萃取 | ✅ | ✅ | | 元组组合 | ✅ 支持最多4个 | ✅ 无限制 | | Option/Result 支持 | ✅ | ✅ | | 自定义萃取器 | ✅ | ✅ | diff --git a/docs/extractors.md b/docs/extractors.md index ab824159..0dc1870e 100644 --- a/docs/extractors.md +++ b/docs/extractors.md @@ -70,7 +70,9 @@ let route = Route::new("api") - **Extension**:从 `Request.extensions()` 提取扩展(需 `T: Clone` 且已注入) -- **Configs**:从 `Request.configs()` 提取全局配置(需 `T: Clone` 且已注入) +- **State**:从应用级共享状态提取 `T`(需 `T: Clone` 且已通过 `Route::with_state` 注入) + +- **Configs**:`State` 的弃用兼容入口;在 2.x 中保留,最早于 3.0 移除 - **Option**:当 `E: FromRequest` 失败时返回 `None` @@ -244,7 +246,8 @@ fn main() { - Extension:从 `Request::extensions()` 克隆提取 `T` - TypedHeader:从请求头以类型化头提取 `H: headers::Header` - Method / Uri / Version / RemoteAddr:轻量信息提取 -- Configs:从全局 `Configs` 提取并克隆 `T`(等价 axum 的 State;在 `prelude` 以别名 `Cfg` 导出) +- State:从应用级 State 提取并克隆 `T` +- Configs:State 的弃用兼容入口,2.x 保留,最早于 3.0 移除 路由注册(统一接口) - 直接使用 `get/post/...` 注册;必要时可以显式使用 `handler_from_extractor(...)` 进行适配。 diff --git a/docs/requirements.md b/docs/requirements.md index 46994ae4..befb59ea 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -1,15 +1,57 @@ # 需求整理 -## 背景 -当前 `silent/src/core/adapt.rs` 混合了承载协议抽象与 hyper HTTP 适配的代码,难以扩展其他协议实现。 - -## 功能范围 -- 为核心模块增加协议层实现,抽象出对协议适配所需的接口。 -- 将 hyper HTTP 适配相关的 adapt 实现迁移到独立目录中,理清职责边界。 -- 保持现有对外 API 行为不变。 -- 扩展 `Protocol` trait 的通用性,使其既可服务于 HTTP,也能在 MQTT 等自定义协议场景中复用。 -- 在 `silent-mqtt` 中实现基于 `Protocol` trait 的 MQTT 适配器,完成报文解析与响应编码。 - -## 验收标准 -- 重构后的代码能够通过 `cargo check`。 -- 新的目录结构清晰分离协议抽象与 hyper 实现,核心模块引用路径正确。 +## 项目定位 + +Silent 是纯 Web 框架。核心负责 Web 协议、路由、请求响应、服务端传输、WebSocket/SSE 基础、测试工具和稳定扩展入口,不发展为包含业务与基础设施集成的一体化平台。 + +当前协议抽象位于 `silent/src/server/protocol/`。具体协议或业务项目只能通过公开接口接入,不得依赖私有目录结构。 + +## 核心范围 + +- HTTP/1.1、HTTP/2、HTTP/3、TLS、QUIC 和通用监听能力; +- 路由、方法匹配、中间件、请求、响应和静态资源; +- 通用请求提取、错误响应扩展、State 和请求扩展; +- WebSocket、SSE、流式响应的协议与资源安全基础; +- TestClient 和协议测试支持; +- 面向独立项目的规范化路由、连接信息、生命周期和服务状态入口。 + +当前核心路线由 #209–#214 跟踪,并由 #223 汇总。进入代码阶段前,必须把规划项拆为单一职责 TODO 和独立分支。 + +## 非核心范围 + +| 能力 | 维护边界 | +|---|---| +| 认证、JWT、角色和权限 | 独立项目维护;核心只提供提取器、中间件、State 和错误扩展 | +| 生产会话存储 | 独立项目维护;核心保留 Cookie、请求扩展和 2.x 兼容入口 | +| Prometheus、OTLP 和监控平台 | 独立项目维护;核心只提供低基数事件和路由信息 | +| 可靠调度、持久化和分布式执行 | 独立项目维护;核心旧 scheduler 在 2.x 兼容保留 | +| 管理界面 | 独立项目维护;核心不内置管理页面 | +| SeaORM 和数据库适配 | 独立项目维护;核心不增加数据库依赖 | +| MQTT 报文和 Broker | 由独立 `silent-mqtt` 仓库维护;核心只提供 Protocol、NetServer 和连接接口 | +| CLI、项目模板和前端工程 | 独立项目维护;核心不内置脚手架 | + +上述生态方向在 Silent 仓库中只记录边界,不追踪开发进度;未来由各独立仓库维护自己的 PLAN、TODO、版本和发布。 + +## 兼容要求 + +- 2.x 不删除或重命名现有公开入口、Cargo feature 和公开重导出类型。 +- 内置 session、scheduler、admin、security、template、grpc、`Configs` 和 `RequestTimeLogger` 在 2.x 保持可用。 +- session、scheduler 等已迁出候选只做必要修复和迁移提示;security、template、grpc 的长期归属由 3.0 RFC 决定。 +- 已迁出能力只做必要修复和迁移提示,不继续加入复杂功能。 +- 破坏性移除只能进入 3.0,并且必须先具备公开 RFC、可用替代、迁移指南和兼容验证。 +- 新能力优先使用新增接口和安全默认值,不向具有公开字段的配置结构直接增加字段,也不向可被外部穷举的公开枚举直接增加变体。 + +## 通用约定 + +- 新增 ID 使用 scru128,不使用 UUID。 +- 时间字段默认使用 `chrono::Local::now().naive_local()`。 +- Rust 代码保持 Rust 2024 风格,避免引入 `unsafe`。 +- 前端生态项目使用 Vue、shadcn/ui 和 yarn,但前端工程不进入本仓库核心。 + +## 开发与验收 + +1. 开发前检查 `PLAN.md` 和本需求整理;未覆盖的功能必须先更新需求。 +2. `TODO.md` 只记录当前单一职责任务,每个任务使用独立分支和 PR。 +3. 核心改动需通过 `cargo fmt -- --check`、`cargo check --all`、全功能 clippy、全功能测试和 `cargo deny check`。 +4. HTTP 语义改动需验证 HTTP/1.1、HTTP/2、HTTP/3 一致性;路由改动需核对性能基准。 +5. 文档中的版本、路径、示例命令和项目归属必须能够从当前仓库核对。 diff --git a/docs/ws-runtime-agnostic.md b/docs/ws-runtime-agnostic.md index 631cdf6f..c80454ce 100644 --- a/docs/ws-runtime-agnostic.md +++ b/docs/ws-runtime-agnostic.md @@ -121,4 +121,4 @@ wrangler publish - 不建议;TokioAdapter 依赖 tokio IO,在浏览器/通用 wasm 环境不可用。 - 如果禁用 `server`,是否还能使用路由/Server? - 不能;Server 相关 API 由 `server` 特性提供。此时应由宿主提供请求/响应上下文, - 仅复用框架的 WS、SSE、模板等纯逻辑模块。 + 仅复用框架的 WS、SSE 等核心纯逻辑模块;模板属于 2.x 兼容能力。 diff --git a/readme.md b/readme.md index 099d24ea..1e7c45f4 100644 --- a/readme.md +++ b/readme.md @@ -1,10 +1,7 @@

Silent

- - build status - -
+build status crates.io Documentation GitWiki @@ -17,118 +14,137 @@

-### 概要 - -Silent 是一个简单的基于Hyper的Web框架,它的目标是提供一个简单的、高效的、易于使用的Web框架。 +## 概要 -### 文档 +Silent 是基于 Hyper 的纯 Web 框架,专注于 Web 协议、路由、请求响应、服务端传输、实时协议基础、测试工具和稳定扩展入口。 - [Crates.io](https://crates.io/crates/silent) - [API 文档](https://docs.rs/silent) -- [GitWiki 文档](https://deepwiki.com/silent-rs/silent) -- [ZRead 文档](https://zread.ai/silent-rs/silent) -- [Cloudflare Worker 使用指南](docs/cloudflare-worker.md) - -### 目标 - -- [x] 路由 -- [x] 中间件 -- [x] 静态文件 -- [x] WebSocket -- [x] 模板 -- [x] 日志 (使用了tracing) -- [x] 配置 -- [x] 会话 -- [x] 安全 -- [x] GRPC -- [x] 通用网络层 (NetServer) -- [x] Cloudflare Worker +- [项目规划](PLAN.md) +- [需求整理](docs/requirements.md) +- [Cloudflare Worker 指南](docs/cloudflare-worker.md) -## NetServer +## 核心能力 -提供与协议无关的通用网络服务器,支持 TCP、Unix Socket 等多种监听方式,并内置连接限流和优雅关停功能。 +- HTTP/1.1、HTTP/2、HTTP/3、TLS、QUIC 和通用 `NetServer`; +- 高性能路由、中间件、请求、响应和通用提取器; +- WebSocket、SSE、流式响应和静态资源; +- Cookie、State、请求扩展和 TestClient; +- Tower、Cloudflare Worker 与独立组件所需的公共扩展入口。 -### 基本用法 +## 项目边界 -```rust -use silent::NetServer; -use std::time::Duration; +认证与权限、生产会话存储、监控导出器、可靠任务调度、数据库接入、管理界面、MQTT 实现和项目脚手架不进入 Silent 核心,由独立仓库自行维护。 -#[tokio::main] -async fn main() { - NetServer::new() - .bind("127.0.0.1:8080".parse().unwrap()) - .with_rate_limiter(10, Duration::from_millis(10), Duration::from_secs(2)) - .with_shutdown(Duration::from_secs(5)) - .serve(|mut stream, peer| async move { - println!("Connection from: {}", peer); - // 处理连接... - Ok(()) - }) - .await; -} -``` +规划中的独立生态包括:`silent-auth`、`silent-session`、`silent-observability`、`silent-scheduler`、`silent-admin`、`silent-seaorm` 和 `silent-cli`。MQTT 已由 [silent-mqtt](https://github.com/silent-rs/silent-mqtt) 独立维护。尚未创建的仓库不提供占位链接。 -### 功能特性 +`silent-openapi` 位于当前工作区,但作为独立 crate 发布并维护自己的兼容关系。 -- **多监听器支持**: 同时监听多个 TCP 或 Unix Socket 地址 -- **连接限流**: 基于令牌桶算法的 QPS 限制 -- **优雅关停**: 支持 Ctrl-C 和 SIGTERM 信号,可配置等待时间 -- **协议无关**: 通过 `ConnectionService` trait 支持任意应用层协议 +## 2.x 兼容能力 -### 示例 +现有 `session`、`scheduler`、`security`、`template` 和 `grpc` feature 在整个 2.x 保持可用: -- [基本 TCP Echo 服务器](./examples/net_server_basic/) -- [自定义命令协议](./examples/net_server_custom_protocol/) +- `session` 和 `scheduler` 是兼容保留入口,只做必要修复,不继续扩展生产存储或可靠调度能力; +- `security` 是通用密码与加密工具,不是认证、用户或权限系统; +- `security`、`template` 和 `grpc` 的长期归属由 3.0 RFC 决定,本路线不预先承诺迁出或移除; +- `admin` 只是 `server + sse + template + session` 的兼容聚合 feature,不包含管理后台或管理界面; +- `Configs`、`RequestTimeLogger` 等已弃用入口在 2.x 继续保留,最早于 3.0 移除。 -## Extractors(萃取器) +破坏性调整只会在替代能力、迁移指南、兼容验证和公开 RFC 完成后进入 3.0。 -### 文档 +## 快速开始 -- [萃取器完整指南](./docs/extractors-guide.md) - 详细的使用文档和最佳实践 -- [API 文档](https://docs.rs/silent) - 完整的 API 参考 +```rust +use silent::prelude::*; -## security +async fn hello(_req: Request) -> Result<&'static str> { + Ok("Hello, Silent!") +} -### argon2 +#[tokio::main] +async fn main() { + let app = Route::new_root().append(Route::new("hello").get(hello)); -add make_password and verify_password function + Server::new() + .bind("127.0.0.1:8080".parse().unwrap()) + .serve(app) + .await; +} +``` -### pbkdf2 +## State -add make_password and verify_password function +应用级共享数据使用 `Route::with_state` 注入,通过 `Request::get_state` 或 `State` 提取器读取: -### aes +```rust +use silent::prelude::*; -re-export aes/aes_gcm +#[derive(Clone)] +struct AppConfig { + name: &'static str, +} -### rsa +async fn handler(req: Request) -> Result { + let config = req.get_state::()?; + Ok(format!("hello {}", config.name)) +} -re-export rsa +let app = Route::new_root() + .with_state(AppConfig { name: "Silent" }) + .get(handler); +``` -## configs +`Configs`、`get_config` 和 `configs` 仅为 2.x 兼容保留,新代码应使用 State API。 -### setting +## NetServer -```rust -use silent::Configs; -let mut configs = Configs::default (); -configs.insert(1i32); -``` +`NetServer` 提供与具体应用层协议无关的 TCP/Unix Socket 监听、连接限流和优雅关停。具体协议实现由独立项目负责。 + +```rust,no_run +use silent::{BoxedConnection, NetServer, RateLimiterConfig, SocketAddr}; +use std::time::Duration; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; -### usage +#[tokio::main] +async fn main() { + let handler = |mut stream: BoxedConnection, _peer: SocketAddr| async move { + let mut buf = [0_u8; 1024]; + let n = stream.read(&mut buf).await?; + stream.write_all(&buf[..n]).await?; + Ok::<(), Box>(()) + }; + + let rate_limit = RateLimiterConfig { + capacity: 100, + refill_every: Duration::from_millis(10), + max_wait: Duration::from_secs(1), + }; -```rust -async fn call(req: Request) -> Result { - let num = req.configs().get::().unwrap(); - Ok(*num) + NetServer::new() + .bind("127.0.0.1:8080".parse().unwrap()) + .unwrap() + .with_rate_limiter(rate_limit) + .with_shutdown(Duration::from_secs(30)) + .serve(handler) + .await; } ``` -## examples for llm +示例: -* [whisper with candle](./examples/candle_whisper/readme.md) +- [基本 TCP Echo 服务器](examples/net_server_basic/) +- [自定义命令协议](examples/net_server_custom_protocol/) +- [萃取器指南](docs/extractors-guide.md) +- [OpenAPI 组件](silent-openapi/README.md) -## complex projects for llm +## 开发检查 + +```bash +cargo fmt -- --check +cargo check --all +cargo clippy --all-targets --all-features --tests --benches -- -D warnings +cargo nextest run --all-features +cargo deny check +``` -* [llm_server](https://github.com/silent-rs/llm_server) +项目采用 Apache-2.0 许可证。 diff --git a/silent-openapi/Cargo.toml b/silent-openapi/Cargo.toml index ac7a306f..d0a0fe7d 100644 --- a/silent-openapi/Cargo.toml +++ b/silent-openapi/Cargo.toml @@ -1,7 +1,7 @@ [package] authors.workspace = true categories = ["web-programming"] -description = "OpenAPI 3.0 support for Silent web framework" +description = "OpenAPI 3.1 support for Silent web framework" edition.workspace = true homepage.workspace = true keywords = ["silent", "openapi", "swagger", "web", "api"] diff --git a/silent-openapi/IMPLEMENTATION_SUMMARY.md b/silent-openapi/IMPLEMENTATION_SUMMARY.md index cf9bcc4c..dda54bd2 100644 --- a/silent-openapi/IMPLEMENTATION_SUMMARY.md +++ b/silent-openapi/IMPLEMENTATION_SUMMARY.md @@ -2,7 +2,7 @@ ## 🎯 项目概述 -成功为Silent Web框架实现了完整的OpenAPI 3.0支持库,包括自动文档生成和Swagger UI集成。 +成功为 Silent Web 框架实现了基于 utoipa 5 的 OpenAPI 3.1 支持库,包括自动文档生成和 Swagger UI 集成。 ## ✅ 已完成的功能 @@ -24,7 +24,7 @@ #### 📖 自动文档生成 - 基于 `utoipa` 的编译时文档生成 - 支持路径参数自动转换 (`` → `{id}`) -- 完整的OpenAPI 3.0规范支持 +- OpenAPI 3.1 规范支持 #### 🎨 Swagger UI集成 - 内置美观的交互式文档界面 diff --git a/silent-openapi/README.md b/silent-openapi/README.md index 7e0450bd..97f9ec04 100644 --- a/silent-openapi/README.md +++ b/silent-openapi/README.md @@ -1,32 +1,35 @@ # Silent OpenAPI -🚀 为 [Silent Web Framework](https://github.com/silent-rs/silent) 提供 OpenAPI 3.0 支持和 Swagger UI 集成。 +`silent-openapi` 为 [Silent Web Framework](https://github.com/silent-rs/silent) 提供基于 utoipa 5 的 OpenAPI 3.1 文档生成,以及 Swagger UI 和 ReDoc 集成。 -## ✨ 特性 +它与 Silent 位于同一工作区,但作为独立 crate 发布和维护兼容关系。 -- 🔧 **深度集成** - 与 Silent 框架无缝集成 -- 📖 **自动文档** - 基于 [utoipa](https://github.com/juhaku/utoipa) 的编译时文档生成 -- 🖥️ **Swagger UI** - 内置美观的交互式 API 文档界面 -- 🚀 **零运行时开销** - 编译时生成,运行时高性能 -- 🎯 **易于使用** - 简单的 API 和丰富的示例 -- 🌐 **中文支持** - 完整的中文文档和错误消息 +## 特性 -## 📦 安装 +- 使用 `utoipa::OpenApi` 和 `ToSchema` 在编译期生成文档; +- 提供 Swagger UI 中间件和路由处理器两种挂载方式; +- 支持 ReDoc、路由文档收集和 OpenAPI JSON 输出; +- 支持 Bearer、API Key 和全局安全要求; +- 可关闭 Swagger UI 的 Try it out,或启用本地嵌入资源。 -在你的 `Cargo.toml` 中添加: +## 安装 ```toml [dependencies] -silent = "2.5" -silent-openapi = "0.1" -utoipa = { version = "4.2", features = ["derive"] } -serde = { version = "1.0", features = ["derive"] } -tokio = { version = "1.0", features = ["full"] } +silent = "2.16" +silent-openapi = "2.16" +utoipa = "5" +serde = { version = "1", features = ["derive"] } +tokio = { version = "1", features = ["full"] } ``` -## 🚀 快速开始 +需要 Swagger UI 本地资源时启用: -### 基础使用 +```toml +silent-openapi = { version = "2.16", features = ["swagger-ui-embedded"] } +``` + +## 快速开始 ```rust use serde::{Deserialize, Serialize}; @@ -38,323 +41,154 @@ use utoipa::OpenApi; struct User { id: u64, name: String, - email: String, } -#[derive(OpenApi)] -#[openapi( - info(title = "用户API", version = "1.0.0"), - paths(get_users, create_user), - components(schemas(User)) -)] -struct ApiDoc; - #[utoipa::path( get, path = "/users", responses((status = 200, description = "用户列表", body = [User])) )] -async fn get_users(_req: Request) -> Result { - let users = vec![ - User { id: 1, name: "张三".to_string(), email: "zhangsan@example.com".to_string() } - ]; - Ok(Response::json(&users)) +async fn list_users(_req: Request) -> Result { + Ok(Response::json(&vec![User { + id: 1, + name: "Alice".to_string(), + }])) } -#[utoipa::path( - post, - path = "/users", - request_body = User, - responses((status = 201, description = "用户创建成功", body = User)) +#[derive(OpenApi)] +#[openapi( + info(title = "用户 API", version = "1.0.0"), + paths(list_users), + components(schemas(User)) )] -async fn create_user(mut req: Request) -> Result { - let user: User = req.form_parse().await?; - Ok(Response::json(&user).with_status(StatusCode::CREATED)) -} +struct ApiDoc; #[tokio::main] -async fn main() -> Result<()> { - logger::fmt().init(); +async fn main() { + let swagger = SwaggerUiMiddleware::new("/docs", ApiDoc::openapi()) + .expect("create Swagger UI"); + let app = Route::new_root() + .hook(swagger) + .append(Route::new("users").get(list_users)); + + Server::new() + .bind("127.0.0.1:8080".parse().unwrap()) + .serve(app) + .await; - // 创建 Swagger UI 中间件 - let swagger = SwaggerUiMiddleware::new("/docs", ApiDoc::openapi())?; - - // 构建路由 - let routes = Route::new("") - .hook(swagger) // 添加 Swagger UI - .append( - Route::new("users") - .get(get_users) - .post(create_user) - ); - - println!("📖 API 文档: http://localhost:8080/docs"); - - Server::new().run(routes); - Ok(()) } ``` -### 使用处理器方式 +启动后访问: -```rust -use silent_openapi::SwaggerUiHandler; +- Swagger UI:`http://127.0.0.1:8080/docs` +- OpenAPI JSON:`http://127.0.0.1:8080/docs/openapi.json` -// 创建 Swagger UI 处理器 -let swagger_handler = SwaggerUiHandler::new("/api-docs", ApiDoc::openapi())?; +## 使用处理器挂载 -let routes = Route::new("") - .append(Route::new("api-docs").any(swagger_handler)) - .append(your_api_routes); -``` - -## 📚 详细用法 - -### 定义数据模型 - -使用 `ToSchema` derive 宏为你的数据结构生成 OpenAPI 模式: +`SwaggerUiHandler` 已实现 `RouterAdapt`,可以直接追加到根路由: ```rust -use silent_openapi::ToSchema; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, ToSchema)] -#[schema(example = json!({ - "id": 1, - "name": "张三", - "email": "zhangsan@example.com" -}))] -struct User { - /// 用户 ID - #[schema(minimum = 1)] - id: u64, - - /// 用户名 - #[schema(min_length = 1, max_length = 50)] - name: String, - - /// 邮箱地址 - #[schema(format = "email")] - email: String, -} -``` - -### 文档化 API 端点 - -使用 `utoipa::path` 宏为你的处理函数生成文档: +use silent::prelude::*; +use silent_openapi::SwaggerUiHandler; -```rust -#[utoipa::path( - get, - path = "/users/{id}", - tag = "users", - summary = "获取用户信息", - description = "根据用户 ID 获取用户详细信息", - params( - ("id" = u64, Path, description = "用户 ID", example = 1) - ), - responses( - (status = 200, description = "成功获取用户信息", body = User), - (status = 404, description = "用户不存在", body = ErrorResponse) - ) -)] -async fn get_user(req: Request) -> Result { - let id: u64 = req.get_path_params("id")?; - // 处理逻辑... -} +let swagger = SwaggerUiHandler::new("/docs", ApiDoc::openapi())?; +let app = Route::new_root() + .append(swagger) + .append(your_api_routes); ``` -### 定义 OpenAPI 文档 - -```rust -#[derive(OpenApi)] -#[openapi( - info( - title = "用户管理 API", - version = "1.0.0", - description = "一个简单的用户管理系统 API", - contact( - name = "API Support", - email = "support@example.com" - ) - ), - servers( - (url = "http://localhost:8080", description = "开发服务器"), - (url = "https://api.example.com", description = "生产服务器") - ), - paths( - get_users, - get_user, - create_user, - update_user, - delete_user - ), - components( - schemas(User, CreateUserRequest, ErrorResponse) - ), - tags( - (name = "users", description = "用户管理相关 API") - ) -)] -struct ApiDoc; +不需要额外创建 `.any()` 路由。 -### 路由自动生成 OpenAPI + 安全定义 + Try it out 开关 +## 从路由生成文档 -无需手写 `#[derive(OpenApi)]`,可以直接从路由生成基础文档,并补充安全定义: +`RouteOpenApiExt` 可以根据 Silent 路由生成基础 OpenAPI 文档: ```rust -use silent_openapi::{RouteOpenApiExt, OpenApiDoc, SwaggerUiMiddleware, SwaggerUiOptions}; +use silent::prelude::*; +use silent_openapi::{OpenApiDoc, RouteOpenApiExt, SwaggerUiHandler}; -// 1) 先构建业务路由 -let routes = Route::new("") +let routes = Route::new("api") .append(Route::new("users").get(list_users)) - .append(Route::new("users").append(Route::new("").get(get_user))); + .append(Route::new("users/").get(get_user)); -// 2) 基于路由生成 OpenAPI 并添加 Bearer(JWT) 安全定义 + 全局 security let openapi = routes.to_openapi("User API", "1.0.0"); let openapi = OpenApiDoc::from_openapi(openapi) .add_bearer_auth("bearerAuth", Some("JWT Bearer token")) .set_global_security("bearerAuth", &[]) .into_openapi(); -// 3) 自定义 UI 选项(如关闭 Try it out)并挂载到 /docs -let options = SwaggerUiOptions { try_it_out_enabled: false }; -let swagger = SwaggerUiMiddleware::with_options("/docs", openapi, options)?; -let app = Route::new("").hook(swagger).append(routes); -``` +let app = Route::new_root() + .append(SwaggerUiHandler::new("/docs", openapi)?) + .append(routes); ``` -## 🎨 配置选项 +路由自动收集用于生成基础文档;需要精确的请求、响应和 schema 信息时,继续使用 `#[utoipa::path]` 和 `#[derive(ToSchema)]`。 -### Swagger UI 自定义 +## Swagger UI 配置 ```rust -// 使用自定义路径 -let swagger = SwaggerUiMiddleware::with_custom_api_doc_path( - "/docs", // Swagger UI 路径 - "/openapi.json", // OpenAPI JSON 路径 - ApiDoc::openapi() -)?; -``` - -### 多种集成方式 - -1. **中间件方式** - 推荐用于全局文档 -2. **处理器方式** - 推荐用于特定路由下的文档 - -## 📖 示例 - -查看 `examples/` 目录中的完整示例: +use silent_openapi::{SwaggerUiMiddleware, SwaggerUiOptions}; -- `basic_openapi.rs` - 基础集成示例 -- `user_api.rs` - 完整的用户管理 API +let options = SwaggerUiOptions { + try_it_out_enabled: false, +}; -运行示例: - -```bash -# 基础示例 -cargo run --example basic_openapi - -# 用户 API 示例 -cargo run --example user_api +let swagger = SwaggerUiMiddleware::with_options( + "/docs", + ApiDoc::openapi(), + options, +)?; ``` -## 🔒 生产环境建议 - -- 关闭交互尝试:将 `try_it_out_enabled` 设为 `false`,避免未授权的在线调用。 -- 保护文档入口:将 `/docs` 放在受保护的子路由或网关后,或在上游加鉴权(如 Basic/JWT)。 -- 安全定义:在 OpenAPI 中声明 `bearerAuth` 并设置全局 `security`,与实际网关/服务策略一致。 -- CORS 与缓存:为 `/openapi.json` 设置合理的 `Cache-Control`,并按需配置 CORS;避免缓存过期导致前端文档不一致。 -- 环境隔离:为 dev/stage/prod 设置不同的 `servers`,并确保敏感接口在非生产环境才开放 `Try it out`。 +自定义 OpenAPI JSON 地址: -## 🛠️ 支持的特性 - -### OpenAPI 3.0 特性 - -- ✅ 路径和操作定义 -- ✅ 请求/响应模式 -- ✅ 参数验证 -- ✅ 标签和分组 -- ✅ 示例数据 -- ✅ 服务器配置 -- ✅ 安全定义(计划中) +```rust +let swagger = SwaggerUiMiddleware::with_custom_api_doc_path( + "/docs", + "/openapi.json", + ApiDoc::openapi(), +)?; +``` -### Swagger UI 特性 +## 安全建议 -- ✅ 交互式 API 测试 -- ✅ 模式浏览 -- ✅ 请求/响应示例 -- ✅ 中文界面支持 -- ✅ 响应式设计 -- ✅ CDN 资源加载 +- 生产环境按需关闭 Try it out; +- 将文档入口放在受保护的路由或网关之后; +- 不要把认证、用户或权限逻辑放入 `silent-openapi`;本 crate 只描述 OpenAPI 安全方案; +- 为不同环境设置正确的 `servers`,避免文档指向错误地址; +- 为 OpenAPI JSON 配置合适的缓存和跨域策略。 -## 🔧 高级用法 +## 示例 -### 错误处理 +当前示例: -```rust -use silent_openapi::{OpenApiError, Result}; - -fn handle_openapi_error(error: OpenApiError) -> Response { - match error { - OpenApiError::Json(e) => { - Response::json(&format!("JSON 错误: {}", e)) - .with_status(StatusCode::BAD_REQUEST) - } - OpenApiError::ResourceNotFound { resource } => { - Response::json(&format!("资源未找到: {}", resource)) - .with_status(StatusCode::NOT_FOUND) - } - _ => { - Response::json("内部服务器错误") - .with_status(StatusCode::INTERNAL_SERVER_ERROR) - } - } -} -``` +- `simple_example`:最小 Swagger UI 集成; +- `user_api`:完整的用户 CRUD 文档; +- `security_example`:Bearer 安全定义和路由处理器挂载。 -### 路由文档收集 +从工作区根目录运行: -```rust -use silent_openapi::{RouteDocumentation, OpenApiDoc}; - -// 从现有路由生成文档 -let doc = my_route.generate_openapi_doc( - "My API", - "1.0.0", - Some("API description") -); +```bash +cargo run -p silent-openapi --example simple_example +cargo run -p silent-openapi --example user_api +cargo run -p silent-openapi --example security_example ``` -## 🚦 版本兼容性 - -| silent-openapi | silent | utoipa | -|---------------|---------|---------| -| 0.1.x | 2.5.x | 4.2.x | - -## 🤝 贡献 +## 版本兼容性 -欢迎贡献代码、报告问题或提出建议! +| silent-openapi | silent | utoipa | OpenAPI | +|---|---|---|---| +| 2.16.x | 2.16.x | 5.x | 3.1 | -1. Fork 项目 -2. 创建特性分支 (`git checkout -b feature/amazing-feature`) -3. 提交更改 (`git commit -m 'Add amazing feature'`) -4. 推送分支 (`git push origin feature/amazing-feature`) -5. 创建 Pull Request +2.x 范围内以工作区当前版本组合为准。升级 utoipa 主版本时需要重新核对生成结果和公开类型兼容性。 -## 📄 许可证 +## 许可证 -本项目采用 MIT 或 Apache-2.0 双许可证。详见 [LICENSE](../LICENSE) 文件。 +本项目采用 Apache-2.0 许可证,详见 [LICENSE](../LICENSE)。 -## 🔗 相关链接 +## 相关链接 - [Silent Web Framework](https://github.com/silent-rs/silent) -- [utoipa - OpenAPI for Rust](https://github.com/juhaku/utoipa) -- [OpenAPI 3.0 规范](https://swagger.io/specification/) -- [Swagger UI](https://swagger.io/tools/swagger-ui/) - ---- - -
-Made with ❤️ for the Rust community -
+- [utoipa](https://github.com/juhaku/utoipa) +- [OpenAPI 3.1 规范](https://spec.openapis.org/oas/v3.1.0) diff --git a/silent-openapi/src/lib.rs b/silent-openapi/src/lib.rs index aaea4a3c..e59be318 100644 --- a/silent-openapi/src/lib.rs +++ b/silent-openapi/src/lib.rs @@ -1,6 +1,6 @@ //! # Silent OpenAPI //! -//! 为Silent Web框架提供OpenAPI 3.0支持,包括自动文档生成和Swagger UI集成。 +//! 为 Silent Web 框架提供 OpenAPI 3.1 支持,包括自动文档生成和 Swagger UI 集成。 //! //! ## 主要特性 //! diff --git a/silent-openapi/src/ui_html.rs b/silent-openapi/src/ui_html.rs index 69e23a24..2cb4153b 100644 --- a/silent-openapi/src/ui_html.rs +++ b/silent-openapi/src/ui_html.rs @@ -94,7 +94,7 @@ pub fn generate_index_html(

Silent Framework API Documentation

-

OpenAPI 3.0

+

OpenAPI 3.1

diff --git a/silent/Cargo.toml b/silent/Cargo.toml index 71515b6b..662dfb7d 100644 --- a/silent/Cargo.toml +++ b/silent/Cargo.toml @@ -21,6 +21,7 @@ rust-version.workspace = true version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] +# 2.x 兼容聚合开关,仅组合现有能力,不包含管理后台或管理界面。 admin = ["server", "sse", "template", "session"] cookie = ["dep:cookie"] default = ["server"] diff --git a/silent/src/configs/mod.rs b/silent/src/configs/mod.rs index fc80813a..b3b37100 100644 --- a/silent/src/configs/mod.rs +++ b/silent/src/configs/mod.rs @@ -41,11 +41,10 @@ pub struct State { /// `Configs` 是 `State` 的类型别名,保持向后兼容。 /// -/// **已弃用**:请使用 `State` 提取器代替 `Configs` 提取器。 -/// `Configs` 将在 v2.18.0 中移除。 +/// **已弃用**:请使用 `State` 代替。`Configs` 在 2.x 中继续保留,最早于 3.0 移除。 #[deprecated( since = "2.16.0", - note = "请使用 State 提取器代替,Configs 将在 v2.18.0 移除" + note = "请使用 State 代替;Configs 在 2.x 中继续保留,最早于 3.0 移除" )] pub type Configs = State; diff --git a/silent/src/extractor/types.rs b/silent/src/extractor/types.rs index 83e1f8c0..16bcfcd2 100644 --- a/silent/src/extractor/types.rs +++ b/silent/src/extractor/types.rs @@ -23,7 +23,7 @@ pub struct State(pub T); #[deprecated( since = "2.16.0", - note = "请使用 State 代替,Configs 将在 v2.18.0 移除" + note = "请使用 State 代替;Configs 在 2.x 中继续保留,最早于 3.0 移除" )] pub struct Configs(pub T); diff --git a/silent/src/middleware/middlewares/request_time_logger.rs b/silent/src/middleware/middlewares/request_time_logger.rs index 43e175a6..7a21f608 100644 --- a/silent/src/middleware/middlewares/request_time_logger.rs +++ b/silent/src/middleware/middlewares/request_time_logger.rs @@ -6,7 +6,8 @@ use chrono::Utc; /// /// # 已弃用 /// -/// 此中间件将在 v2.17.0 版本移除,请使用 [`Logger`](super::Logger) 替代。 +/// 请使用 [`Logger`](super::Logger) 替代。`RequestTimeLogger` 在 2.x 中继续保留, +/// 最早于 3.0 移除。 /// /// `Logger` 相比 `RequestTimeLogger` 的改进: /// - 使用 `Instant` 单调时钟替代 `Utc::now().time()`,避免跨午夜负值问题 @@ -20,7 +21,10 @@ use chrono::Utc; /// /// let _ = RequestTimeLogger::new(); /// ``` -#[deprecated(since = "2.15.0", note = "将在 v2.17.0 移除,请使用 Logger 替代")] +#[deprecated( + since = "2.15.0", + note = "请使用 Logger 替代;RequestTimeLogger 在 2.x 中继续保留,最早于 3.0 移除" +)] #[derive(Default, Clone)] pub struct RequestTimeLogger; diff --git a/silent/src/server/route_connection.rs b/silent/src/server/route_connection.rs index 1e746829..c44046cd 100644 --- a/silent/src/server/route_connection.rs +++ b/silent/src/server/route_connection.rs @@ -64,7 +64,7 @@ impl RouteConnectionService { } } - /// 构建冻结路由树(包含 session/cookie/scheduler 检查) + /// 构建冻结路由树(包含 2.x 兼容的 session/cookie/scheduler 检查) fn build_route_tree(route: &Route) -> RouteTree { #[allow(unused_mut)] let mut route = route.clone(); From 0b6e5e58ae94e0459261ff84ce3a2254a649fb18 Mon Sep 17 00:00:00 2001 From: Hubert Shelley <46239302+hubertshelley@users.noreply.github.com> Date: Fri, 10 Jul 2026 20:51:25 +0800 Subject: [PATCH 2/3] =?UTF-8?q?docs(todo):=20=E8=AE=B0=E5=BD=95=E8=B7=AF?= =?UTF-8?q?=E7=BA=BF=E6=A0=A1=E5=87=86=20PR?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index 69c69b36..b96acc8c 100644 --- a/TODO.md +++ b/TODO.md @@ -4,7 +4,7 @@ > > 当前分支:`feature/roadmap-sync` > -> 状态:验证完成,待提交 PR +> 状态:已完成,等待 PR #224 合并 ## 当前任务 @@ -16,7 +16,7 @@ - [x] 修正 silent-openapi 的版本组合、示例、许可证和 OpenAPI 版本说明。 - [x] 将 `Configs`、`RequestTimeLogger` 的移除承诺统一为最早 3.0。 - [x] 完成格式、构建、测试和依赖检查。 -- [ ] 创建 PR 并关联 #222。 +- [x] 创建 PR #224 并关联 #222。 ## 完成标准 From dad436b22c758233c3c3a458864f5c9c04a47501 Mon Sep 17 00:00:00 2001 From: Hubert Shelley <46239302+hubertshelley@users.noreply.github.com> Date: Fri, 10 Jul 2026 21:25:01 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix(ci):=20=E5=85=BC=E5=AE=B9=20Rust=201.97?= =?UTF-8?q?=20=E4=B8=A5=E6=A0=BC=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO.md | 1 + silent-openapi/src/route.rs | 2 +- silent/src/server/listener.rs | 5 ++--- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/TODO.md b/TODO.md index b96acc8c..62cf78c9 100644 --- a/TODO.md +++ b/TODO.md @@ -15,6 +15,7 @@ - [x] 更新根 README,区分核心能力、2.x 兼容能力和独立生态项目。 - [x] 修正 silent-openapi 的版本组合、示例、许可证和 OpenAPI 版本说明。 - [x] 将 `Configs`、`RequestTimeLogger` 的移除承诺统一为最早 3.0。 +- [x] 修正 Rust 1.97 严格检查发现的两处兼容写法。 - [x] 完成格式、构建、测试和依赖检查。 - [x] 创建 PR #224 并关联 #222。 diff --git a/silent-openapi/src/route.rs b/silent-openapi/src/route.rs index 4cb95601..c9b59f5e 100644 --- a/silent-openapi/src/route.rs +++ b/silent-openapi/src/route.rs @@ -54,7 +54,7 @@ impl DocumentedRoute { let full_path = if base_path.is_empty() { path_doc.path.clone() } else { - format!("{}{}", base_path.trim_end_matches('/'), &path_doc.path) + format!("{}{}", base_path.trim_end_matches('/'), path_doc.path) }; // 转换Silent路径参数格式到OpenAPI格式 diff --git a/silent/src/server/listener.rs b/silent/src/server/listener.rs index 8e913b79..86aba31e 100644 --- a/silent/src/server/listener.rs +++ b/silent/src/server/listener.rs @@ -357,11 +357,10 @@ impl Listeners { return Some(Err(e)); } } - } else if let Some(next_ready) = earliest_ready { + } else { + let next_ready = earliest_ready?; sleep_until(next_ready).await; continue; - } else { - return None; } } }