Skip to content

Commit f85678f

Browse files
committed
Make Rust SDK transport dependencies feature-specific
1 parent df57dd2 commit f85678f

25 files changed

Lines changed: 567 additions & 29 deletions

eventmesh-sdks/eventmesh-sdk-rust/Cargo.toml

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,47 @@ rustdoc-args = ["--cfg", "docsrs"]
4242
# compiling a protocol implementation.
4343
default = []
4444
# Transport protocols (implemented incrementally).
45-
grpc = ["dep:tonic", "dep:prost", "dep:prost-types"]
45+
grpc = [
46+
"dep:tonic",
47+
"dep:prost",
48+
"dep:prost-types",
49+
"dep:tonic-build",
50+
"dep:tokio",
51+
"dep:tokio-util",
52+
"dep:tracing",
53+
"dep:rand",
54+
"dep:uuid",
55+
"dep:http",
56+
]
4657
# HTTP transport (producer, consumer, webhook middleware + built-in server).
47-
http = ["dep:reqwest", "dep:serde_urlencoded", "dep:axum", "dep:tower", "dep:tower-http"]
58+
http = [
59+
"dep:reqwest",
60+
"dep:serde_urlencoded",
61+
"dep:axum",
62+
"dep:tokio",
63+
"dep:tokio-util",
64+
"dep:tracing",
65+
"dep:rand",
66+
"dep:uuid",
67+
"dep:bytes",
68+
"dep:http",
69+
]
4870
# TCP transport (producer, consumer, native TCP wire protocol).
4971
# `tokio-util/codec` is required by the framed codec in
5072
# `src/transport/tcp/{codec,connection}.rs`; without it the crate fails to
5173
# compile when `tcp` is enabled without `grpc` (tonic pulls in `codec` only as
5274
# a side effect of the `grpc` feature).
53-
tcp = ["tokio-util/codec"]
75+
tcp = [
76+
"dep:tokio",
77+
"dep:tokio-stream",
78+
"dep:tokio-util",
79+
"tokio-util/codec",
80+
"dep:futures",
81+
"dep:tracing",
82+
"dep:rand",
83+
"dep:uuid",
84+
"dep:bytes",
85+
]
5486
# Message models.
5587
cloud_events = ["dep:cloudevents", "dep:chrono"]
5688
# Convenience aggregate.
@@ -64,18 +96,18 @@ e2e = ["grpc", "http", "tcp", "cloud_events"]
6496
interop_e2e = ["e2e"]
6597

