AFast
简体中文 | English
AFast 是一个高性能 Rust Web 后端框架。用 #[handler] 标注函数即可——框架自动注册路由、通过紧凑二进制协议分发请求,并一键生成 TypeScript / JavaScript / Kotlin / Rust 客户端代码。
亮点
- 零路由定义 —
#[handler]标注函数,无需手动路由表 - 紧凑二进制协议 — 比 JSON 更小更快,专为内部通信设计
- 零拷贝 State —
State<T>持有&'static T,无 per-request clone,不要求T: Clone - 自动代码生成 — TypeScript / JavaScript / Kotlin / Rust 客户端,完整类型定义
- 交互式 API 文档 — 内置 Web 文档,暗色/亮色主题,在线测试,WS/SSE 调试
- 多传输层 — WebSocket、HTTP/1.1、HTTP/2、TCP,按需组合
- TLS / HTTPS — 基于 rustls,ALPN 协商 HTTP/2
- RESTful 端点 —
#[get]/#[post]/#[put]/#[delete]+ JSON - WebSocket & SSE —
#[ws]和#[sse]路由宏 - 长连接 — 通过
Receiver/Sender双向流式通信 - 生命周期钩子 —
before_request/on_response/on_error/on_connect/on_disconnect - 请求上下文 —
Ctx<T>请求级上下文,hook 写入,handler 读取 - 请求限流 — 命名策略 + 可插拔存储后端
快速示例
use afast::{AFast, handler, service, State, Data, Result};
use afast::{AFastDeserialize, AFastSerialize, Tag};
struct AppState { db_url: String }
#[derive(AFastDeserialize, Tag)]
#[tag("请求体")]
struct HelloReq { name: String }
#[derive(AFastSerialize, Tag)]
#[tag("响应体")]
struct HelloResp { message: String }
#[handler(desc("打招呼"))]
async fn hello(
state: State<AppState>,
req: Data<HelloReq>,
) -> Result<HelloResp> {
Ok(HelloResp { message: format!("Hello, {}!", req.name) })
}
#[tokio::main]
async fn main() {
let svc = service!("api" => { h(hello) });
AFast::new()
.state(AppState { db_url: "localhost".into() })
.service(svc)
.http("0.0.0.0:5000")
.run().await.unwrap();
}
快速开始
添加依赖
[dependencies]
afast = { version = "0.1.24", features = ["http", "ordinary-http", "ts"] }
tokio = { version = "1", features = ["full"] }
定义 State 和 Handler
#![allow(unused)]
fn main() {
use afast::{AFast, handler, service, State, Data, Custom, Result};
use afast::{AFastDeserialize, AFastSerialize, Tag};
use std::sync::{Arc, Mutex};
// State — no Clone required, holds &'static T internally
struct AppState {
db_url: String,
counter: Arc<Mutex<u64>>,
}
#[derive(AFastDeserialize, Tag)]
#[tag("Auth info")]
struct AuthCustom { token: i64, platform: String }
#[derive(AFastDeserialize, Tag)]
#[tag("Request body")]
struct HelloReq { name: String }
#[derive(AFastSerialize, Tag)]
#[tag("Response body")]
struct HelloResp { message: String }
#[handler(desc("Say hello"), name("hello"))]
async fn hello(
state: State<AppState>,
auth: Custom<AuthCustom>,
req: Data<HelloReq>,
) -> Result<HelloResp> {
let mut count = state.counter.lock().unwrap();
*count += 1;
Ok(HelloResp { message: format!("Hello, {}! (count: {})", req.name, count) })
}
}
注册路由并运行
#[tokio::main]
async fn main() {
let svc = service!("api", "Example API" => {
h(hello),
});
AFast::new()
.state(AppState {
db_url: "localhost".into(),
counter: Arc::new(Mutex::new(0)),
})
.service(svc)
.http("0.0.0.0:5000")
.run().await.unwrap();
}
运行
cargo run
- HTTP API:
POST http://localhost:5000/_api(二进制协议) - API 文档:
http://localhost:5000/doc - 生成的 TS 客户端:
./client/api.ts
多种传输层
#![allow(unused)]
fn main() {
AFast::new()
.state(app_state)
.service(svc)
.ws("0.0.0.0:3001") // 二进制 WebSocket
.tcp("0.0.0.0:4001") // 二进制 TCP
.http("0.0.0.0:5001") // HTTP + ordinary 路由
.run().await.unwrap();
}
或者将 WS 合并到 HTTP(同一端口):
#![allow(unused)]
fn main() {
AFast::new()
.state(app_state)
.service(svc)
.ws("0.0.0.0:5001")
.http("0.0.0.0:5001") // 相同端口 → 自动合并
.run().await.unwrap();
}
Feature 列表
核心
| Feature | 描述 | 依赖 |
|---|---|---|
binary | 二进制协议 (POST /_api、WS 帧、TCP 帧) | — |
http | HTTP 服务器 | hyper, hyper-util, http-body-util |
ws | WebSocket 服务器 | tokio-tungstenite, futures-util, binary |
tcp | TCP 服务器 (长度前缀帧) | binary |
代码生成
| Feature | 描述 | 依赖 |
|---|---|---|
ts | TypeScript 客户端生成 (ESM + 完整类型) | — |
js | JavaScript 客户端生成 (ESM + JSDoc) | — |
kt | Kotlin 客户端生成 | — |
rs | Rust 客户端生成 (Tokio 异步 / std 同步 TCP) | — |
cs | C# / .NET 客户端生成 (HttpClient / WebSocket / TCP) | — |
code | 按需代码生成,端点 /code/{service}/{lang} | http |
文档
| Feature | 描述 | 依赖 |
|---|---|---|
doc | 交互式 API 文档,端点 /doc | http, js |
Ordinary 路由
| Feature | 描述 | 依赖 |
|---|---|---|
ordinary-http | RESTful JSON 端点 (#[get]/#[post] 等) | http, serde, serde_json |
ordinary-ws | 基于路径的 WebSocket 端点 (#[ws]) | ws, ordinary-http |
ordinary-sse | Server-Sent Events 端点 (#[sse]) | ordinary-http, futures-util |
协议选项
| Feature | 描述 |
|---|---|
seq64 | WS 请求 ID 使用 i64(默认 i32) |
len64 | WS 负载长度使用 u64(默认 u32) |
tag-u8 | 枚举标签使用 u8(默认) |
tag-u16 | 枚举标签使用 u16 |
tag-u32 | 枚举标签使用 u32 |
TLS
| Feature | 描述 | 依赖 |
|---|---|---|
tls | 基于 rustls 的 HTTPS,ALPN 协商 HTTP/2,支持 channel 热重载 | http, tokio-rustls, rustls, rustls-pemfile |
可选能力
| Feature | 描述 |
|---|---|
marker | 基于标记的条件序列化,通过 AFast::marker() 设置 |
hook | 生命周期钩子 (before_request/on_connect 等),支持全局和按服务配置 |
rate-limit | 命名策略的速率限制 (FixedWindow/SlidingWindow/TokenBucket) |
tls | TLS/HTTPS 支持,基于 rustls 并支持 ALPN |
注意: 如果服务端使用了
seq64或len64,生成的客户端代码必须使用相同的 Feature,否则会出现协议不匹配。
核心概念
Handler 注册
#[handler] 过程宏在编译时生成以下内容:
- 原始函数保持不变
HandlerMeta— 名称、描述、参数列表、返回类型的元数据HandlerInvokertrait 实现 — 类型擦除的调用器,反序列化参数并调用函数- 一个静态调用器实例 — 由
register!宏引用
#![allow(unused)]
fn main() {
#[handler(desc("Get user"), name("get_user"))]
async fn get_user_handler(
state: State<AppState>,
auth: Custom<Auth>,
req: Data<UserIdRequest>,
) -> Result<UserInfo> {
// ...
}
}
desc("...")— 设置文档和 JSDoc 注释中使用的描述name("...")— 覆盖客户端方法名(默认为 Rust 函数名)cache(seconds)— 启用客户端缓存rate_limit("policy")— 将 handler 绑定到命名的速率限制策略- 其他属性 — 作为自定义属性收集到
HandlerMeta::attrs中
自定义属性
你可以在 handler 宏中添加任意属性。它们会作为 Attr 键值对收集到 HandlerMeta::attrs 中:
#![allow(unused)]
fn main() {
#[handler(desc("Create user"), tag("admin"), timeout(30), deprecated)]
async fn create_user(...) -> ... { ... }
}
在运行时,通过 invoker.meta().unwrap().attrs 读取:
#![allow(unused)]
fn main() {
if let Some(meta) = invoker.meta() {
for attr in meta.attrs {
match attr.value {
AttrValue::Str(v) => println!("{} = {}", attr.key, v),
AttrValue::Int(v) => println!("{} = {}", attr.key, v),
AttrValue::Bool(v) => println!("{} = {}", attr.key, v),
}
}
}
}
值类型会自动推断:
- 字符串:
tag("admin")→AttrValue::Str("admin") - 整数:
timeout(30)→AttrValue::Int(30) - 布尔:
deprecated→AttrValue::Bool(true)
支持两种语法:tag("admin") 和 tag = "admin"。
自定义属性也可以在钩子中通过 RequestContext::attrs 访问:
#![allow(unused)]
fn main() {
impl Hook for MyHook {
fn before_request(&self, ctx: &RequestContext) -> Option<Box<dyn RequestGuard>> {
for attr in ctx.attrs {
if attr.key == "deprecated" {
eprintln!("WARNING: {} is deprecated", ctx.handler_name);
}
}
None
}
}
}
多 State 支持
AFast 支持注册多个 State 类型。StateMap 使用 TypeId 作为键,每个类型对应一个值。State<T> 持有 &'static T 引用 — 值在启动时通过 Box::leak 分配一次,不会在每次请求时克隆:
#![allow(unused)]
fn main() {
struct DbConfig { url: String }
struct RedisConfig { url: String }
struct AppConfig { name: String }
let app = AFast::new()
.state(DbConfig { url: "postgres://...".into() })
.state(RedisConfig { url: "redis://...".into() })
.state(AppConfig { name: "my-app".into() });
#[handler(desc("Multiple State example"))]
async fn my_handler(
db: State<DbConfig>,
redis: State<RedisConfig>,
config: State<AppConfig>,
) -> Result<()> {
println!("DB: {}, Redis: {}, App: {}", db.url, redis.url, config.name);
Ok(())
}
}
如果 handler 引用了未注册的 State 类型,运行时会返回 CODE_STATE_NOT_FOUND 错误。
内部可变性
由于 State<T> 提供的是共享的 &'static T 引用,修改需要使用内部可变性模式。将可变字段包装在 Arc<Mutex<...>> 或 Arc<RwLock<...>> 中:
#![allow(unused)]
fn main() {
use std::sync::{Arc, Mutex};
struct AppState {
db: Arc<Mutex<Database>>,
counter: Arc<Mutex<u64>>,
}
#[handler(desc("Increment counter"))]
async fn increment(state: State<AppState>) -> Result<()> {
let mut count = state.counter.lock().unwrap();
*count += 1;
Ok(())
}
}
State<T> 不再要求 T: Clone — 只需 T: 'static。
多 Data 参数
Handler 可以接受多个 Data<T> 参数,从二进制负载中按顺序反序列化:
#![allow(unused)]
fn main() {
#[derive(AFastDeserialize, Tag)]
#[tag("Pagination")]
struct PageRequest { page: i64, size: i64 }
#[derive(AFastDeserialize, Tag)]
#[tag("Filter")]
struct FilterRequest { keyword: String, status: i32 }
#[handler(desc("Search users"))]
async fn search_users(
page: Data<PageRequest>,
filter: Data<FilterRequest>,
) -> Result<PageResponse> {
// ...
}
}
生成的 TypeScript 客户端方法签名:
async searchUsers(page: PageRequest, filter: FilterRequest): Promise<PageResponse>
自定义错误类型
afast::Result<T> 默认错误类型为 Error,但所有 handler 宏均支持自定义错误类型。实现 AFastError trait 即可:
#![allow(unused)]
fn main() {
use afast::AFastError;
enum AppError {
NotFound { resource: String },
Forbidden { reason: String },
}
impl AFastError for AppError {
fn code(&self) -> i64 {
match self {
AppError::NotFound { .. } => 404,
AppError::Forbidden { .. } => 403,
}
}
fn message(&self) -> String {
match self {
AppError::NotFound { resource } => format!("{} not found", resource),
AppError::Forbidden { reason } => reason.clone(),
}
}
}
}
然后在 handler 中使用 afast::Result<T, AppError>:
#![allow(unused)]
fn main() {
#[handler(desc("Get user"))]
async fn get_user(id: Data<UserId>) -> afast::Result<UserInfo, AppError> {
let user = find_user(&id).ok_or(AppError::NotFound { resource: "user".into() })?;
Ok(user)
}
}
适用于所有宏:#[handler]、#[get]/#[post]/#[put]/#[delete]、#[ws]、#[sse]。
使用 afast::Result<T>(不指定第二个参数)等价于 Result<T, Error>,完全向后兼容。
提取器类型
| 提取器 | 描述 | 协议 |
|---|---|---|
State<T> | 从 StateMap 按类型注入共享状态 (T: ’static, 零拷贝 &’static T) | 所有 |
Ctx<T> | 注入钩子设置的请求上下文数据 (T: Clone) | 所有 |
Data<T> | 从二进制负载反序列化请求体 | HTTP/WS/TCP |
Custom<T> | 反序列化客户端自定义上下文(如认证令牌) | HTTP/WS/TCP |
Receiver | 接收来自客户端的二进制消息(长连接) | WS/TCP |
Sender | 向客户端发送二进制消息(长连接) | WS/TCP |
Query<T> | 从 URL 查询字符串反序列化(需要 ordinary-http) | HTTP |
Param<T> | 从路由路径参数 (:id) 反序列化(需要 ordinary-http) | HTTP |
Body<T> | 从 HTTP JSON 请求体反序列化(需要 ordinary-http) | HTTP |
Header<T> | 从 HTTP 请求头反序列化(需要 ordinary-http) | HTTP |
FullPath | 获取完整请求路径,如 /users/123(需要 ordinary-http) | HTTP |
服务与嵌套
service! 宏通过 group 构建 handler 树,实现命名空间管理:
#![allow(unused)]
fn main() {
let api_svc = service!("api", "User API" => {
h(health),
group("user" => {
h(list_users),
h(get_user),
group("posts" => {
h(list_posts),
}),
}),
group("chat" => {
h(chat), // 使用 Receiver/Sender 的持久连接
}),
});
}
客户端命名空间路径变为 api.user.list_users、api.chat.chat 等。
在 group 内可以混合使用二进制和 ordinary HTTP 路由:
#![allow(unused)]
fn main() {
group("user" => {
h(get_user), // 二进制 handler
get(":id", get_user_by_id), // GET /user/:id
post("", create_user), // POST /user
delete(":id", delete_user), // DELETE /user/:id
}),
}
同名服务合并
注册多个同名服务时,后续的 handler 和路由会自动合并到第一个服务中:
#![allow(unused)]
fn main() {
let user_svc = service!("api", "User API" => {
h(list_users),
h(create_user),
});
let user_extra_svc = service!("api" => {
h(delete_user),
get(":id", get_user_http),
});
let app = AFast::new()
.service(user_svc)
.service(user_extra_svc); // 合并到 "api"
}
空名称服务
名称为空字符串 ("") 的服务注册的 handler 可通过二进制协议调用,但会从客户端代码生成和 API 文档中排除:
#![allow(unused)]
fn main() {
let internal_svc = service!("", "Internal" => {
h(debug_info),
get("ping", ping),
});
}
Catch-all 路由
使用 * 或 *name 语法注册 catch-all 路由,捕获所有未匹配其他路由的请求:
#![allow(unused)]
fn main() {
let svc = service!("api" => {
get("users/:id", get_user), // 精确匹配优先
get("*", catch_all_get), // 捕获其余所有 GET
post("*path", catch_all_post), // 捕获其余所有 POST,路径存入 "path"
});
}
匹配优先级(从高到低):
- 精确路由(如
/users/list) - 参数路由(如
/users/:id) - Catch-all 路由(
*或*name)
即使 catch-all 最先注册,具体路由仍然优先匹配。内置端点(/_api、/_ws、/code、/doc)不会被 catch-all 拦截。
Catch-all 捕获的路径可通过 Param 提取器获取:
#![allow(unused)]
fn main() {
use std::collections::HashMap;
use afast::{Param, Json, FullPath};
#[get(desc("Catch-all handler"))]
async fn catch_all_get(
path: FullPath,
Param(params): Param<HashMap<String, String>>,
) -> Json<serde_json::Value> {
let rest = params.get("*").unwrap(); // 或 params.get("path") 如果写的是 *path
Json(serde_json::json!({ "full_path": path.0, "remaining": rest }))
}
}
类型标签
#[derive(Tag)] 为结构体和枚举生成运行时类型元数据。代码生成器通过 FieldMeta.structure 函数指针递归发现嵌套类型:
#![allow(unused)]
fn main() {
use afast::Tag;
#[derive(Tag)]
#[tag("User role")]
enum Role {
Admin,
User { level: i32 },
Guest { expires_at: i64 },
Custom(String),
}
#[derive(Tag)]
#[tag("User info")]
struct User {
name: String,
role: Role, // 自动递归发现 Role 的字段
tags: Vec<String>, // Vec 元素类型自动展开
avatar: Option<Vec<u8>>,
}
}
验证规则
| 规则 | 示例 | 描述 |
|---|---|---|
gt(value, code, "msg") | #[afast(gt(0, 400, "must > 0"))] | 大于 |
gte(value, code, "msg") | #[afast(gte(1, 400, "must >= 1"))] | 大于等于 |
lt(value, code, "msg") | #[afast(lt(100, 400, "must < 100"))] | 小于 |
lte(value, code, "msg") | #[afast(lte(99, 400, "must <= 99"))] | 小于等于 |
len(min, max, code, "msg") | #[afast(len(1, 20, 400, "len 1-20"))] | 长度约束 |
of(["a","b"], code, "msg") | #[afast(of(["a","b"], 400, "a or b"))] | 枚举值 |
条件序列化 (Marker)
当启用 marker Feature 时,AFast::marker() 设置一个全局标记字符串(默认 "afast"),传递给 afastdata 的 to_bytes_with / from_bytes_with。使用 #[afast(skip_with("marker"))] 标注的字段会根据当前激活的标记在序列化/反序列化时被条件跳过。
跳过模式
#[afast(skip)]— 字段始终跳过,永不序列化/反序列化。必须有Default实现或初始化函数。#[afast(skip_with("marker"))]— 当标记匹配时跳过;否则正常序列化。
标记会递归传播到嵌套类型(Vec<T>、Option<T> 等)。
生成的客户端代码(TS/JS/KT/RS)和 API 文档会自动排除被跳过的字段。
示例
#![allow(unused)]
fn main() {
#[derive(AFastSerialize, AFastDeserialize, Tag)]
#[tag("User info")]
struct User {
name: String,
#[afast(skip)]
internal_secret: String, // 始终跳过
#[afast(skip_with("afast"))]
internal_note: String, // 当 marker 为 "afast" 时跳过
}
let app = AFast::new()
.marker("afast") // 设置标记;默认已经是 "afast"
.service(svc)
.http("0.0.0.0:5000");
}
不启用 marker Feature 时,serialize / deserialize 使用普通的 to_bytes / from_bytes,所有字段始终包含。但 #[afast(skip)] 字段仍会从生成的客户端代码中排除。
速率限制
启用 rate-limit Feature 可为 handler 应用命名的速率限制策略。支持 HTTP、WebSocket 和 TCP 传输层。
配置
#![allow(unused)]
fn main() {
use afast::{RateLimitConfig, RateLimitPolicy, RateLimitKey, Algorithm};
let app = AFast::new()
.rate_limit(
RateLimitConfig::new()
.policy(RateLimitPolicy {
id: "login".into(),
max_requests: 5,
window_secs: 60,
key: RateLimitKey::Ip,
algorithm: Algorithm::SlidingWindow,
})
.default_policy("global")
.policy(RateLimitPolicy {
id: "global".into(),
max_requests: 100,
window_secs: 1,
key: RateLimitKey::Ip,
algorithm: Algorithm::SlidingWindow,
}),
)
.service(svc)
.http("0.0.0.0:5000");
}
绑定 Handler
#![allow(unused)]
fn main() {
#[handler(rate_limit("login"), desc("User login"))]
async fn login(
state: State<AppState>,
req: Data<LoginRequest>,
) -> Result<LoginResponse> {
// ...
}
}
没有 rate_limit 的 handler 自动使用 default_policy。如果未设置默认策略,则不受速率限制。
速率限制键
| 键 | 描述 | HTTP | WebSocket | TCP |
|---|---|---|---|---|
Ip | 客户端 IP(支持 X-Forwarded-For) | ✅ | ✅ | ✅ |
Header("name") | HTTP 头值(如 API Key) | ✅ | ✅ (握手时缓存) | ⏭ 跳过 |
Connection | 按连接(WS/TCP 消息速率) | ⏭ 跳过 | ✅ | ✅ |
Global | 共享全局计数器 | ✅ | ✅ | ✅ |
存储后端
默认的 InMemoryStore 在进程内存中保存计数器。实现 RateLimitStore 可使用自定义后端(如 Redis):
#![allow(unused)]
fn main() {
use afast::RateLimitStore;
struct RedisStore { /* ... */ }
impl RateLimitStore for RedisStore {
fn incr<'a>(&'a self, key: &'a str, ttl_secs: u64)
-> Pin<Box<dyn Future<Output = u64> + Send + 'a>> { /* INCR + EXPIRE */ }
fn get<'a>(&'a self, key: &'a str)
-> Pin<Box<dyn Future<Output = u64> + Send + 'a>> { /* GET */ }
fn set<'a>(&'a self, key: &'a str, value: u64, ttl_secs: u64)
-> Pin<Box<dyn Future<Output = ()> + Send + 'a>> { /* SET + EXPIRE */ }
fn delete<'a>(&'a self, key: &'a str)
-> Pin<Box<dyn Future<Output = ()> + Send + 'a>> { /* DEL */ }
}
}
拒绝响应
- HTTP: 状态码
429 Too Many Requests,响应体:{"code":-90012,"message":"Too many requests"} - WebSocket / TCP: 错误帧,错误码
-90012
可通过 RateLimitConfig::rejected_code() 和 rejected_message() 自定义。
生命周期钩子
启用 hook Feature 可拦截请求生命周期事件,用于可观测性、链路追踪、日志记录或自定义中间件。
快速示例
#![allow(unused)]
fn main() {
use afast::hook::{Hook, RequestContext, RequestGuard, ConnectionGuard};
struct LoggingHook;
impl Hook for LoggingHook {
fn before_request(&self, ctx: &RequestContext) -> Option<Box<dyn RequestGuard>> {
println!("→ {} ({})", ctx.handler_name, ctx.transport);
Some(Box::new(std::time::Instant::now()))
}
fn on_connect(&self, ctx: &RequestContext) -> Option<Box<dyn ConnectionGuard>> {
println!("↕ connect: {} ({})", ctx.handler_name, ctx.transport);
Some(Box::new(()))
}
}
impl RequestGuard for std::time::Instant {
fn on_response(&mut self, ctx: &RequestContext, _resp: &[u8]) {
println!("← {} OK ({:?})", ctx.handler_name, self.elapsed());
}
fn on_error(&mut self, ctx: &RequestContext, err: &afast::Error) {
println!("✗ {} error: {}", ctx.handler_name, err);
}
}
impl ConnectionGuard for () {
fn on_disconnect(&mut self, ctx: &RequestContext) {
println!("✕ disconnect: {} ({})", ctx.handler_name, ctx.transport);
}
}
}
钩子 Trait
Hook — 入口点
#![allow(unused)]
fn main() {
pub trait Hook: Send + Sync + 'static {
/// Called before each request for request-response interfaces
/// (HTTP binary, WS binary, TCP binary, ordinary HTTP).
/// Return a `RequestGuard` to observe the response.
fn before_request(&self, ctx: &RequestContext) -> Option<Box<dyn RequestGuard>> { None }
/// Called when a connection is established for connection-oriented interfaces
/// (WS long, TCP long, ordinary WS, SSE).
/// Return a `ConnectionGuard` to observe disconnection.
fn on_connect(&self, ctx: &RequestContext) -> Option<Box<dyn ConnectionGuard>> { None }
}
}
RequestGuard — 每请求观察者
#![allow(unused)]
fn main() {
pub trait RequestGuard: Send + 'static {
/// Called when the handler returns Ok.
fn on_response(&mut self, ctx: &RequestContext, response: &[u8]) {}
/// Called when the handler returns Err.
fn on_error(&mut self, ctx: &RequestContext, error: &afast::Error) {}
}
}
ConnectionGuard — 长连接观察者
#![allow(unused)]
fn main() {
pub trait ConnectionGuard: Send + 'static {
/// Called when the connection is closed.
fn on_disconnect(&mut self, ctx: &RequestContext) {}
}
}
全局钩子和服务钩子
#![allow(unused)]
fn main() {
let app = AFast::new()
.hook(LoggingHook) // 全局:所有 handler
.service(
service!("api" => { h(handler) })
.hook(ApiSpecificHook) // 服务级:仅该服务的 handler
);
}
- 全局钩子对所有服务中的每个 handler 运行。
- 服务钩子仅对该服务中的 handler 运行。
- 两者始终执行 — 不会互相替代。
- 执行顺序:先全局,后服务(洋葱模型)。
按传输层的钩子生命周期
钩子按接口类型分为两类:
before_request(请求-响应):HTTP 二进制、WS 二进制、TCP 二进制、ordinary HTTP。on_connect(面向连接):WS 长连接、TCP 长连接、ordinary WS、SSE。
二进制协议 (HTTP POST /_api、WS /_ws、TCP)
普通 handler(请求-响应):
before_request → handler → on_response / on_error
长连接 handler (call_stream):
on_connect → handler → on_disconnect
Ordinary HTTP (ordinary-http)
before_request → handler → on_response / on_error
ordinary HTTP 不会调用 on_connect / on_disconnect(无状态请求-响应)。
Ordinary WebSocket (ordinary-ws)
on_connect → handler → on_disconnect
on_connect:在 WebSocket 握手完成后触发。on_disconnect:在 handler 返回且转发任务清理完成后触发。
Ordinary SSE (ordinary-sse)
on_connect → handler (spawned) → on_disconnect
on_connect:在 SSE 响应发送和 handler 生成之前触发。on_disconnect:在 handler 任务完成后触发。
请求上下文集成
钩子可以通过 RequestContext 上的 ctx 字段读写请求数据。然后 handler 可以通过 Ctx<T> 提取器访问这些数据。
#![allow(unused)]
fn main() {
use afast::hook::{Hook, RequestContext};
#[derive(Clone)]
struct RequestId(pub String);
struct CtxHook;
impl Hook for CtxHook {
fn before_request(&self, ctx: &RequestContext) -> Option<Box<dyn RequestGuard>> {
// 写入请求上下文
ctx.ctx.insert(RequestId(format!("req-{:08x}", /* ... */)));
None
}
}
}
Handler 自动获取:
#![allow(unused)]
fn main() {
#[handler(desc("..."))]
async fn my_handler(ctx: afast::Ctx<RequestId>) -> afast::Result<()> {
println!("request_id = {}", ctx.0 .0);
Ok(())
}
}
同一个请求的所有钩子和 handler 共享同一个上下文。对于长连接 handler(WS/TCP),上下文在整个连接期间持续存在。
详见 请求上下文 (Ctx)。
访问自定义属性
RequestContext 通过 ctx.attrs 暴露 handler 的自定义属性:
#![allow(unused)]
fn main() {
impl Hook for DeprecationHook {
fn before_request(&self, ctx: &RequestContext) -> Option<Box<dyn RequestGuard>> {
for attr in ctx.attrs {
match attr.key {
"deprecated" => eprintln!("WARNING: {} is deprecated", ctx.handler_name),
"tag" => {
if let AttrValue::Str(v) = attr.value {
eprintln!("tag: {}", v);
}
}
_ => {}
}
}
None
}
}
}
获取客户端 IP
RequestContext 提供两个字段用于获取客户端 IP:
#![allow(unused)]
fn main() {
impl Hook for IpHook {
fn before_request(&self, ctx: &RequestContext) -> Option<Box<dyn RequestGuard>> {
// TCP 连接的对端 IP
let peer_ip = &ctx.client_ip;
// 真实客户端 IP(从 X-Forwarded-For / X-Real-IP 头获取)
let real_ip = ctx.forwarded_for.as_deref().unwrap_or(&ctx.client_ip);
println!("client: {}, real: {}", peer_ip, real_ip);
None
}
}
}
client_ip:所有传输层均有值forwarded_for:仅 HTTP/WS 有值,TCP 为None
钩子键 — 路由匹配
钩子通过 "service_name:route_path" 匹配,而不是通过 handler 函数名。这避免了同一服务中不同 group 内出现相同函数名时的冲突:
#![allow(unused)]
fn main() {
service!("admin" => {
group("users" => {
get("info", get_info), // key: "admin:/users/info"
}),
group("posts" => {
get("info", get_info), // key: "admin:/posts/info" — 无冲突!
}),
})
}
对于合并的服务(同名服务多次注册),钩子条目会自动去重。
RequestContext 字段
| 字段 | 类型 | 描述 |
|---|---|---|
handler_name | &'static str | Handler 函数名 |
handler_desc | &'static str | #[handler(desc(...))] 中的描述 |
transport | &'static str | "http-binary"、"http"、"ws-binary"、"ws"、"tcp" 或 "sse" |
is_binary | bool | 是否为二进制协议 handler |
method | &'static str | HTTP 方法 ("GET"、"POST" 等),非 HTTP 时为空 |
long_connection | bool | 是否为长连接 handler (Receiver/Sender) |
handler_id | usize | Handler 在二进制分发表中的偏移量(ordinary 路由为 0) |
state | Arc<StateMap> | 共享应用状态 |
ctx | RequestCtx | 请求上下文容器(钩子写入,handler 通过 Ctx<T> 读取) |
attrs | &'static [Attr] | #[handler(...)] 中的自定义 handler 属性 |
支持的提取器
所有提取器在所有传输层上均可工作:
| 提取器 | HTTP | WS | SSE | TCP |
|---|---|---|---|---|
State<T> | ✅ | ✅ | ✅ | ✅ |
Query<T> | ✅ | ✅ | ✅ | — |
Param<T> | ✅ | ✅ | ✅ | — |
Header<T> | ✅ | ✅ | ✅ | — |
Body<T> | ✅ | — | — | — |
Custom<T> | — | — | — | ✅ |
Data | — | — | — | ✅ |
WsSender | — | ✅ | — | — |
WsReceiver | — | ✅ | — | — |
SseSender | — | — | ✅ | — |
Sender | — | — | — | ✅ |
Receiver | — | — | — | ✅ |
服务端示例输出
[hook] ↕ connect: chat_ws (ws) ← on_connect
[check-svc] ▶ chat_ws ← 服务钩子
[ws-chat] client joined room: test ← handler 运行
[ws-chat] client left room: test ← handler 返回
[hook] ✕ disconnect: chat_ws (ws) ← on_disconnect
[check-svc] ◀ chat_ws done ← 服务钩子完成
请求上下文 (Ctx)
Ctx<T> 提取器提供按请求(或按连接)的类型化数据,在整个 handler 生命周期中流转。与应用全局的 State<T> 不同,Ctx<T> 的作用域限于单个请求。
核心概念
State<T> | Ctx<T> | |
|---|---|---|
| 作用域 | 应用全局 | 按请求 / 按连接 |
| 存储 | StateMap(启动时设置一次) | RequestCtx(每个请求创建) |
| 生命周期 | 整个应用 | 请求开始 → 完全结束 |
| 用途 | 数据库连接池、配置 | 请求 ID、认证信息、计时 |
| 设置方 | AFast::state() | 钩子 (before_request、on_connect) |
工作原理
请求到达
│
▼
RequestCtx::new() ← 框架创建空上下文
│
▼
Hook: before_request() ← 钩子插入值: ctx.ctx.insert(RequestId(...))
│
▼
Handler 执行 ← 框架提取: Ctx<RequestId> 从上下文中
│
▼
Hook: on_response() ← 钩子读取值: ctx.ctx.get::<RequestId>()
│
▼
RequestCtx 释放 ← 所有值被释放
对于长连接 handler(WS/TCP),RequestCtx 在整个连接期间持续存在:
连接建立
│
▼
RequestCtx::new() ← 创建一次
│
▼
Hook: on_connect() ← 钩子插入连接级数据
│
▼
消息 1: handler 执行 ← 读取 Ctx<T>
消息 2: handler 执行 ← 相同的 Ctx<T>(消息间共享)
...
│
▼
Hook: on_disconnect() ← 钩子读取最终状态
│
▼
RequestCtx 释放
快速示例
1. 定义上下文数据
#![allow(unused)]
fn main() {
#[derive(Clone)]
pub struct RequestInfo {
pub request_id: String,
pub started_at: std::time::Instant,
}
}
类型必须实现 Clone + Send + Sync + 'static(用于通过 Ctx<T> 提取)。
2. 创建插入数据的钩子
#![allow(unused)]
fn main() {
use afast::hook::{Hook, RequestContext, RequestGuard};
struct CtxHook;
impl Hook for CtxHook {
fn before_request(&self, ctx: &RequestContext) -> Option<Box<dyn RequestGuard>> {
ctx.ctx.insert(RequestInfo {
request_id: format!("req-{:08x}", /* ... */),
started_at: std::time::Instant::now(),
});
None // 如果只是写入上下文,不需要 guard
}
}
}
3. 在 handler 中使用
#![allow(unused)]
fn main() {
use afast::{Ctx, handler};
#[handler(desc("My handler"))]
async fn my_handler(ctx: Ctx<RequestInfo>) -> afast::Result<MyResp> {
let elapsed = ctx.0.started_at.elapsed();
println!("request {} took {:?}", ctx.0.request_id, elapsed);
// ...
}
}
ctx.0 给你内部的 RequestInfo 值。
支持的 Handler 类型
Ctx<T> 在所有 handler 类型中均可使用:
| Handler 类型 | 宏 | 示例 |
|---|---|---|
| 二进制协议 | #[handler] | async fn h(ctx: Ctx<Info>) -> Result<T> |
| HTTP ordinary | #[get] / #[post] 等 | async fn h(ctx: Ctx<Info>) -> HttpResult<Json<T>> |
| WebSocket | #[ws] | async fn h(ctx: Ctx<Info>, sender: WsSender) -> Result<()> |
| SSE | #[sse] | async fn h(ctx: Ctx<Info>, sender: SseSender) -> Result<()> |
| 长连接 | #[handler] + Receiver/Sender | async fn h(ctx: Ctx<Info>, rx: Receiver, tx: Sender) |
Ctx<T> 不参与二进制/ordinary 互斥检查,因此可以与任何其他提取器组合使用。
参数位置
Ctx<T> 可以放在参数列表的任意位置。按惯例放在第一位:
#![allow(unused)]
fn main() {
#[handler(desc("..."))]
async fn my_handler(
ctx: Ctx<RequestInfo>, // ← 上下文在前
state: State<AppState>, // ← 然后是 state
data: Data<MyReq>, // ← 最后是 data
) -> afast::Result<MyResp> {
// ...
}
}
在钩子中读写
钩子通过 RequestContext::ctx 与上下文交互:
#![allow(unused)]
fn main() {
impl Hook for MyHook {
fn before_request(&self, ctx: &RequestContext) -> Option<Box<dyn RequestGuard>> {
// 写入
ctx.ctx.insert(MyData { value: 42 });
// 读取(如果其他钩子之前写入了数据)
// ctx.ctx.get::<OtherData>()
Some(Box::new(MyGuard))
}
}
impl RequestGuard for MyGuard {
fn on_response(&mut self, ctx: &RequestContext, _resp: &[u8]) {
// 读取 handler 或之前钩子写入的数据
if let Some(data) = ctx.ctx.get::<MyData>() {
println!("value was {}", data.value);
}
}
}
}
获取客户端 IP
RequestContext 提供两个字段用于获取客户端 IP 地址:
| 字段 | 类型 | 说明 |
|---|---|---|
client_ip | String | TCP 连接的对端 IP(peer_addr) |
forwarded_for | Option<String> | 从 X-Forwarded-For / X-Real-IP 头获取的真实 IP |
#![allow(unused)]
fn main() {
impl Hook for MyHook {
fn before_request(&self, ctx: &RequestContext) -> Option<Box<dyn RequestGuard>> {
// 直接连接的 IP(可能是代理 IP)
let ip = &ctx.client_ip;
// 真实客户端 IP(仅 HTTP/WS 有值)
let real_ip = ctx.forwarded_for.as_deref().unwrap_or(&ctx.client_ip);
println!("client: {} (real: {})", ip, real_ip);
None
}
}
}
说明:
client_ip在所有传输层(HTTP、WS、TCP、SSE)均有值forwarded_for仅在 HTTP 和 WebSocket 传输层有值,TCP 始终为None- 当处于反向代理后面时,建议优先使用
forwarded_for
API 参考
RequestCtx — 容器
#![allow(unused)]
fn main() {
// 创建空上下文
let ctx = RequestCtx::new();
// 插入值(按类型为键)
ctx.insert(my_value);
// 获取克隆的值
let val: Option<MyType> = ctx.get::<MyType>();
// 克隆(廉价 — 共享 Arc)
let ctx2 = ctx.clone();
}
Ctx<T> — 提取器
#![allow(unused)]
fn main() {
pub struct Ctx<T>(pub T);
// 访问内部值
let inner: T = my_ctx.0;
}
性能
RequestCtx::new()分配单个Arc<RwLock<HashMap>>— 非常廉价。insert()和get()获取RwLock— 在典型场景(先顺序写入再读取)下无竞争,开销可忽略。RequestCtx::clone()是Arc克隆 — O(1)。- 对于大型上下文值,用
Arc<T>包装可使get()的克隆更廉价。 - 不使用
Ctx<T>的 handler 零提取成本 — 空上下文被创建但不会被读取。
传输层
AFast 支持多种传输层,可以在不同端口上同时运行。
HTTP
HTTP 服务端点:
| 方法 | 路径 | 描述 |
|---|---|---|
| POST | /_api | 二进制 handler 分发 |
| GET | /_ws | WebSocket 升级(合并模式) |
| GET | /code/{service}/{lang} | 按需代码生成(需要 code) |
| GET | /doc | API 文档索引(需要 doc) |
| GET | /doc/{service} | 服务特定文档(需要 doc) |
| * | ordinary 路由 | RESTful 端点(需要 ordinary-http) |
| GET | /path/:param | ordinary-ws 路由的 WebSocket 升级(需要 ordinary-ws) |
| GET | /path | ordinary-sse 路由的 SSE 流(需要 ordinary-sse) |
HTTP 响应格式:
- 成功:
[0u8][0i64][data: bytes] - 错误:
[1u8][code: i64][message: bytes]
WebSocket
WS 帧格式:
Request: [req_id: SeqId][handler_id: u32][len: Len][payload]
Push: [0: SeqId][conn_id: u32][len: Len][payload]
Heartbeat:[0xFFFFFFFF: SeqId][len: Len][conn_id1: u32]...
SeqId 类型由 seq64 Feature 控制(i32 或 i64),Len 类型由 len64 Feature 控制(u32 或 u64)。
TCP
TCP 使用 4 字节大端序长度前缀帧,每帧包含完整的二进制负载。适用于嵌入式设备或原生 TCP 场景。
HTTP + WS 端口合并
当 ws_addr 和 http_addr 设置为相同地址时,AFast 通过 HTTP Upgrade 将 WebSocket 合并到 HTTP 服务器中:
#![allow(unused)]
fn main() {
// 相同端口同时用于 HTTP 和 WebSocket
let app = AFast::new()
.service(svc)
.ws("0.0.0.0:5000")
.http("0.0.0.0:5000"); // 相同地址,自动合并
}
TLS / HTTPS
AFast 支持基于 rustls 的 TLS/HTTPS,ALPN 协商 HTTP/2。
基本用法
#![allow(unused)]
fn main() {
let app = AFast::new()
.service(svc)
.https("0.0.0.0:5443", "./cert.pem", "./key.pem", None);
}
优雅降级
如果证书文件不存在,服务器自动降级为普通 HTTP:
afast: TLS cert files not found, starting without encryption: [::]:5443
热重载证书
通过 broadcast channel 在运行时重载证书,无需重启:
#![allow(unused)]
fn main() {
let (reload_tx, reload_rx) = tokio::sync::broadcast::channel(1);
let app = AFast::new()
.https("0.0.0.0:5443", "./cert.pem", "./key.pem", Some(reload_rx));
// 使用原始路径重载
reload_tx.send(None).unwrap();
// 使用新路径重载
reload_tx.send(Some(TlsReloadMessage {
cert_path: "/new/cert.pem".into(),
key_path: "/new/key.pem".into(),
})).unwrap();
}
Ordinary HTTP (REST)
使用 ordinary-http,在 service! 宏内使用 get/post/put/patch/delete 定义 RESTful 路由:
#![allow(unused)]
fn main() {
use afast::{get, Query, Param, Body, Header, Json, HttpResult};
#[derive(Deserialize)]
struct UserQuery { page: i64, size: i64 }
#[derive(Serialize)]
struct UserResponse { id: i64, name: String }
#[get(":id")]
async fn get_user(
state: State<AppState>,
param: Param<UserPath>,
query: Query<UserQuery>,
) -> HttpResult<Json<UserResponse>> {
Ok(Json(UserResponse { id: param.id, name: format!("User {}", param.id) }))
}
}
响应类型
| 类型 | HTTP 状态码 | Content-Type |
|---|---|---|
Json<T> | 200 | application/json |
Text | 200 | text/plain |
Html | 200 | text/html |
File | 200 | 自定义 + Content-Disposition: attachment |
Status(code) | 自定义 | — |
Redirect::temporary(url) / Redirect::permanent(url) | 302 / 301 | Location 头 |
CORS
通过 AFast::cors() 启用 CORS(跨源资源共享),需要 http feature。所有 HTTP 端点——包括二进制 /_api、普通 HTTP 路由以及代码/文档端点——都会自动包含 CORS 头:
#![allow(unused)]
fn main() {
use afast::{AFast, CorsConfig};
// 开发环境:允许所有来源
AFast::new()
.cors(CorsConfig::permissive())
.http("0.0.0.0:5000")
.run().await;
// 生产环境:指定来源并启用凭证
AFast::new()
.cors(
CorsConfig::new(vec!["https://example.com", "https://app.example.com"])
.allow_credentials(true)
.max_age(7200)
)
.http("0.0.0.0:5000")
.run().await;
}
服务器会自动:
- 响应
OPTIONS预检请求,返回204 No Content - 在每个 HTTP 响应中注入
Access-Control-Allow-Origin - 为预检请求设置
Access-Control-Allow-Methods、Access-Control-Allow-Headers和Access-Control-Max-Age
安全头
每个 HTTP 响应默认包含以下安全头:
| 头部 | 值 |
|---|---|
x-content-type-options | nosniff |
x-frame-options | DENY |
content-security-policy | default-src 'self' |
可通过 AFast::security_headers() 覆盖:
#![allow(unused)]
fn main() {
AFast::new()
.security_headers(vec![
("x-content-type-options", "nosniff"),
("x-frame-options", "SAMEORIGIN"),
("content-security-policy", "default-src 'self'; script-src 'self' 'unsafe-inline'"),
])
.http("0.0.0.0:5000")
.run().await;
}
Ordinary WebSocket
使用 ordinary-ws,在 service! 宏内使用 ws 定义 WebSocket 路由。这些路由使用文本/JSON 帧而非二进制协议:
#![allow(unused)]
fn main() {
use afast::{ws, WsSender, WsReceiver, WsParam};
#[derive(Deserialize)]
struct ChatParam { room: String }
#[ws(desc("Chat room"))]
async fn chat_ws(
param: WsParam<ChatParam>,
sender: WsSender,
mut receiver: WsReceiver,
) -> afast::Result<()> {
while let Some(msg) = receiver.recv().await {
if let afast::WsMessage::Text(text) = msg {
sender.send_text(format!("[{}] {}", param.0.room, text)).await?;
}
}
Ok(())
}
let svc = service!("chat" => {
ws("/chat/:room", chat_ws),
});
}
连接: ws://host:port/chat/general?token=abc
TS/JS 客户端会自动生成平台感知的 WebSocket 连接方法,兼容浏览器、UniApp 和微信小程序。
Server-Sent Events (SSE)
需要 ordinary-sse Feature。使用 sse() 注册 SSE 路由:
#![allow(unused)]
fn main() {
use afast::{SseSender, Query};
#[derive(Deserialize)]
struct SseQuery { room: Option<String> }
#[afast::sse(desc("SSE event stream"))]
async fn notifications(
query: Query<SseQuery>,
sender: SseSender,
) -> afast::Result<()> {
let room = query.0.room.unwrap_or_default();
// 发送命名事件
sender.send_event("connected", &serde_json::json!({"room": room})).await?;
// 发送数据事件(自动序列化为 JSON)
let mut count = 0u64;
loop {
tokio::time::sleep(Duration::from_secs(1)).await;
count += 1;
if sender.send_event("tick", &serde_json::json!({"count": count})).await.is_err() {
break; // 客户端断开连接
}
}
Ok(())
}
let svc = service!("events" => {
sse("/notifications", notifications),
});
}
连接: GET http://host:port/notifications?room=general
响应头:
Content-Type: text/event-stream; charset=utf-8Cache-Control: no-cacheConnection: keep-aliveTransfer-Encoding: chunked
线路格式:
event: connected
data: {"room":"general"}
event: tick
data: {"count":1}
SseSender 方法
| 方法 | 描述 |
|---|---|
send<T: Serialize>(data) | 发送 data: 事件,值为 JSON 序列化 |
send_event<T: Serialize>(event, data) | 发送命名事件,值为 JSON 序列化 |
SseEvent 字段
| 字段 | 类型 | 线路格式 |
|---|---|---|
event | Option<&str> | event: name\n |
data | String | data: ...\n |
id | Option<&str> | id: ...\n |
retry | Option<u64> | retry: ...\n |
客户端代码生成
- TS/JS: 生成基于
EventSource的方法 - KT (OkHttp): 使用
okhttp3.sse.EventSource - KT (非 OkHttp): 使用
java.net.http.HttpClient配合BodyHandlers.ofLines()
// TS/JS 生成的客户端
const es = client.apis.sse_stream({ room: "general" });
es.addEventListener("connected", (e) => console.log("Connected:", e.data));
es.addEventListener("tick", (e) => console.log("Tick:", JSON.parse(e.data)));
es.close();
长连接
使用 Receiver/Sender 的 handler 会被自动检测为长连接模式:
#![allow(unused)]
fn main() {
#[handler(desc("Chat"))]
async fn chat(
state: State<AppState>,
auth: Custom<Auth>,
mut receiver: Receiver,
sender: Sender,
) -> Result<()> {
sender.send(b"Welcome!".to_vec()).await?;
while let Some(msg) = receiver.recv().await {
sender.send(msg).await?; // 回显
}
Ok(())
}
}
生成的客户端为长连接 handler 返回 Socket 对象,提供 send()/close() 方法和 onMessage 回调。
文件上传
AFast 通过 multer crate 支持 multipart/form-data 文件上传。提供两种提取模式:
原始 Multipart (Multipart)
直接访问 multipart 流:
#![allow(unused)]
fn main() {
use afast::{post, Multipart, HttpResult, Json, Tag};
use serde::Serialize;
#[derive(Serialize, Tag)]
#[tag("上传结果")]
struct UploadResult {
filename: String,
size: usize,
}
#[post(desc("上传文件"))]
async fn upload(mut form: Multipart) -> HttpResult<Json<UploadResult>> {
let field = form.next_field().await?.ok_or_else(|| {
afast::Error::custom(400, "未提供文件")
})?;
let filename = field.file_name().unwrap_or("unknown").to_string();
let data = field.bytes().await?;
Ok(Json(UploadResult { filename, size: data.len() }))
}
}
类型化提取 (MultipartForm<T>)
使用 #[derive(FromFormData)] 自动提取到结构体:
#![allow(unused)]
fn main() {
use afast::{post, MultipartForm, FileField, HttpResult, Json, Tag, FromFormData};
use serde::Serialize;
#[derive(FromFormData, Tag)]
#[tag("上传表单")]
struct UploadForm {
description: String,
file: FileField,
}
#[derive(Serialize, Tag)]
#[tag("上传结果")]
struct UploadResult {
filename: String,
description: String,
size: usize,
}
#[post(desc("带表单数据的上传"))]
async fn upload_typed(form: MultipartForm<UploadForm>) -> HttpResult<Json<UploadResult>> {
let data = form.0;
Ok(Json(UploadResult {
filename: data.file.filename.unwrap_or_else(|| "unknown".to_string()),
description: data.description,
size: data.file.bytes.len(),
}))
}
}
#[derive(FromFormData)]
该 derive 宏自动实现 FromFormData trait。每个结构体字段名必须与对应的表单字段名匹配。
支持的字段类型
| Rust 类型 | 表单字段 | 说明 |
|---|---|---|
String | 文本字段 | 直接文本值 |
i8/i16/i32/i64/u8/u16/u32/u64/f32/f64 | 文本字段 | 从文本解析 |
bool | 文本字段 | "true"/"false"/"1"/"0" |
FileField | 文件字段 | 收集字节、文件名、内容类型 |
Option<T> | 可选字段 | 缺失时默认为 None |
FileField 结构
#![allow(unused)]
fn main() {
pub struct FileField {
pub name: String, // 表单字段名
pub filename: Option<String>, // 原始文件名
pub content_type: Option<String>, // MIME 类型
pub bytes: Vec<u8>, // 文件内容
}
}
客户端代码生成
TS/JS/KT 代码生成器自动生成基于 FormData 的上传代码:
// 生成的 TypeScript 客户端
const formData = new FormData();
formData.append('description', 'My file');
formData.append('file', new Blob(['content']), 'test.txt');
const result = await client.apis.upload_typed({ body: formData });
使用 curl 测试
# 原始上传
curl -F "file=@test.txt" http://localhost:5001/upload
# 带多个字段的类型化上传
curl -F "description=我的文件" -F "file=@test.txt" http://localhost:5001/upload/typed
代码生成
静态生成(编译时文件输出)
#![allow(unused)]
fn main() {
use afast::{GenerateTarget, Lang, JsTsCallType, RsCallType, NetCallType};
let app = AFast::new()
.service(api_svc)
.generate(vec![
GenerateTarget {
lang: Lang::TS(vec![JsTsCallType::Fetch, JsTsCallType::Ws]),
path: "./code".into(),
debug: false,
},
GenerateTarget {
lang: Lang::RS(vec![RsCallType::TcpAsync]),
path: "./src/bin/client".into(),
debug: true,
},
GenerateTarget {
lang: Lang::CS(vec![NetCallType::Http, NetCallType::Ws, NetCallType::Tcp]),
path: "./client".into(),
debug: true,
},
]);
}
动态生成(HTTP 端点)
GET /code/api/ts?call=fetch,ws
GET /code/api/js?call=fetch,ws
GET /code/pay/kt?call=http,ws,tcp
GET /code/api/rs?call=tcp-async
GET /code/api/cs?call=http,ws,tcp
支持的传输类型
TS/JS
| 值 | API |
|---|---|
fetch | 浏览器 fetch |
ws | 浏览器 WebSocket |
nodetcp | Node.js net |
buntcp | Bun Bun.connect |
unirequest | UniApp uni.request |
uniws | UniApp uni.connectSocket |
wxrequest | 微信小程序 wx.request |
wxws | 微信小程序 wx.connectSocket |
Kotlin
| 值 | API |
|---|---|
http / fetch | java.net.HttpURLConnection |
ws | java.net.http.WebSocket |
tcp | java.net.Socket |
Rust
| 值 | API |
|---|---|
tcp-async | tokio::net::TcpStream (异步) |
tcp-sync | std::net::TcpStream (同步) |
C# / .NET
| 值 | API |
|---|---|
http / fetch | System.Net.Http.HttpClient |
ws | System.Net.WebSockets.ClientWebSocket |
tcp | System.Net.Sockets.TcpClient |
客户端使用
TypeScript / JavaScript
import { ApiClient } from './api';
// 专用 WS 端口
const wsClient = new ApiClient({
host: 'localhost',
port: 3001,
tls: false,
transport: 'ws',
debug: false,
});
await wsClient.apis._ready;
const result = await wsClient.apis.user.list_users({ page: 1, size: 20 });
// HTTP (fetch) 模式
const httpClient = new ApiClient({
host: 'localhost',
port: 5001,
tls: false,
transport: 'fetch',
debug: false,
});
await httpClient.apis._ready;
const users = await httpClient.apis.user.list_users({ page: 1, size: 20 });
Custom 提取器
通过 customs 为 Custom<T> 提取器提供值:
const client = new ApiClient({
host: 'localhost',
port: 5001,
tls: false,
transport: 'fetch',
customs: {
AuthCustom: () => ({ token: 'my-token' }),
},
});
Header 提取器
通过 headers 为 Header<T> 提取器提供值:
const client = new ApiClient({
host: 'localhost',
port: 5001,
tls: false,
transport: 'fetch',
headers: {
AuthHeader: async () => ({ authorization: 'Bearer my-token' }),
},
});
Kotlin
// HTTP 模式
val client = ApiClient(host = "localhost", port = 5001, tls = false)
val users = client.userListUsers(page = 1, size = 20)
// OkHttp 模式(Android 兼容)
val client = ApiClient(host = "localhost", port = 5001, tls = false, callType = KtCallType.OkHttp)
// WebSocket 模式
val wsClient = ApiClient(host = "localhost", port = 3001, tls = false, callType = KtCallType.Ws)
Rust
#![allow(unused)]
fn main() {
// 异步 TCP 客户端
let mut client = AfastSocket::connect("localhost:4001").await?;
let users: ListUsersResp = client.call(1, &req).await?;
// 同步 TCP 客户端
let mut client = AfastSocketSync::connect("localhost:4001")?;
let users: ListUsersResp = client.call(1, &req)?;
}
C# / .NET
// HTTP 模式
await using var client = new ApiClient("localhost", 5001, false, ApiClient.Transport.Http);
var users = await client.Apis.User.ListUsers(new UserListUsersRequest { Page = 1, Size = 20 });
// WebSocket 模式
await using var wsClient = new ApiClient("localhost", 3001, false, ApiClient.Transport.Ws);
var result = await wsClient.Apis.User.ListUsers(new UserListUsersRequest { Page = 1, Size = 20 });
// TCP 模式
await using var tcpClient = new ApiClient("localhost", 4001, false, ApiClient.Transport.Tcp);
var result = await tcpClient.Apis.User.ListUsers(new UserListUsersRequest { Page = 1, Size = 20 });
// Custom 提取器
client.Customs["AuthCustom"] = async () => new AuthCustom { Token = "my-token" };
客户端传输模式在构造时确定。
注意:普通 HTTP 路由(如
#[get]、#[post])仅在fetch/http传输模式下可用。WS/TCP 传输仅支持二进制协议 handler(#[handler])。
客户端缓存
cache(seconds) 属性启用客户端缓存:
#![allow(unused)]
fn main() {
#[handler(desc("List users"), cache(60))]
async fn list_users(...) -> Result<ListUsersResponse> { /* ... */ }
}
生成的客户端:
const users = await client.apis.admin.listUsers({ page: 1, size: 20 });
// 60 秒内,相同参数返回缓存数据
const fresh = await client.apis.admin.listUsers({ page: 1, size: 20 }, true);
// force = true 跳过缓存
关于 TextEncoder / TextDecoder
生成的客户端代码使用 TextEncoder 和 TextDecoder API。这些在 React Native(旧版本)、微信小程序和旧浏览器中不可用。
解决方案:
- Polyfill:
npm install text-encoding+import 'text-encoding' - React Native 0.72+: 内置支持
- 微信/UniApp: 使用
wxrequest/wxws/unirequest/uniws传输类型
二进制协议
类型映射
| Rust 类型 | TS/JS 类型 | Kotlin 类型 |
|---|---|---|
i8 ~ i64, u8 ~ u64, f32, f64 | number | Int/Long/Float/Double |
bool | boolean | Boolean |
String, &str | string | String |
Vec<u8> | Uint8Array | ByteArray |
Option<T> | T | null | T? |
Vec<T> | T[] | List<T> |
| struct | { field: Type } | data class |
| enum | { tag: 'Variant', data: ... } | sealed class |
错误码
系统保留错误码范围为 -90011 到 -90000。用户自定义错误不得使用此范围。
| 常量 | 值 | 描述 |
|---|---|---|
CODE_SIGNAL | -90000 | 操作系统信号(如 Ctrl+C) |
CODE_MSG_TOO_SHORT | -90001 | 消息过短 |
CODE_PAYLOAD_MISMATCH | -90002 | 负载长度不匹配 |
CODE_SERIALIZE | -90003 | 序列化/反序列化错误 |
CODE_STATE_NOT_FOUND | -90004 | State 类型未注册 |
CODE_HANDLER | -90005 | Handler 执行错误 |
CODE_INVALID_PARAM | -90006 | 无效参数 |
CODE_IO | -90007 | I/O 错误 |
CODE_WS | -90008 | WebSocket 错误 |
CODE_HTTP | -90009 | HTTP 错误 |
CODE_TCP | -90010 | TCP 错误 |
CODE_LONG_CONNECTION_NOT_SUPPORTED | -90011 | HTTP 模式不支持长连接 |
CODE_RATE_LIMITED | -90012 | 超出速率限制 |
#![allow(unused)]
fn main() {
// 自定义错误(错误码必须在保留范围之外)
return Err(afast::Error::custom(400, "invalid request parameter"));
}
自定义错误类型
实现 AFastError trait 即可定义自己的错误类型,handler 直接返回 Result<T, MyError>:
#![allow(unused)]
fn main() {
use afast::AFastError;
enum MyError {
NotFound(String),
Unauthorized,
}
impl AFastError for MyError {
fn code(&self) -> i64 {
match self {
MyError::NotFound(_) => 404,
MyError::Unauthorized => 401,
}
}
fn message(&self) -> String {
match self {
MyError::NotFound(name) => format!("{} not found", name),
MyError::Unauthorized => "unauthorized".into(),
}
}
}
#[handler(desc("Get user"))]
async fn get_user(id: Data<UserId>) -> afast::Result<UserInfo, MyError> {
find_user(id).ok_or(MyError::NotFound("user".into()))
}
}
所有 handler 宏(#[handler]、#[get]/#[post]/#[put]/#[delete]、#[ws]、#[sse])均支持自定义错误类型。afast::Result<T> 默认使用 Error,完全向后兼容。
交互式文档
启用 doc Feature 后,访问 http://host:port/doc 可查看交互式 API 文档。
设置
#![allow(unused)]
fn main() {
let app = AFast::new()
.service(svc)
.document(afast::DocConfig::with("My API", "./docs"))
.http("0.0.0.0:5001");
}
GET /doc— 索引页,列出所有服务(名称以_开头的服务会被隐藏)GET /doc/{service}— 服务文档,包含类型定义和在线测试面板- 深色/浅色主题切换
- 静态 HTML 文件写入
./docs目录
功能
二进制 Handler 测试
每个二进制 handler 显示一个表单,包含:
Custom、Data和State参数的输入字段- 发送按钮,将数据序列化为二进制协议
- 响应面板,显示反序列化后的结果
Ordinary HTTP 测试
REST 端点(GET、POST 等)显示:
- 路径参数输入
- 查询参数输入
- JSON 请求体编辑器
- 响应状态码和响应体显示
WebSocket 调试
WS 路由(#[ws])显示调试面板,包含:
- 路径参数输入(从路由模式自动检测)
- 查询参数输入
- 连接/断开按钮
- 消息输入框和发送按钮(Enter 快捷键)
- 实时日志面板,显示发送/接收的消息
面板连接到 HTTP 端口(ordinary WS 路由使用 HTTP 升级)。
SSE 调试
SSE 路由(#[sse])显示调试面板,包含:
- 路径参数输入
- 查询参数输入
- 连接/断开按钮
- 实时事件日志,显示命名事件和数据
配置面板
右上角的设置面板可配置:
- 传输层:
ws(二进制)、fetch(HTTP)、tcp - Host: 服务器主机名(默认:
localhost) - Port: 从服务器配置自动检测
- TLS: 启用安全连接
服务可见性
名称以 _(下划线)开头的服务会从文档索引页隐藏,但仍可通过直接 URL 访问(/doc/_service)。适用于仅内部使用的端点。
项目结构
afast/ — 主框架 crate(核心类型、State、传输层、代码生成)
afast-macros/ — 过程宏(#[handler]、register!、#[derive(Tag)])
example/ — 示例项目(完整用法,包括 HTTP、WS、TCP、文档)
依赖关系
afast→afast-macros、afastdata、tokioafast-macros→syn、quote、proc-macro2- 用户 crate 间接依赖
afastdata-core(由#[derive(Tag)]展开代码引用)
测试
单元测试
cargo test --lib
集成测试
示例项目包含所有支持语言的测试客户端:
1. 启动示例服务器
cargo run -p example --bin example
这将启动:
- HTTP 服务器,端口 5001
- WebSocket 服务器,端口 3001
- TCP 服务器,端口 4001
- API 文档: http://localhost:5001/doc
2. 运行测试客户端
Rust (TCP 传输):
cargo run -p example --bin test_client
JavaScript (Node.js — fetch + ws + nodetcp):
cd client && node test_js.mjs
TypeScript (Bun — fetch + ws + buntcp):
cd client && bun run test_ts.ts
Kotlin (Gradle — HTTP + WS + TCP):
cd client/kt-test && gradle run
Feature 组合
测试框架在各种 Feature 组合下能否正常编译:
cargo check
cargo check --features hook
cargo check --features rate-limit
cargo check --features ordinary-http
cargo check --features ordinary-ws
cargo check --features ordinary-sse
cargo check --features "ordinary-http,ordinary-ws,ordinary-sse,hook,rate-limit"
cargo check --all-features
Clippy
cargo clippy --all-features
许可证
MIT
AFast AI 开发指南
本文档专为 AI 助手设计,帮助 AI 理解如何使用 AFast 框架进行开发。
核心规则
- 始终使用
#[handler]宏 — 不要手动注册路由 - 使用
Result<T>处理错误 — 所有 handler 必须返回Result<T> - 使用
State<T>管理共享状态 — 零拷贝,无需 clone - 使用
Data<T>接收请求体 — 自动从二进制负载反序列化 - 使用
Custom<T>获取认证信息 — 客户端提供的认证上下文 - 使用
Ctx<T>获取请求上下文 — 由 hooks 写入,handler 读取 - 类型必须派生
Tag— 所有请求/响应类型需要#[derive(Tag)]和#[tag("描述")] - HTTP handler 使用
Header<T>做认证 — 不要用Custom<T>
Handler 签名模式
重要:参数必须使用解构语法 afast::X(name): afast::X<Type>
二进制协议 Handler(基础)
#![allow(unused)]
fn main() {
use afast::{AFastDeserialize, AFastSerialize, Tag, handler};
use crate::state::AppState;
#[derive(AFastDeserialize, Tag)]
#[tag("请求体描述")]
pub struct MyRequest {
#[tag("字段描述")]
pub name: String,
}
#[derive(AFastSerialize, Tag)]
#[tag("响应体描述")]
pub struct MyResponse {
#[tag("字段描述")]
pub message: String,
}
#[handler(desc("描述这个 handler 的功能"))]
pub async fn my_handler(
afast::State(state): afast::State<AppState>,
afast::Data(req): afast::Data<MyRequest>,
) -> afast::Result<MyResponse> {
let db = state.db.lock().await;
Ok(MyResponse { message: format!("Hello, {}!", req.name) })
}
}
带认证的二进制 Handler(Custom)
#![allow(unused)]
fn main() {
use afast::{AFastDeserialize, AFastSerialize, Tag, handler};
#[derive(AFastDeserialize, Tag)]
#[tag("认证令牌")]
pub struct AuthCustom {
#[tag("Bearer token")]
pub token: String,
}
#[handler(desc("需要认证的接口"))]
pub async fn protected(
afast::State(state): afast::State<AppState>,
afast::Custom(auth): afast::Custom<AuthCustom>,
afast::Data(req): afast::Data<MyRequest>,
) -> afast::Result<MyResponse> {
// auth.token 可用
Ok(MyResponse { message: "已授权".into() })
}
}
带请求上下文的 Handler(Ctx)
#![allow(unused)]
fn main() {
use afast::{Tag, handler};
#[derive(Clone, Debug)]
pub struct RequestInfo {
pub request_id: String,
}
#[handler(desc("带上下文的接口"))]
pub async fn with_context(
afast::Ctx(ctx): afast::Ctx<RequestInfo>,
afast::State(state): afast::State<AppState>,
) -> afast::Result<MyResponse> {
println!("请求 ID: {}", ctx.request_id);
Ok(MyResponse { message: "完成".into() })
}
}
HTTP REST Handler(Ordinary HTTP)
HTTP handler 使用 Header<T> 做认证,返回 HttpResult<Json<T>>:
#![allow(unused)]
fn main() {
use afast::{get, post, put, delete, Tag};
use serde::{Deserialize, Serialize};
// HTTP 认证用 Header,不用 Custom
#[derive(Debug, Deserialize, Tag)]
#[tag("HTTP 认证头")]
pub struct AuthHeader {
#[tag("Authorization 头")]
pub authorization: String,
}
impl AuthHeader {
pub fn token(&self) -> &str {
self.authorization.strip_prefix("Bearer ").unwrap_or(&self.authorization)
}
}
#[derive(Debug, Deserialize, Tag)]
#[tag("查询参数")]
pub struct ListUsersQuery {
#[tag("页码")]
pub page: Option<i64>,
#[tag("每页数量")]
pub size: Option<i64>,
}
#[derive(Debug, Serialize, Tag)]
#[tag("用户 HTTP 响应")]
pub struct UserHttp {
#[tag("用户 ID")]
pub id: i64,
#[tag("用户名")]
pub username: String,
}
#[derive(Debug, Serialize, Tag)]
#[tag("用户列表响应")]
pub struct ListUsersHttpResponse {
#[tag("总数")]
pub total: i64,
#[tag("用户列表")]
pub items: Vec<UserHttp>,
}
// 注意:#[get] 里是 desc,不是路径!路径在 service! 宏里定义
#[get(desc("通过 HTTP 列出用户"))]
pub async fn list_users_http(
afast::State(state): afast::State<AppState>,
afast::Header(auth): afast::Header<AuthHeader>,
afast::Query(query): afast::Query<ListUsersQuery>,
) -> afast::HttpResult<afast::Json<ListUsersHttpResponse>> {
let db = state.db.lock().await;
let _user_id = db.get_user_id_by_token(auth.token()).await
.ok_or_else(|| afast::Error::custom(401, "invalid token"))?;
let items = db.read(0, 100).await;
Ok(afast::Json(ListUsersHttpResponse { total: items.len() as i64, items: vec![] }))
}
}
服务注册
路径在 service! 宏中定义,不在 handler 属性中:
use afast::{AFast, service};
// 二进制 handler 用 h() 包裹
// HTTP handler 路径在 service! 中定义
let admin_svc = service!("admin", "管理服务" => {
group("user" => {
// 二进制协议 handler
h(create_user),
h(list_users),
// HTTP REST handler — 路径在这里定义
get("", list_users_http),
post("", create_user_http),
group(":user_id" => {
get("", get_user_http),
put("", update_user_http),
delete("", delete_user_http),
})
})
});
// catch-all 路由
let check_svc = service!("check", "检查服务" => {
h(health),
get("*", catch_all_get),
});
#[tokio::main]
async fn main() {
AFast::new()
.state(AppState::new())
.service(admin_svc)
.service(check_svc)
.http("0.0.0.0:5000")
.run()
.await
.unwrap();
}
AppState 模式
#![allow(unused)]
fn main() {
use std::sync::Arc;
use tokio::sync::Mutex;
#[derive(Clone)]
pub struct AppState {
pub db: Arc<Mutex<Database>>,
}
impl AppState {
pub fn new() -> Self {
Self { db: Arc::new(Mutex::new(Database::new())) }
}
}
}
类型派生规则
二进制协议类型 — 用于 #[handler] 的 Data<T> 和返回值:
#![allow(unused)]
fn main() {
#[derive(AFastDeserialize, Tag)]
#[tag("请求描述")]
pub struct MyRequest {
#[tag("字段描述")]
pub field: String,
}
#[derive(AFastSerialize, Tag)]
#[tag("响应描述")]
pub struct MyResponse {
#[tag("字段描述")]
pub field: String,
}
}
HTTP 类型 — 用于 #[get]/#[post] 等的 Body<T>、Query<T> 和返回值(需要 serde):
#![allow(unused)]
fn main() {
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Tag)]
#[tag("HTTP 请求体")]
pub struct MyBody {
#[tag("字段描述")]
pub field: String,
}
#[derive(Debug, Serialize, Tag)]
#[tag("HTTP 响应")]
pub struct MyHttpResponse {
#[tag("字段描述")]
pub field: String,
}
}
枚举类型 — 生成的 TypeScript 为 tagged union:
#![allow(unused)]
fn main() {
#[derive(AFastSerialize, AFastDeserialize, Tag)]
#[tag("用户角色")]
pub enum Role {
#[tag("管理员")]
Admin,
#[tag("普通用户")]
User,
#[tag("访客")]
Guest,
}
// 生成的 TS: type Role = { tag: 'Admin', data: null } | { tag: 'User', data: null } | ...
}
错误处理
使用 afast::Error::custom(code, message) 返回自定义错误:
#![allow(unused)]
fn main() {
#[handler(desc("带错误处理的 handler"))]
pub async fn with_error(
afast::State(state): afast::State<AppState>,
) -> afast::Result<MyResponse> {
if some_condition {
return Err(afast::Error::custom(400, "请求参数错误"));
}
let data = state.db.lock().await.query().await
.map_err(|e| afast::Error::custom(500, e.to_string()))?;
Ok(MyResponse { message: "成功".into() })
}
}
HTTP 方法(RESTful 路由)
HTTP handler 与二进制 handler 的关键区别:
- 认证用
Header<T>而不是Custom<T> - 返回
afast::HttpResult<afast::Json<T>>而不是afast::Result<T> - 类型需要
serde::Deserialize/serde::Serialize+Tag - 路径在
service!宏中定义,不在#[get]属性中
#![allow(unused)]
fn main() {
use afast::{get, post, put, delete, Tag};
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Tag)]
#[tag("查询参数")]
pub struct ListUsersQuery {
#[tag("页码")]
pub page: Option<i64>,
#[tag("每页数量")]
pub size: Option<i64>,
}
#[derive(Debug, Deserialize, Tag)]
#[tag("创建用户请求")]
pub struct CreateUserBody {
#[tag("用户名")]
pub username: String,
#[tag("密码")]
pub password: String,
#[tag("显示名")]
pub name: String,
}
#[derive(Debug, Deserialize, Tag)]
#[tag("用户 ID 路径参数")]
pub struct UserIdParam {
#[tag("用户 ID")]
pub user_id: i64,
}
#[derive(Debug, Deserialize, Tag)]
#[tag("更新用户请求")]
pub struct UpdateUserBody {
#[tag("显示名")]
pub name: String,
#[tag("年龄")]
pub age: i32,
#[tag("是否激活")]
pub active: bool,
}
// 路径在 service! 宏中定义,这里只写 desc
#[get(desc("列出用户"))]
pub async fn list_users_http(
afast::State(state): afast::State<AppState>,
afast::Header(auth): afast::Header<AuthHeader>,
afast::Query(query): afast::Query<ListUsersQuery>,
) -> afast::HttpResult<afast::Json<ListUsersHttpResponse>> {
// GET /user?page=1&size=10
Ok(afast::Json(ListUsersHttpResponse { total: 0, items: vec![] }))
}
#[post(desc("创建用户"))]
pub async fn create_user_http(
afast::State(state): afast::State<AppState>,
afast::Header(auth): afast::Header<AuthHeader>,
afast::Body(body): afast::Body<CreateUserBody>,
) -> afast::HttpResult<afast::Json<CreateUserHttpResponse>> {
// POST /user 带 JSON 请求体
Ok(afast::Json(CreateUserHttpResponse { id: 1 }))
}
// 在 service! 中的注册方式:
// service!("admin", "管理服务" => {
// group("user" => {
// get("", list_users_http), // GET /user
// post("", create_user_http), // POST /user
// group(":user_id" => {
// get("", get_user_http), // GET /user/:user_id
// put("", update_user_http), // PUT /user/:user_id
// delete("", delete_user_http), // DELETE /user/:user_id
// })
// })
// })
}
WebSocket 处理器
路径在 service! 宏中定义:
#![allow(unused)]
fn main() {
use afast::ws;
use afast::extractors::{WsSender, WsReceiver};
#[ws(desc("聊天 WebSocket"))]
pub async fn chat_ws(
afast::State(state): afast::State<AppState>,
sender: WsSender,
receiver: WsReceiver,
) {
while let Some(msg) = receiver.recv().await {
sender.send(msg).await;
}
}
// 在 service! 中注册:
// let chat_svc = service!("chat", "聊天服务" => {
// ws("/chat/:room", chat_ws),
// });
}
SSE(服务器推送事件)
路径在 service! 宏中定义:
#![allow(unused)]
fn main() {
use afast::sse;
use afast::extractors::SseSender;
#[sse(desc("事件流"))]
pub async fn sse_stream(sender: SseSender) {
for i in 0..10 {
sender.send(format!("事件 {}", i)).await;
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
}
// 在 service! 中注册:
// let chat_svc = service!("chat", "聊天服务" => {
// sse("/sse", sse_stream),
// });
}
生命周期钩子
#![allow(unused)]
fn main() {
use afast::hook::{Hook, RequestContext, RequestGuard, ConnectionGuard};
struct MyHook;
impl Hook for MyHook {
fn before_request(&self, ctx: &RequestContext) -> Option<Box<dyn RequestGuard>> {
eprintln!("→ {} ({})", ctx.handler_name, ctx.transport);
Some(Box::new(MyTimer(std::time::Instant::now())))
}
fn on_connect(&self, ctx: &RequestContext) -> Option<Box<dyn ConnectionGuard>> {
eprintln!("↕ connect: {}", ctx.handler_name);
Some(Box::new(MyConnGuard))
}
}
struct MyTimer(std::time::Instant);
struct MyConnGuard;
impl RequestGuard for MyTimer {
fn on_response(&mut self, ctx: &RequestContext, _resp: &[u8]) {
eprintln!("← {} OK ({:?})", ctx.handler_name, self.0.elapsed());
}
fn on_error(&mut self, ctx: &RequestContext, err: &afast::Error) {
eprintln!("✗ {} error: {}", ctx.handler_name, err);
}
}
impl ConnectionGuard for MyConnGuard {
fn on_disconnect(&mut self, ctx: &RequestContext) {
eprintln!("✕ disconnect: {}", ctx.handler_name);
}
}
// 在 main 中注册:
// AFast::new().hook(MyHook)
}
速率限制
在 #[handler] 属性中使用 rate_limit("policy_name"),并在 main 中配置策略:
#![allow(unused)]
fn main() {
use afast::RateLimitConfig;
#[handler(desc("带限流的接口"), rate_limit("api_limit"))]
pub async fn limited_endpoint(
afast::State(state): afast::State<AppState>,
) -> afast::Result<MyResponse> {
Ok(MyResponse { message: "成功".into() })
}
// 在 main 中配置限流策略:
// AFast::new()
// .rate_limit(RateLimitConfig::new()
// .policy("api_limit", afast::RateLimitPolicy::fixed_window(100, 60)))
}
客户端代码生成
启动服务后,可以生成客户端代码:
# TypeScript
curl http://localhost:5000/code/auth/ts
# JavaScript
curl http://localhost:5000/code/auth/js
# Kotlin
curl http://localhost:5000/code/auth/kt
# Rust
curl http://localhost:5000/code/auth/rs
其中 auth 是 service 名称。每个 service 生成一个独立的客户端文件。
客户端使用示例
TypeScript / JavaScript 客户端
生成的 TS 客户端为每个 service 创建一个 Client 类,通过 apis 属性暴露所有 handler 方法。
import { AuthClient } from './auth';
import { AdminClient } from './admin';
// ── 二进制协议客户端(fetch 模式,最常用)──
const auth = new AuthClient({
host: 'localhost',
port: 5001,
tls: false,
transport: 'fetch', // 'fetch' | 'ws' | 'nodetcp' | 'buntcp'
debug: true, // 开启后会打印请求/响应日志
customs: {
// Custom<T> 提取器需要提供一个返回 Promise 的工厂函数
AuthCustom: async () => ({ token: myToken })
}
});
await auth.apis._ready; // 等待连接就绪(fetch 模式立即就绪)
// 调用二进制 handler — 参数和返回值都有完整类型
const reg = await auth.apis.signup({
username: 'alice',
password: 'secret',
name: 'Alice'
});
console.log(reg.user.username); // 'alice'
console.log(reg.token); // 自动生成的 token
const login = await auth.apis.login({
username: 'alice',
password: 'secret'
});
// 无参数的 handler
const uid = await auth.apis.get_user_id();
// ── 同时有二进制和 HTTP 路由的客户端 ──
const admin = new AdminClient({
host: 'localhost',
port: 5001,
tls: false,
transport: 'fetch',
customs: {
AuthCustom: async () => ({ token: myToken })
},
headers: {
// HTTP 路由的 Header<T> 认证
AuthHeader: async () => ({ authorization: `Bearer ${myToken}` })
}
});
await admin.apis._ready;
// 二进制 handler — 通过 apis 下的分组访问
const users = await admin.apis.user.list_users({ page: 1, size: 10 });
console.log(users.total, users.items);
const newUser = await admin.apis.user.create_user({
username: 'bob', password: 'pw', name: 'Bob'
});
// HTTP REST handler — 通过 apis 下的分组访问
const httpUsers = await admin.apis.user.list_users_http({
queries: { page: 1, size: 10 } // Query 参数放在 queries 中
});
const created = await admin.apis.user.create_user_http({
body: { username: 'charlie', password: 'pw', name: 'Charlie' } // Body 放在 body 中
});
// 路径参数自动从参数对象中提取
const updated = await admin.apis.user.user_id.update_user_http({
user_id: 123, // 路径参数 :user_id
body: { name: 'Updated', age: 25, active: true }
});
// ── WebSocket 模式(支持长连接和推送)──
const wsAuth = new AuthClient({
host: 'localhost',
port: 3001,
tls: false,
transport: 'ws',
customs: { AuthCustom: async () => ({ token: myToken }) }
});
await wsAuth.apis._ready;
// 使用方式完全相同,底层自动走 WebSocket 二进制帧
const result = await wsAuth.apis.login({ username: 'alice', password: 'secret' });
Kotlin 客户端
import afast.generated.*
fun main() = runBlocking {
// 创建客户端
val auth = AuthClient(
host = "localhost",
port = 5001,
tls = false,
transport = "http", // "http" | "ws" | "tcp"
customFns = AuthCustomFns(
AuthCustom = { AuthCustom(token = myToken) }
)
)
// 调用 handler — suspend 函数,直接在协程中调用
val reg = auth.apis.signup(RegisterRequest(
username = "alice",
password = "secret",
name = "Alice"
))
println(reg.user.username) // "alice"
println(reg.token)
val login = auth.apis.login(LoginRequest(
username = "alice",
password = "secret"
))
// 无参数
val uid = auth.apis.get_user_id()
// 带 HTTP 路由的 service
val admin = AdminClient(
host = "localhost",
port = 5001,
tls = false,
transport = "http",
customFns = AdminCustomFns(
AuthCustom = { AuthCustom(token = myToken) }
),
headerFns = AdminHeaderFns(
AuthHeader = { AuthHeader(authorization = "Bearer $myToken") }
)
)
// 二进制 handler
val users = admin.apis.user.list_users(ListUsersRequest(page = 1, size = 10))
// HTTP REST handler
val httpUsers = admin.apis.user.list_users_http(
queries = UserListUsersHttpQuery(page = 1, size = 10)
)
}
客户端类型自动生成规则
| Rust 类型 | TypeScript 类型 | 说明 |
|---|---|---|
String | string | |
i32/i64/u32/u64/f32/f64 | number | |
bool | boolean | |
Vec<T> | T[] | |
Option<T> | T | null | |
HashMap<K,V> | Record<K,V> | |
Vec<u8> | Uint8Array | |
enum { A, B } | { tag: 'A', data: null } | { tag: 'B', data: null } | Tagged union |
#[handler(name("signup"))] | apis.signup(...) | 客户端方法名 |
group("user" => { ... }) | apis.user.xxx(...) | 嵌套分组 |
客户端支持的传输方式
| 传输方式 | 说明 | 适用场景 |
|---|---|---|
fetch | HTTP/1.1 或 HTTP/2 | 浏览器、Node.js(最常用) |
ws | WebSocket 二进制帧 | 浏览器、Node.js 长连接 |
nodetcp | Node.js TCP | Node.js 高性能场景 |
buntcp | Bun TCP | Bun 运行时 |
unirequest | uni-app HTTP | 小程序/APP |
uniws | uni-app WebSocket | 小程序/APP 长连接 |
wxrequest | 微信小程序 HTTP | 微信小程序 |
wxws | 微信小程序 WebSocket | 微信小程序长连接 |
常见错误
- 忘记
Tag派生 — 所有在 handler 中使用的类型必须派生Tag,字段需要#[tag("描述")] - 不要用
String接收请求体 — 始终使用Data<T>配合结构体 - 忘记
Result返回类型 — 二进制 handler 返回afast::Result<T>,HTTP handler 返回afast::HttpResult<afast::Json<T>> - 不要手动注册路由 — 使用
#[handler]宏 - 不要 clone State —
State<T>持有&'static T,直接使用即可 - 参数必须解构 — 写
afast::State(state): afast::State<AppState>而不是state: State<AppState> - HTTP 认证用 Header — HTTP handler 用
Header<T>做认证,不要用Custom<T> - HTTP 类型需要 serde — HTTP handler 的请求/响应类型需要
Deserialize/Serialize+Tag #[get]里写 desc 不写路径 — 路径在service!宏中定义- 路径参数在 service! 中用
:param— 如group(":user_id" => { get("", handler) })
项目结构模板
my-project/
├── Cargo.toml
└── src/
├── main.rs # 入口,service! 定义和 AFast 配置
├── state.rs # AppState + Database 定义
└── handler/
├── mod.rs
├── auth.rs # 认证相关 handler
├── admin.rs # 管理相关 handler(含 HTTP 路由)
└── chat.rs # WebSocket/SSE handler
快速参考
| 宏 | 用途 | 示例 |
|---|---|---|
#[handler] | 定义二进制协议 handler | #[handler(desc("..."), name("..."), cache(60), rate_limit("..."))] |
#[get] | 定义 HTTP GET | #[get(desc("..."))] — 路径在 service! 中 |
#[post] | 定义 HTTP POST | #[post(desc("..."))] |
#[put] | 定义 HTTP PUT | #[put(desc("..."))] |
#[delete] | 定义 HTTP DELETE | #[delete(desc("..."))] |
#[ws] | 定义 WebSocket | #[ws(desc("..."))] |
#[sse] | 定义 SSE | #[sse(desc("..."))] |
service! | 创建服务并定义路由 | service!("name", "desc" => { h(fn), get("path", fn) }) |
h() | 注册二进制 handler | h(my_handler) |
group() | 路由分组 | group("user" => { get("", fn), group(":id" => { get("", fn) }) }) |
| 提取器 | 用途 | 解构语法 |
|---|---|---|
State<T> | 共享应用状态 | afast::State(state): afast::State<AppState> |
Data<T> | 二进制请求体 | afast::Data(req): afast::Data<MyRequest> |
Custom<T> | 二进制认证上下文 | afast::Custom(auth): afast::Custom<AuthCustom> |
Ctx<T> | 请求上下文(hook 写入) | afast::Ctx(ctx): afast::Ctx<RequestInfo> |
Query<T> | HTTP 查询参数 | afast::Query(q): afast::Query<MyQuery> |
Param<T> | HTTP 路径参数 | afast::Param(p): afast::Param<MyParam> |
Body<T> | HTTP 请求体 | afast::Body(b): afast::Body<MyBody> |
Header<T> | HTTP 请求头 | afast::Header(h): afast::Header<AuthHeader> |