Skip to content

Commit 6324740

Browse files
committed
feat(volo-thrift): add multi service support
1 parent a7d2463 commit 6324740

File tree

13 files changed

+1360
-26
lines changed

13 files changed

+1360
-26
lines changed

.github/workflows/security.yaml

Lines changed: 0 additions & 17 deletions
This file was deleted.

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/thrift-multi-service.md

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
# Volo-Thrift Multi Service 使用指南
2+
3+
## 1. 功能概述
4+
5+
Multi Service 功能允许一个 volo-thrift Server 同时处理多个 Thrift Service,通过 TTHeader 中的 `isn` (IDL Service Name) 字段进行路由。
6+
7+
### 主要特性
8+
9+
- **多服务支持**:单个 Server 可注册多个 Thrift Service
10+
- **ISN 路由**:根据请求头中的 `isn` 字段自动路由到对应服务
11+
- **默认服务**:支持设置默认服务处理无 ISN 或未知 ISN 的请求
12+
- **零拷贝**:基于 `Bytes` 的请求/响应传递,避免重复序列化
13+
- **向后兼容**:单服务模式 API 保持不变
14+
15+
## 2. 快速开始
16+
17+
### 2.1 定义多个 Thrift Service
18+
19+
```thrift
20+
// hello.thrift
21+
namespace rs hello
22+
23+
struct HelloRequest {
24+
1: required string name,
25+
}
26+
27+
struct HelloResponse {
28+
1: required string message,
29+
}
30+
31+
service HelloService {
32+
HelloResponse hello(1: HelloRequest req),
33+
}
34+
```
35+
36+
```thrift
37+
// echo.thrift
38+
namespace rs echo
39+
40+
struct EchoRequest {
41+
1: required string message,
42+
}
43+
44+
struct EchoResponse {
45+
1: required string message,
46+
}
47+
48+
service EchoService {
49+
EchoResponse echo(1: EchoRequest req),
50+
}
51+
```
52+
53+
### 2.2 实现 Service Handler
54+
55+
```rust
56+
use volo_gen::hello::{HelloService, HelloRequest, HelloResponse};
57+
use volo_gen::echo::{EchoService, EchoRequest, EchoResponse};
58+
59+
// HelloService 实现
60+
#[derive(Clone)]
61+
struct HelloServiceImpl;
62+
63+
impl HelloService for HelloServiceImpl {
64+
async fn hello(
65+
&self,
66+
req: HelloRequest,
67+
) -> Result<HelloResponse, volo_thrift::ServerError> {
68+
Ok(HelloResponse {
69+
message: format!("Hello, {}!", req.name).into(),
70+
})
71+
}
72+
}
73+
74+
// EchoService 实现
75+
#[derive(Clone)]
76+
struct EchoServiceImpl;
77+
78+
impl EchoService for EchoServiceImpl {
79+
async fn echo(
80+
&self,
81+
req: EchoRequest,
82+
) -> Result<EchoResponse, volo_thrift::ServerError> {
83+
Ok(EchoResponse {
84+
message: req.message,
85+
})
86+
}
87+
}
88+
```
89+
90+
### 2.3 创建 Multi Service Server
91+
92+
```rust
93+
use volo_thrift::server::{Router, Server};
94+
use std::net::SocketAddr;
95+
96+
#[volo::main]
97+
async fn main() {
98+
let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
99+
100+
// 创建各个服务(使用 from_handler 方法)
101+
let hello_service = volo_gen::hello::HelloServiceServer::from_handler(HelloServiceImpl);
102+
let echo_service = volo_gen::echo::EchoServiceServer::from_handler(EchoServiceImpl);
103+
104+
// 创建 Router
105+
let router = Router::new()
106+
.with_default_service(hello_service) // 设置默认服务
107+
.add_service(echo_service); // 添加额外服务
108+
109+
// 启动 Server
110+
Server::with_router(router)
111+
.run(volo::net::Address::from(addr))
112+
.await
113+
.unwrap();
114+
}
115+
```
116+
117+
## 3. API 详解
118+
119+
### 3.1 Router
120+
121+
`Router` 是多服务路由器,负责根据 ISN 将请求分发到对应的服务。
122+
123+
```rust
124+
use volo_thrift::server::Router;
125+
126+
// 创建空路由器
127+
let router = Router::new();
128+
129+
// 设置默认服务(处理无 ISN 或未知 ISN 的请求)
130+
let router = router.with_default_service(service_a);
131+
132+
// 添加额外服务
133+
let router = router.add_service(service_b);
134+
135+
// 查询已注册服务数量
136+
let count = router.service_count();
137+
138+
// 检查是否有默认服务
139+
let has_default = router.has_default_service();
140+
```
141+
142+
### 3.2 NamedService Trait
143+
144+
每个服务需要实现 `NamedService` trait 以提供服务名称。代码生成器会自动为生成的 `*Server` 类型实现此 trait。
145+
146+
```rust
147+
pub trait NamedService {
148+
/// IDL 中定义的 service 名称
149+
const NAME: &'static str;
150+
}
151+
152+
// 自动生成的实现示例
153+
impl<S> NamedService for HelloServiceServer<S> {
154+
const NAME: &'static str = "HelloService";
155+
}
156+
```
157+
158+
### 3.3 Server::with_router
159+
160+
使用 `Server::with_router` 创建支持多服务的 Server:
161+
162+
```rust
163+
use volo_thrift::server::Server;
164+
165+
let server = Server::with_router(router)
166+
.layer(SomeMiddleware) // 支持 layer
167+
.run(addr)
168+
.await?;
169+
```
170+
171+
## 4. 路由规则
172+
173+
Router 按以下优先级进行路由:
174+
175+
1. **精确匹配**:如果请求携带 `isn` 且匹配某个已注册服务名称,路由到该服务
176+
2. **默认回退**:如果请求无 `isn``isn` 不匹配任何服务,路由到默认服务
177+
3. **错误处理**:如果无默认服务且无法匹配,返回 `ApplicationException(UNKNOWN_METHOD)`
178+
179+
```
180+
请求到达
181+
182+
183+
有 ISN?
184+
/ \
185+
是 否
186+
│ │
187+
▼ ▼
188+
匹配服务? ──否──► 有默认服务?
189+
│ / \
190+
是 是 否
191+
│ │ │
192+
▼ ▼ ▼
193+
路由到匹配服务 路由到默认服务 返回错误
194+
```
195+
196+
## 5. 生成代码变更
197+
198+
代码生成器为每个 `*Server` 类型自动生成以下实现:
199+
200+
### 5.1 NamedService 实现
201+
202+
```rust
203+
impl<S> NamedService for HelloServiceServer<S> {
204+
const NAME: &'static str = "HelloService"; // IDL 中的 service 名称
205+
}
206+
```
207+
208+
### 5.2 Service<ServerContext, Bytes> 实现
209+
210+
```rust
211+
impl<S> Service<ServerContext, Bytes> for HelloServiceServer<S>
212+
where
213+
S: HelloService + Send + Sync + 'static,
214+
{
215+
type Response = Bytes;
216+
type Error = ServerError;
217+
218+
async fn call(&self, cx: &mut ServerContext, payload: Bytes)
219+
-> Result<Bytes, ServerError>
220+
{
221+
// 从 context 重建消息标识(零拷贝)
222+
// 解码请求
223+
// 调用业务逻辑
224+
// 编码响应
225+
}
226+
}
227+
```
228+
229+
### 5.3 服务创建方法
230+
231+
```rust
232+
#[derive(Clone)]
233+
pub struct HelloServiceServer<S> {
234+
inner: S, // 私有字段
235+
}
236+
237+
impl<S> HelloServiceServer<S> {
238+
/// 从 handler 创建服务实例,用于多服务路由
239+
pub fn from_handler(handler: S) -> Self {
240+
Self { inner: handler }
241+
}
242+
}
243+
244+
impl<S: HelloService + Send + Sync + 'static> HelloServiceServer<S> {
245+
/// 创建单服务 Server(传统用法)
246+
pub fn new(inner: S) -> Server<Self, ...> {
247+
Server::new(Self { inner })
248+
}
249+
}
250+
```
251+
252+
## 6. 与 volo-grpc 对比
253+
254+
| 特性 | volo-thrift | volo-grpc |
255+
|------|-------------|-----------|
256+
| 路由标识 | TTHeader `isn` 字段 | gRPC path |
257+
| NamedService | 基于 IDL service 名称 | 基于 package.service 名称 |
258+
| Router API | 相同 | 相同 |
259+
| 默认服务 | 支持 | 支持 |
260+
261+
## 7. 注意事项
262+
263+
1. **ISN 编码**:客户端需要在 TTHeader 的 string KV 中设置 `isn` 字段
264+
2. **服务名称**:使用 IDL 中定义的原始 service 名称(非 Rust 名称)
265+
3. **Handler Clone**:Handler 实现需要实现 `Clone` trait
266+
4. **单服务兼容**:现有单服务代码无需修改,`Server::new` API 保持不变
267+
268+
## 8. 完整示例
269+
270+
参见 `examples/tests/thrift_multi_service.rs` 中的集成测试。
271+
272+
### 8.1 Server 示例
273+
274+
```rust
275+
use volo_thrift::server::{Router, Server};
276+
277+
#[derive(Clone)]
278+
struct HelloImpl;
279+
impl HelloService for HelloImpl { /* ... */ }
280+
281+
#[derive(Clone)]
282+
struct EchoImpl;
283+
impl EchoService for EchoImpl { /* ... */ }
284+
285+
#[volo::main]
286+
async fn main() {
287+
let addr = "127.0.0.1:8080".parse().unwrap();
288+
289+
let router = Router::new()
290+
.with_default_service(HelloServiceServer { inner: HelloImpl })
291+
.add_service(EchoServiceServer { inner: EchoImpl });
292+
293+
Server::with_router(router)
294+
.run(volo::net::Address::from(addr))
295+
.await
296+
.unwrap();
297+
}
298+
```
299+
300+
### 8.2 测试路由逻辑
301+
302+
```rust
303+
use volo_thrift::server::Router;
304+
use volo_thrift::context::ThriftContext;
305+
use motore::service::Service;
306+
307+
#[tokio::test]
308+
async fn test_routing() {
309+
let router = Router::new()
310+
.with_default_service(hello_service)
311+
.add_service(echo_service);
312+
313+
// 测试 ISN 路由
314+
let mut cx = ServerContext::default();
315+
cx.set_idl_service_name("EchoService".into());
316+
let resp = router.call(&mut cx, payload).await?;
317+
318+
// 测试默认路由
319+
let mut cx = ServerContext::default();
320+
let resp = router.call(&mut cx, payload).await?;
321+
}
322+
```
323+
324+
## 9. 相关类型导出
325+
326+
以下类型从 `volo_thrift` 导出:
327+
328+
```rust
329+
// 路由相关
330+
pub use volo_thrift::server::{Router, NamedService};
331+
pub use volo_thrift::server::Server; // with_router 方法
332+
333+
// ISN 相关(通过 ThriftContext trait)
334+
pub use volo_thrift::context::ThriftContext; // idl_service_name() / set_idl_service_name()
335+
pub use volo_thrift::codec::default::ttheader::HEADER_IDL_SERVICE_NAME; // ISN header key ("isn")
336+
337+
// Bytes 类型
338+
pub use volo_thrift::{Bytes, BytesMut};
339+
```

examples/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ pilota-thrift-fieldmask.workspace = true
158158
pilota-thrift-reflect.workspace = true
159159
volo = { path = "../volo" }
160160
volo-grpc = { path = "../volo-grpc", features = ["grpc-web"] }
161-
volo-thrift = { path = "../volo-thrift", features = ["multiplex"] }
161+
volo-thrift = { path = "../volo-thrift" }
162162
volo-http = { path = "../volo-http", features = [
163163
"client",
164164
"server",
@@ -172,8 +172,10 @@ volo-http = { path = "../volo-http", features = [
172172
volo-gen = { path = "./volo-gen" }
173173

174174
[features]
175+
default = ["multiplex"]
175176
tls = ["rustls"]
176177
shmipc = ["volo/shmipc", "volo-thrift/shmipc"]
178+
multiplex = ["volo-thrift/multiplex"]
177179

178180
__tls = []
179181
rustls = ["__tls", "volo/rustls", "volo-grpc/rustls", "volo-http/rustls"]

0 commit comments

Comments
 (0)