6698
[dependencies]
67-
tokio = { version = "1", features = ["full"] }
68-
tokio-stream = { version = "0.1", features = ["net"] }
69-
tokio-util = "0.7"
70-
futures = "0.3"
99+
tokio = { version = "1", optional = true, features = ["full"] }
100+
tokio-stream = { version = "0.1", optional = true, features = ["net"] }
101+
tokio-util = { version = "0.7", optional = true }
102+
futures = { version = "0.3", optional = true }
71103
serde = { version = "1", features = ["derive"] }
72104
serde_json = "1"
73105
thiserror = "1"
74-
bytes = "1"
75-
rand = "0.8"
76-
uuid = { version = "1", features = ["v4"] }
77-
tracing = "0.1"
78-
http = "1"
106+
bytes = { version = "1", optional = true }
107+
rand = { version = "0.8", optional = true }
108+
uuid = { version = "1", optional = true, features = ["v4"] }
109+
tracing = { version = "0.1", optional = true }
110+
http = { version = "1", optional = true }
79111
# gRPC transport.
80112
tonic = { version = "0.12", optional = true }
81113
prost = { version = "0.13", optional = true }
@@ -86,12 +118,10 @@ chrono = { version = "0.4", optional = true, default-features = false, features
86118
# HTTP transport.
87119
reqwest = { version = "0.12", optional = true, features = ["json"] }
88120
serde_urlencoded = { version = "0.7", optional = true }
89-
tower = { version = "0.5", optional = true }
90-
tower-http = { version = "0.6", optional = true }
91121
axum = { version = "0.7", optional = true }
92122

93123
[build-dependencies]
94-
tonic-build = "0.12"
124+
tonic-build = { version = "0.12", optional = true }
95125

96126
[dev-dependencies]
97127
tokio = { version = "1", features = ["full", "test-util"] }
@@ -110,16 +140,36 @@ name = "grpc_consumer"
110140
path = "examples/grpc/consumer.rs"
111141
required-features = ["grpc"]
112142

143+
[[example]]
144+
name = "grpc_webhook_consumer"
145+
path = "examples/grpc/webhook_consumer.rs"
146+
required-features = ["grpc"]
147+
113148
[[example]]
114149
name = "grpc_producer_cloud_events"
115150
path = "examples/grpc/producer_cloud_events.rs"
116151
required-features = ["grpc", "cloud_events"]
117152

153+
[[example]]
154+
name = "grpc_batch"
155+
path = "examples/grpc/batch.rs"
156+
required-features = ["grpc"]
157+
158+
[[example]]
159+
name = "grpc_request_reply"
160+
path = "examples/grpc/request_reply.rs"
161+
required-features = ["grpc"]
162+
118163
[[example]]
119164
name = "http_producer"
120165
path = "examples/http/producer.rs"
121166
required-features = ["http"]
122167

168+
[[example]]
169+
name = "http_producer_cloud_events"
170+
path = "examples/http/producer_cloud_events.rs"
171+
required-features = ["http", "cloud_events"]
172+
123173
[[example]]
124174
name = "http_consumer_server"
125175
path = "examples/http/consumer_server.rs"
@@ -144,3 +194,13 @@ required-features = ["tcp"]
144194
name = "tcp_producer_cloud_events"
145195
path = "examples/tcp/producer_cloud_events.rs"
146196
required-features = ["tcp", "cloud_events"]
197+
198+
[[example]]
199+
name = "tcp_broadcast"
200+
path = "examples/tcp/broadcast.rs"
201+
required-features = ["tcp"]
202+
203+
[[example]]
204+
name = "tcp_request_reply"
205+
path = "examples/tcp/request_reply.rs"
206+
required-features = ["tcp"]

eventmesh-sdks/eventmesh-sdk-rust/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ transport failures. HTTP consumers and webhook registrations additionally
8282
provide `close().await`, which unregisters remote subscriptions before
8383
signalling shutdown and joining.
8484

85+
`GrpcWebhookConsumer` does not automatically unregister remote webhook
86+
subscriptions when `shutdown()` or `join()` is called. Retain the subscriptions
87+
and webhook URL, call `unsubscribe(...).await` explicitly, and only then call
88+
`shutdown()` and `join().await`. See the `grpc_webhook_consumer` example.
89+
8590
HTTP request/reply is not exposed because the current SDK and stock Runtime do not provide a complete HTTP responder path. Use gRPC or TCP for request/reply.
8691

8792
`Message` is a public dialect envelope, not a wire format. The selected transport owns protobuf, HTTP form, or TCP frame serialization. With `cloud_events`, CloudEvents remain CloudEvents; `Message::into_event_mesh()` does not silently flatten them into the native EventMesh model.

eventmesh-sdks/eventmesh-sdk-rust/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
2525
return Ok(());
2626
}
2727

28+
#[cfg(feature = "grpc")]
2829
tonic_build::configure()
2930
.build_server(false)
3031
.build_client(true)

eventmesh-sdks/eventmesh-sdk-rust/examples/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,28 @@ Each example is a small executable with one responsibility. They use the default
66
| --- | --- | --- | --- |
77
| gRPC | `grpc_producer` | Publish a native EventMesh message | `cargo run --example grpc_producer --features grpc` |
88
| gRPC | `grpc_consumer` | Stream consumption with `MessageHandler` | `cargo run --example grpc_consumer --features grpc` |
9+
| gRPC | `grpc_webhook_consumer` | Register an application-owned webhook and explicitly unregister it on exit | `cargo run --example grpc_webhook_consumer --features grpc` |
910
| gRPC | `grpc_producer_cloud_events` | Publish a CloudEvent | `cargo run --example grpc_producer_cloud_events --features grpc,cloud_events` |
11+
| gRPC | `grpc_batch` | Publish several native messages with one batch RPC | `cargo run --example grpc_batch --features grpc` |
12+
| gRPC | `grpc_request_reply` | Run a synchronous subscriber and complete request/reply | `cargo run --example grpc_request_reply --features grpc` |
1013
| HTTP | `http_producer` | Publish over HTTP | `cargo run --example http_producer --features http` |
14+
| HTTP | `http_producer_cloud_events` | Publish a CloudEvent over HTTP | `cargo run --example http_producer_cloud_events --features http,cloud_events` |
1115
| HTTP | `http_consumer_server` | SDK-managed axum callback server | `cargo run --example http_consumer_server --features http` |
1216
| HTTP | `http_consumer_custom` | Application-owned axum webhook endpoint | `cargo run --example http_consumer_custom --features http` |
1317
| TCP | `tcp_producer` | Connected TCP publish | `cargo run --example tcp_producer --features tcp` |
1418
| TCP | `tcp_consumer` | Connected TCP subscribe | `cargo run --example tcp_consumer --features tcp` |
1519
| TCP | `tcp_producer_cloud_events` | Publish a CloudEvent over TCP | `cargo run --example tcp_producer_cloud_events --features tcp,cloud_events` |
20+
| TCP | `tcp_broadcast` | Send a fire-and-forget broadcast | `cargo run --example tcp_broadcast --features tcp` |
21+
| TCP | `tcp_request_reply` | Run a synchronous subscriber and complete request/reply | `cargo run --example tcp_request_reply --features tcp` |
1622

1723
Run a consumer first, then run its corresponding producer. The HTTP examples listen on ports 8080 (built-in server) and 8081 (custom endpoint); change the advertised callback URL when EventMesh cannot reach `127.0.0.1`.
1824

1925
The examples intentionally use minimal configuration. For timeouts, identity, credentials, HTTP TLS, endpoint weights, and TCP reconnect tuning, consult the public rustdoc with `cargo doc --features full --no-deps --open`.
26+
27+
The two HTTP consumer examples handle Ctrl-C and call `close().await`, which
28+
unregisters their remote subscriptions before stopping local background work.
29+
Use the same shutdown pattern in long-running applications.
30+
31+
The gRPC webhook consumer has no automatic remote cleanup. Its example retains
32+
the subscription and URL, calls `unsubscribe().await` after Ctrl-C, and then
33+
stops and joins the local heartbeat task.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
use eventmesh::{
19+
config::{Endpoint, GrpcConfig, ProducerOptions},
20+
EventMeshMessage, GrpcClient, Message,
21+
};
22+
23+
#[tokio::main]
24+
async fn main() -> eventmesh::Result<()> {
25+
let client = GrpcClient::new(GrpcConfig::new(Endpoint::new("127.0.0.1", 10_205)?))?;
26+
let producer = client.producer(ProducerOptions::new("test-producerGroup"))?;
27+
let messages = (1..=3)
28+
.map(|index| {
29+
EventMeshMessage::new(
30+
"test-topic-rust-sdk",
31+
format!("hello from rust batch #{index}"),
32+
)
33+
.map(Message::from)
34+
})
35+
.collect::<eventmesh::Result<Vec<_>>>()?;
36+
37+
println!("published: {:?}", producer.publish_batch(messages).await?);
38+
Ok(())
39+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
use eventmesh::{
19+
config::{Endpoint, GrpcConfig, GrpcConsumerOptions, ProducerOptions},
20+
EventMeshMessage, GrpcClient, Message, Subscription,
21+
};
22+
use std::time::Duration;
23+
24+
const TOPIC: &str = "test-topic-rust-sdk";
25+
26+
#[tokio::main]
27+
async fn main() -> eventmesh::Result<()> {
28+
let client = GrpcClient::new(GrpcConfig::new(Endpoint::new("127.0.0.1", 10_205)?))?;
29+
let consumer = client
30+
.stream_consumer(
31+
GrpcConsumerOptions::new("test-consumerGroup"),
32+
[Subscription::new(TOPIC).with_delivery_type(eventmesh::DeliveryType::Sync)],
33+
|request: Message| async move {
34+
let request = request.into_event_mesh()?;
35+
Ok(Some(Message::from(EventMeshMessage::new(
36+
request.topic(),
37+
"pong",
38+
)?)))
39+
},
40+
)
41+
.await?;
42+
let producer = client.producer(ProducerOptions::new("test-producerGroup"))?;
43+
44+
// Give EventMesh time to make the new subscription routable before the
45+
// first synchronous request.
46+
tokio::time::sleep(Duration::from_secs(1)).await;
47+
let reply = producer
48+
.request_reply(Message::from(EventMeshMessage::new(TOPIC, "ping")?))
49+
.await?;
50+
println!("reply: {reply:?}");
51+
52+
consumer.shutdown();
53+
consumer.join().await
54+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
use eventmesh::{
19+
config::{ConsumerOptions, Endpoint, GrpcConfig},
20+
GrpcClient, Subscription,
21+
};
22+
23+
const TOPIC: &str = "test-topic-rust-sdk";
24+
const WEBHOOK_URL: &str = "http://127.0.0.1:8080/eventmesh/callback";
25+
26+
#[tokio::main]
27+
async fn main() -> eventmesh::Result<()> {
28+
// Start an HTTP endpoint at WEBHOOK_URL before running this example.
29+
let client = GrpcClient::new(GrpcConfig::new(Endpoint::new("127.0.0.1", 10_205)?))?;
30+
let consumer = client
31+
.webhook_consumer(ConsumerOptions::new("test-consumerGroup"))
32+
.await?;
33+
let subscription = Subscription::new(TOPIC);
34+
35+
consumer
36+
.subscribe([subscription.clone()], WEBHOOK_URL)
37+
.await?;
38+
println!("registered {TOPIC}; press Ctrl-C to unregister and exit");
39+
tokio::signal::ctrl_c().await?;
40+
41+
// shutdown() only stops local heartbeat work. Explicitly remove the
42+
// remote registration first so it does not linger until server expiry.
43+
let unsubscribe_result = consumer.unsubscribe([subscription], WEBHOOK_URL).await;
44+
consumer.shutdown();
45+
let join_result = consumer.join().await;
46+
unsubscribe_result?;
47+
join_result
48+
}

eventmesh-sdks/eventmesh-sdk-rust/examples/http/consumer_custom.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ async fn main() -> eventmesh::Result<()> {
5757
consumer
5858
.subscribe(Subscription::new("test-topic-rust-sdk"), webhook_url)
5959
.await?;
60-
axum::serve(listener, app).await?;
61-
Ok(())
60+
axum::serve(listener, app)
61+
.with_graceful_shutdown(async {
62+
let _ = tokio::signal::ctrl_c().await;
63+
})
64+
.await?;
65+
consumer.close().await
6266
}

eventmesh-sdks/eventmesh-sdk-rust/examples/http/consumer_server.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,11 @@ async fn main() -> eventmesh::Result<()> {
4545
PrintHandler,
4646
)
4747
.await?;
48-
consumer.join().await
48+
tokio::select! {
49+
result = consumer.join() => result,
50+
signal = tokio::signal::ctrl_c() => {
51+
signal?;
52+
consumer.close().await
53+
}
54+
}
4955
}

0 commit comments

Comments
 (0)