starpc 0.52.1 → 0.52.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -8
- package/dist/srpc/client-rpc.d.ts +1 -0
- package/dist/srpc/client-rpc.js +9 -0
- package/dist/srpc/client-rpc.test.d.ts +1 -0
- package/dist/srpc/client-rpc.test.js +50 -0
- package/dist/srpc/common-rpc.d.ts +5 -0
- package/dist/srpc/common-rpc.js +37 -8
- package/dist/srpc/errors.d.ts +2 -0
- package/dist/srpc/errors.js +14 -0
- package/dist/srpc/index.d.ts +1 -1
- package/dist/srpc/index.js +1 -1
- package/echo/Cargo.toml +8 -0
- package/echo/echo_e2e_test.cpp +542 -985
- package/echo/echo_srpc.pb.cpp +3 -5
- package/echo/echo_srpc.pb.go +6 -9
- package/echo/echo_srpc.pb.hpp +23 -3
- package/echo/echo_srpc.pb.rs +61 -26
- package/echo/echo_srpc.py +4 -1
- package/echo/half_close_test.rs +178 -0
- package/echo/integration_server.rs +14 -21
- package/echo/main.rs +28 -48
- package/go.mod +1 -1
- package/go.sum +2 -18
- package/integration/cross-language/cpp-client.cpp +446 -402
- package/integration/cross-language/cpp-pipe-server.cpp +139 -0
- package/integration/cross-language/cpp-pipe_test.go +114 -0
- package/integration/cross-language/cpp-server.cpp +222 -186
- package/mock/mock_srpc.pb.cpp +1 -1
- package/mock/mock_srpc.pb.hpp +1 -1
- package/mock/mock_srpc.pb.rs +10 -9
- package/package.json +1 -1
- package/srpc/CMakeLists.txt +3 -58
- package/srpc/README-cpp.md +69 -0
- package/srpc/accept.go +10 -4
- package/srpc/accept_test.go +25 -0
- package/srpc/build.rs +129 -34
- package/srpc/client-half-close_test.go +152 -0
- package/srpc/client-invoker.go +32 -42
- package/srpc/client-invoker_test.go +53 -0
- package/srpc/client-rpc.cpp +65 -80
- package/srpc/client-rpc.go +32 -33
- package/srpc/client-rpc.hpp +52 -38
- package/srpc/client-rpc.test.ts +67 -0
- package/srpc/client-rpc.ts +10 -0
- package/srpc/client-rpc_test.go +76 -0
- package/srpc/client.cpp +99 -87
- package/srpc/client.hpp +46 -35
- package/srpc/client.rs +78 -38
- package/srpc/client_half_close_test.cc +331 -0
- package/srpc/client_invoker.rs +75 -0
- package/srpc/common-rpc.cpp +186 -146
- package/srpc/common-rpc.go +5 -4
- package/srpc/common-rpc.hpp +119 -89
- package/srpc/common-rpc.ts +43 -8
- package/srpc/common-rpc_test.go +23 -0
- package/srpc/error.rs +19 -2
- package/srpc/errors.hpp +80 -50
- package/srpc/errors.ts +16 -0
- package/srpc/handler.hpp +9 -7
- package/srpc/handler.rs +7 -45
- package/srpc/index.ts +2 -0
- package/srpc/invoker.hpp +82 -55
- package/srpc/invoker.rs +14 -52
- package/srpc/lib.rs +2 -0
- package/srpc/message.hpp +17 -12
- package/srpc/msg-stream.go +1 -0
- package/srpc/msg-stream.hpp +108 -61
- package/srpc/mux.cpp +84 -84
- package/srpc/mux.hpp +48 -38
- package/srpc/mux.rs +4 -4
- package/srpc/net.go +12 -3
- package/srpc/packet.cpp +60 -58
- package/srpc/packet.hpp +35 -20
- package/srpc/packet.rs +8 -27
- package/srpc/rpc.rs +332 -115
- package/srpc/rpc_lifecycle_test.cc +333 -0
- package/srpc/server-cancel_test.go +39 -0
- package/srpc/server-rpc.cpp +83 -73
- package/srpc/server-rpc.go +21 -19
- package/srpc/server-rpc.hpp +56 -37
- package/srpc/server.rs +76 -131
- package/srpc/starpc.hpp +4 -2
- package/srpc/stream.go +4 -0
- package/srpc/stream.hpp +95 -34
- package/srpc/stream.rs +7 -2
- package/srpc/testing.rs +11 -66
- package/srpc/transport.rs +21 -71
- package/srpc/websocket.rs +106 -57
- package/srpc/writer.hpp +46 -32
- package/srpc/yamux.rs +82 -26
- package/srpc/tests/integration_test.rs +0 -495
package/srpc/yamux.rs
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
//! Yamux transport adapters for starpc.
|
|
2
2
|
|
|
3
3
|
use async_trait::async_trait;
|
|
4
|
-
use futures::future::poll_fn;
|
|
4
|
+
use futures::{future::poll_fn, stream::FuturesUnordered, StreamExt};
|
|
5
5
|
use std::io;
|
|
6
6
|
use std::pin::Pin;
|
|
7
7
|
use std::sync::Arc;
|
|
8
8
|
use tokio::io::{AsyncRead, AsyncWrite};
|
|
9
|
-
use tokio::sync::{mpsc, oneshot};
|
|
9
|
+
use tokio::sync::{mpsc, oneshot, Mutex};
|
|
10
|
+
use tokio::task::JoinHandle;
|
|
10
11
|
use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt};
|
|
12
|
+
use tokio_util::sync::CancellationToken;
|
|
11
13
|
|
|
12
14
|
use crate::client::{OpenStream, PacketReceiver};
|
|
13
15
|
use crate::error::{Error, Result};
|
|
@@ -19,14 +21,30 @@ use crate::transport::{create_packet_channel, DEFAULT_CHANNEL_BUFFER};
|
|
|
19
21
|
type OpenResult = std::result::Result<::yamux::Stream, ::yamux::ConnectionError>;
|
|
20
22
|
type OpenRequest = oneshot::Sender<OpenResult>;
|
|
21
23
|
|
|
22
|
-
///
|
|
24
|
+
/// Opens StarPC packet streams over one owned yamux connection.
|
|
25
|
+
/// Clones share the connection. Dropping the last opener cancels its driver;
|
|
26
|
+
/// explicit close cancels and joins the driver before returning.
|
|
23
27
|
#[derive(Clone)]
|
|
24
28
|
pub struct YamuxStreamOpener {
|
|
29
|
+
driver: Arc<ClientDriver>,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
struct ClientDriver {
|
|
25
33
|
requests: mpsc::Sender<OpenRequest>,
|
|
34
|
+
task: Mutex<Option<JoinHandle<()>>>,
|
|
35
|
+
closed: CancellationToken,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
impl Drop for ClientDriver {
|
|
39
|
+
fn drop(&mut self) {
|
|
40
|
+
if let Some(task) = self.task.get_mut().take() {
|
|
41
|
+
task.abort();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
26
44
|
}
|
|
27
45
|
|
|
28
46
|
impl YamuxStreamOpener {
|
|
29
|
-
///
|
|
47
|
+
/// Creates a client-mode yamux opener over a Tokio transport.
|
|
30
48
|
pub fn client<T>(transport: T) -> Self
|
|
31
49
|
where
|
|
32
50
|
T: AsyncRead + AsyncWrite + Send + Unpin + 'static,
|
|
@@ -34,17 +52,41 @@ impl YamuxStreamOpener {
|
|
|
34
52
|
Self::client_with_config(transport, ::yamux::Config::default())
|
|
35
53
|
}
|
|
36
54
|
|
|
37
|
-
///
|
|
55
|
+
/// Creates a client-mode yamux opener with a custom configuration.
|
|
38
56
|
pub fn client_with_config<T>(transport: T, config: ::yamux::Config) -> Self
|
|
39
57
|
where
|
|
40
58
|
T: AsyncRead + AsyncWrite + Send + Unpin + 'static,
|
|
41
59
|
{
|
|
42
60
|
let (requests, request_rx) = mpsc::channel(DEFAULT_CHANNEL_BUFFER);
|
|
43
|
-
|
|
44
|
-
|
|
61
|
+
let closed = CancellationToken::new();
|
|
62
|
+
let task = spawn_client_driver(transport.compat(), config, request_rx, closed.clone());
|
|
63
|
+
Self {
|
|
64
|
+
driver: Arc::new(ClientDriver {
|
|
65
|
+
requests,
|
|
66
|
+
task: Mutex::new(Some(task)),
|
|
67
|
+
closed,
|
|
68
|
+
}),
|
|
69
|
+
}
|
|
45
70
|
}
|
|
46
71
|
|
|
47
|
-
///
|
|
72
|
+
/// Cancels the shared connection and joins its driver.
|
|
73
|
+
/// Concurrent callers wait for the same completed teardown.
|
|
74
|
+
pub async fn close(&self) {
|
|
75
|
+
let mut task = self.driver.task.lock().await;
|
|
76
|
+
if let Some(task) = task.as_mut() {
|
|
77
|
+
task.abort();
|
|
78
|
+
let _ = task.await;
|
|
79
|
+
}
|
|
80
|
+
task.take();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// Waits for the connection driver to end, including peer closure and protocol failure.
|
|
84
|
+
/// Observers do not prevent a concurrent explicit close from joining the driver.
|
|
85
|
+
pub async fn closed(&self) {
|
|
86
|
+
self.driver.closed.cancelled().await;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Creates a client-mode yamux opener over a WebSocket.
|
|
48
90
|
#[cfg(feature = "websocket")]
|
|
49
91
|
pub fn client_websocket<S>(socket: tokio_tungstenite::WebSocketStream<S>) -> Self
|
|
50
92
|
where
|
|
@@ -53,7 +95,7 @@ impl YamuxStreamOpener {
|
|
|
53
95
|
Self::client(crate::websocket::websocket_byte_stream(socket))
|
|
54
96
|
}
|
|
55
97
|
|
|
56
|
-
///
|
|
98
|
+
/// Creates a client-mode yamux WebSocket opener with a custom configuration.
|
|
57
99
|
#[cfg(feature = "websocket")]
|
|
58
100
|
pub fn client_websocket_with_config<S>(
|
|
59
101
|
socket: tokio_tungstenite::WebSocketStream<S>,
|
|
@@ -70,7 +112,8 @@ impl YamuxStreamOpener {
|
|
|
70
112
|
impl OpenStream for YamuxStreamOpener {
|
|
71
113
|
async fn open_stream(&self) -> Result<(Arc<dyn PacketWriter>, PacketReceiver)> {
|
|
72
114
|
let (tx, rx) = oneshot::channel();
|
|
73
|
-
self.
|
|
115
|
+
self.driver
|
|
116
|
+
.requests
|
|
74
117
|
.send(tx)
|
|
75
118
|
.await
|
|
76
119
|
.map_err(|_| Error::StreamClosed)?;
|
|
@@ -85,7 +128,7 @@ impl OpenStream for YamuxStreamOpener {
|
|
|
85
128
|
}
|
|
86
129
|
}
|
|
87
130
|
|
|
88
|
-
///
|
|
131
|
+
/// Serves yamux substreams within this connection's owned lifetime.
|
|
89
132
|
pub async fn handle_server_connection<I, T>(
|
|
90
133
|
server: &Server<I>,
|
|
91
134
|
transport: T,
|
|
@@ -98,18 +141,23 @@ where
|
|
|
98
141
|
let mut connection =
|
|
99
142
|
::yamux::Connection::new(transport.compat(), config, ::yamux::Mode::Server);
|
|
100
143
|
|
|
144
|
+
// Keep accepted calls within the connection's lifetime. Losing the connection
|
|
145
|
+
// or dropping this future drops every call and its cancellation guard.
|
|
146
|
+
let mut calls = FuturesUnordered::new();
|
|
101
147
|
loop {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
148
|
+
tokio::select! {
|
|
149
|
+
incoming = poll_fn(|cx| connection.poll_next_inbound(cx)) => {
|
|
150
|
+
match incoming {
|
|
151
|
+
Some(Ok(stream)) => calls.push(server.handle_stream(stream.compat())),
|
|
152
|
+
Some(Err(err)) => return Err(connection_error(err)),
|
|
153
|
+
None => return Ok(()),
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
Some(result) = calls.next(), if !calls.is_empty() => {
|
|
157
|
+
if let Err(err) = result {
|
|
158
|
+
server.report_error(err);
|
|
159
|
+
}
|
|
110
160
|
}
|
|
111
|
-
Some(Err(err)) => return Err(connection_error(err)),
|
|
112
|
-
None => return Ok(()),
|
|
113
161
|
}
|
|
114
162
|
}
|
|
115
163
|
}
|
|
@@ -126,21 +174,29 @@ fn spawn_client_driver<T>(
|
|
|
126
174
|
transport: T,
|
|
127
175
|
config: ::yamux::Config,
|
|
128
176
|
mut requests: mpsc::Receiver<OpenRequest>,
|
|
129
|
-
|
|
177
|
+
closed: CancellationToken,
|
|
178
|
+
) -> JoinHandle<()>
|
|
130
179
|
where
|
|
131
180
|
T: futures::io::AsyncRead + futures::io::AsyncWrite + Send + Unpin + 'static,
|
|
132
181
|
{
|
|
182
|
+
// Construct the guard before spawning so cancellation before the first poll also notifies.
|
|
183
|
+
let ended = closed.drop_guard();
|
|
133
184
|
tokio::spawn(async move {
|
|
185
|
+
let _ended = ended;
|
|
134
186
|
let mut connection = ::yamux::Connection::new(transport, config, ::yamux::Mode::Client);
|
|
135
187
|
let mut pending = None;
|
|
136
|
-
let mut requests_closed = false;
|
|
137
188
|
|
|
138
189
|
loop {
|
|
139
190
|
let event = poll_fn(|cx| {
|
|
140
|
-
if pending.is_none()
|
|
191
|
+
if pending.is_none() {
|
|
141
192
|
match Pin::new(&mut requests).poll_recv(cx) {
|
|
142
193
|
std::task::Poll::Ready(Some(request)) => pending = Some(request),
|
|
143
|
-
std::task::Poll::Ready(None) =>
|
|
194
|
+
std::task::Poll::Ready(None) => {
|
|
195
|
+
return std::task::Poll::Ready(DriverEvent::Closed {
|
|
196
|
+
pending: None,
|
|
197
|
+
err: None,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
144
200
|
std::task::Poll::Pending => {}
|
|
145
201
|
}
|
|
146
202
|
}
|
|
@@ -193,6 +249,6 @@ where
|
|
|
193
249
|
fn connection_error(err: ::yamux::ConnectionError) -> Error {
|
|
194
250
|
match err {
|
|
195
251
|
::yamux::ConnectionError::Io(err) => Error::Io(err),
|
|
196
|
-
other => Error::Io(io::Error::
|
|
252
|
+
other => Error::Io(io::Error::other(other.to_string())),
|
|
197
253
|
}
|
|
198
254
|
}
|
|
@@ -1,495 +0,0 @@
|
|
|
1
|
-
//! Integration tests for starpc, mirroring the Go/JS test patterns.
|
|
2
|
-
//!
|
|
3
|
-
//! These tests verify:
|
|
4
|
-
//! 1. Unary RPC
|
|
5
|
-
//! 2. Server streaming (5 messages)
|
|
6
|
-
//! 3. Client streaming
|
|
7
|
-
//! 4. Bidirectional streaming
|
|
8
|
-
//! 5. Error handling
|
|
9
|
-
//! 6. Wire format compatibility
|
|
10
|
-
|
|
11
|
-
use async_trait::async_trait;
|
|
12
|
-
use prost::Message;
|
|
13
|
-
use std::sync::Arc;
|
|
14
|
-
use std::time::Duration;
|
|
15
|
-
|
|
16
|
-
use starpc::error::{Error, Result};
|
|
17
|
-
use starpc::handler::Handler;
|
|
18
|
-
use starpc::invoker::Invoker;
|
|
19
|
-
use starpc::mux::Mux;
|
|
20
|
-
use starpc::server::Server;
|
|
21
|
-
use starpc::stream::{Stream, StreamExt};
|
|
22
|
-
use starpc::testing::{create_test_pair, SingleInMemoryOpener};
|
|
23
|
-
use starpc::Client;
|
|
24
|
-
|
|
25
|
-
// Simple EchoMsg for testing
|
|
26
|
-
#[derive(Clone, PartialEq, Message)]
|
|
27
|
-
struct EchoMsg {
|
|
28
|
-
#[prost(string, tag = "1")]
|
|
29
|
-
body: String,
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const BODY_TXT: &str = "hello world via starpc e2e test";
|
|
33
|
-
|
|
34
|
-
/// Echo server implementation matching Go's echo/server.go
|
|
35
|
-
struct EchoServer;
|
|
36
|
-
|
|
37
|
-
#[async_trait]
|
|
38
|
-
impl Invoker for EchoServer {
|
|
39
|
-
async fn invoke_method(
|
|
40
|
-
&self,
|
|
41
|
-
_service_id: &str,
|
|
42
|
-
method_id: &str,
|
|
43
|
-
stream: Box<dyn Stream>,
|
|
44
|
-
) -> (bool, Result<()>) {
|
|
45
|
-
match method_id {
|
|
46
|
-
"Echo" => (true, self.echo(stream).await),
|
|
47
|
-
"EchoServerStream" => (true, self.echo_server_stream(stream).await),
|
|
48
|
-
"EchoClientStream" => (true, self.echo_client_stream(stream).await),
|
|
49
|
-
"EchoBidiStream" => (true, self.echo_bidi_stream(stream).await),
|
|
50
|
-
_ => (false, Err(Error::Unimplemented)),
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
impl Handler for EchoServer {
|
|
56
|
-
fn service_id(&self) -> &'static str {
|
|
57
|
-
"echo.Echoer"
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
fn method_ids(&self) -> &'static [&'static str] {
|
|
61
|
-
&[
|
|
62
|
-
"Echo",
|
|
63
|
-
"EchoServerStream",
|
|
64
|
-
"EchoClientStream",
|
|
65
|
-
"EchoBidiStream",
|
|
66
|
-
]
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
impl EchoServer {
|
|
71
|
-
/// Unary echo - returns the same message
|
|
72
|
-
async fn echo(&self, stream: Box<dyn Stream>) -> Result<()> {
|
|
73
|
-
let msg: EchoMsg = stream.msg_recv().await?;
|
|
74
|
-
stream.msg_send(&msg).await?;
|
|
75
|
-
Ok(())
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/// Server streaming - sends 5 copies of the message
|
|
79
|
-
async fn echo_server_stream(&self, stream: Box<dyn Stream>) -> Result<()> {
|
|
80
|
-
let msg: EchoMsg = stream.msg_recv().await?;
|
|
81
|
-
|
|
82
|
-
// Send 5 responses with delays
|
|
83
|
-
for _ in 0..5 {
|
|
84
|
-
if stream.context().is_cancelled() {
|
|
85
|
-
return Err(Error::Cancelled);
|
|
86
|
-
}
|
|
87
|
-
stream.msg_send(&msg).await?;
|
|
88
|
-
tokio::time::sleep(Duration::from_millis(10)).await;
|
|
89
|
-
}
|
|
90
|
-
Ok(())
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/// Client streaming - returns first message received
|
|
94
|
-
async fn echo_client_stream(&self, stream: Box<dyn Stream>) -> Result<()> {
|
|
95
|
-
let msg: EchoMsg = stream.msg_recv().await?;
|
|
96
|
-
stream.msg_send(&msg).await?;
|
|
97
|
-
Ok(())
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/// Bidirectional streaming - server sends first, then echoes all messages
|
|
101
|
-
async fn echo_bidi_stream(&self, stream: Box<dyn Stream>) -> Result<()> {
|
|
102
|
-
// Server sends initial message
|
|
103
|
-
let initial = EchoMsg {
|
|
104
|
-
body: "hello from server".to_string(),
|
|
105
|
-
};
|
|
106
|
-
stream.msg_send(&initial).await?;
|
|
107
|
-
|
|
108
|
-
// Echo all received messages
|
|
109
|
-
loop {
|
|
110
|
-
match stream.msg_recv::<EchoMsg>().await {
|
|
111
|
-
Ok(msg) => {
|
|
112
|
-
if msg.body.is_empty() {
|
|
113
|
-
return Err(Error::Remote("got message with empty body".to_string()));
|
|
114
|
-
}
|
|
115
|
-
stream.msg_send(&msg).await?;
|
|
116
|
-
}
|
|
117
|
-
Err(Error::StreamClosed) => break,
|
|
118
|
-
Err(e) => return Err(e),
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
Ok(())
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/// Test infrastructure: creates connected client and server
|
|
126
|
-
async fn setup_e2e() -> (starpc::SrpcClient<SingleInMemoryOpener>, tokio::task::JoinHandle<()>) {
|
|
127
|
-
let (opener, server_stream) = create_test_pair();
|
|
128
|
-
|
|
129
|
-
// Set up the server
|
|
130
|
-
let mux = Arc::new(Mux::new());
|
|
131
|
-
mux.register(Arc::new(EchoServer)).unwrap();
|
|
132
|
-
let server = Server::with_arc(mux);
|
|
133
|
-
|
|
134
|
-
// Spawn server handler
|
|
135
|
-
let server_handle = tokio::spawn(async move {
|
|
136
|
-
let _ = server.handle_stream(server_stream).await;
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
// Create client
|
|
140
|
-
let client = starpc::SrpcClient::new(opener);
|
|
141
|
-
|
|
142
|
-
(client, server_handle)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// ============================================================================
|
|
146
|
-
// Tests matching Go's server_test.go
|
|
147
|
-
// ============================================================================
|
|
148
|
-
|
|
149
|
-
#[tokio::test]
|
|
150
|
-
async fn test_e2e_unary() {
|
|
151
|
-
let (client, server_handle) = setup_e2e().await;
|
|
152
|
-
|
|
153
|
-
// Make unary call
|
|
154
|
-
let request = EchoMsg {
|
|
155
|
-
body: BODY_TXT.to_string(),
|
|
156
|
-
};
|
|
157
|
-
let response: EchoMsg = client
|
|
158
|
-
.exec_call("echo.Echoer", "Echo", &request)
|
|
159
|
-
.await
|
|
160
|
-
.expect("exec_call failed");
|
|
161
|
-
|
|
162
|
-
assert_eq!(response.body, BODY_TXT);
|
|
163
|
-
|
|
164
|
-
server_handle.abort();
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
#[tokio::test]
|
|
168
|
-
async fn test_e2e_server_stream() {
|
|
169
|
-
let (client, server_handle) = setup_e2e().await;
|
|
170
|
-
|
|
171
|
-
// Send request and open stream
|
|
172
|
-
let request = EchoMsg {
|
|
173
|
-
body: BODY_TXT.to_string(),
|
|
174
|
-
};
|
|
175
|
-
let data = request.encode_to_vec();
|
|
176
|
-
let stream = client
|
|
177
|
-
.new_stream("echo.Echoer", "EchoServerStream", Some(&data))
|
|
178
|
-
.await
|
|
179
|
-
.expect("new_stream failed");
|
|
180
|
-
|
|
181
|
-
// Close send side
|
|
182
|
-
stream.close_send().await.expect("close_send failed");
|
|
183
|
-
|
|
184
|
-
// Expect to receive 5 messages
|
|
185
|
-
let expected_rx = 5;
|
|
186
|
-
let mut received = 0;
|
|
187
|
-
|
|
188
|
-
loop {
|
|
189
|
-
match stream.msg_recv::<EchoMsg>().await {
|
|
190
|
-
Ok(msg) => {
|
|
191
|
-
assert_eq!(msg.body, BODY_TXT);
|
|
192
|
-
received += 1;
|
|
193
|
-
}
|
|
194
|
-
Err(Error::StreamClosed) => break,
|
|
195
|
-
Err(e) => panic!("unexpected error: {}", e),
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
assert_eq!(
|
|
200
|
-
received, expected_rx,
|
|
201
|
-
"expected {} messages, got {}",
|
|
202
|
-
expected_rx, received
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
server_handle.abort();
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
#[tokio::test]
|
|
209
|
-
async fn test_e2e_client_stream() {
|
|
210
|
-
let (client, server_handle) = setup_e2e().await;
|
|
211
|
-
|
|
212
|
-
// Open stream without initial message
|
|
213
|
-
let stream = client
|
|
214
|
-
.new_stream("echo.Echoer", "EchoClientStream", None)
|
|
215
|
-
.await
|
|
216
|
-
.expect("new_stream failed");
|
|
217
|
-
|
|
218
|
-
// Send a message
|
|
219
|
-
let request = EchoMsg {
|
|
220
|
-
body: BODY_TXT.to_string(),
|
|
221
|
-
};
|
|
222
|
-
stream.msg_send(&request).await.expect("msg_send failed");
|
|
223
|
-
|
|
224
|
-
// Close send side
|
|
225
|
-
stream.close_send().await.expect("close_send failed");
|
|
226
|
-
|
|
227
|
-
// Receive response
|
|
228
|
-
let response: EchoMsg = stream.msg_recv().await.expect("msg_recv failed");
|
|
229
|
-
assert_eq!(response.body, BODY_TXT);
|
|
230
|
-
|
|
231
|
-
stream.close().await.ok();
|
|
232
|
-
server_handle.abort();
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
#[tokio::test]
|
|
236
|
-
async fn test_e2e_bidi_stream() {
|
|
237
|
-
let (client, server_handle) = setup_e2e().await;
|
|
238
|
-
|
|
239
|
-
// Open bidirectional stream
|
|
240
|
-
let stream = client
|
|
241
|
-
.new_stream("echo.Echoer", "EchoBidiStream", None)
|
|
242
|
-
.await
|
|
243
|
-
.expect("new_stream failed");
|
|
244
|
-
|
|
245
|
-
// Receive server's initial message
|
|
246
|
-
let initial: EchoMsg = stream.msg_recv().await.expect("msg_recv failed");
|
|
247
|
-
assert_eq!(initial.body, "hello from server");
|
|
248
|
-
|
|
249
|
-
// Send a message from client
|
|
250
|
-
let client_msg = EchoMsg {
|
|
251
|
-
body: "hello from client".to_string(),
|
|
252
|
-
};
|
|
253
|
-
stream.msg_send(&client_msg).await.expect("msg_send failed");
|
|
254
|
-
|
|
255
|
-
// Receive echoed message
|
|
256
|
-
let echoed: EchoMsg = stream.msg_recv().await.expect("msg_recv failed");
|
|
257
|
-
assert_eq!(echoed.body, "hello from client");
|
|
258
|
-
|
|
259
|
-
// Close the stream
|
|
260
|
-
stream.close().await.expect("close failed");
|
|
261
|
-
server_handle.abort();
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
#[tokio::test]
|
|
265
|
-
async fn test_e2e_multiple_bidi_messages() {
|
|
266
|
-
let (client, server_handle) = setup_e2e().await;
|
|
267
|
-
|
|
268
|
-
let stream = client
|
|
269
|
-
.new_stream("echo.Echoer", "EchoBidiStream", None)
|
|
270
|
-
.await
|
|
271
|
-
.expect("new_stream failed");
|
|
272
|
-
|
|
273
|
-
// Receive server's initial message
|
|
274
|
-
let _: EchoMsg = stream.msg_recv().await.expect("initial recv failed");
|
|
275
|
-
|
|
276
|
-
// Send and receive multiple messages
|
|
277
|
-
for i in 0..10 {
|
|
278
|
-
let msg = EchoMsg {
|
|
279
|
-
body: format!("message {}", i),
|
|
280
|
-
};
|
|
281
|
-
stream.msg_send(&msg).await.expect("msg_send failed");
|
|
282
|
-
|
|
283
|
-
let echoed: EchoMsg = stream.msg_recv().await.expect("msg_recv failed");
|
|
284
|
-
assert_eq!(echoed.body, format!("message {}", i));
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
stream.close().await.expect("close failed");
|
|
288
|
-
server_handle.abort();
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
#[tokio::test]
|
|
292
|
-
async fn test_e2e_unary_empty_message() {
|
|
293
|
-
let (client, server_handle) = setup_e2e().await;
|
|
294
|
-
|
|
295
|
-
// Send empty message
|
|
296
|
-
let request = EchoMsg {
|
|
297
|
-
body: String::new(),
|
|
298
|
-
};
|
|
299
|
-
let response: EchoMsg = client
|
|
300
|
-
.exec_call("echo.Echoer", "Echo", &request)
|
|
301
|
-
.await
|
|
302
|
-
.expect("exec_call failed");
|
|
303
|
-
|
|
304
|
-
assert_eq!(response.body, "");
|
|
305
|
-
|
|
306
|
-
server_handle.abort();
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
#[tokio::test]
|
|
310
|
-
async fn test_e2e_unimplemented_method() {
|
|
311
|
-
let (client, server_handle) = setup_e2e().await;
|
|
312
|
-
|
|
313
|
-
let request = EchoMsg {
|
|
314
|
-
body: "test".to_string(),
|
|
315
|
-
};
|
|
316
|
-
let result: Result<EchoMsg> = client
|
|
317
|
-
.exec_call("echo.Echoer", "NonExistentMethod", &request)
|
|
318
|
-
.await;
|
|
319
|
-
|
|
320
|
-
assert!(result.is_err());
|
|
321
|
-
|
|
322
|
-
server_handle.abort();
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
#[tokio::test]
|
|
326
|
-
async fn test_codec_wire_format() {
|
|
327
|
-
use starpc::codec::PacketCodec;
|
|
328
|
-
use starpc::proto::{packet::Body, CallData, CallStart, Packet};
|
|
329
|
-
use tokio_util::codec::{Decoder, Encoder};
|
|
330
|
-
|
|
331
|
-
let mut codec = PacketCodec::new();
|
|
332
|
-
let mut buf = bytes::BytesMut::new();
|
|
333
|
-
|
|
334
|
-
// Test CallStart encoding
|
|
335
|
-
let call_start = Packet {
|
|
336
|
-
body: Some(Body::CallStart(CallStart {
|
|
337
|
-
rpc_service: "test.Service".into(),
|
|
338
|
-
rpc_method: "TestMethod".into(),
|
|
339
|
-
data: vec![1, 2, 3, 4],
|
|
340
|
-
data_is_zero: false,
|
|
341
|
-
})),
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
codec
|
|
345
|
-
.encode(call_start.clone(), &mut buf)
|
|
346
|
-
.expect("encode failed");
|
|
347
|
-
|
|
348
|
-
// Verify length prefix (little-endian u32)
|
|
349
|
-
let len = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]) as usize;
|
|
350
|
-
assert_eq!(len, buf.len() - 4);
|
|
351
|
-
|
|
352
|
-
// Decode and verify
|
|
353
|
-
let decoded = codec
|
|
354
|
-
.decode(&mut buf)
|
|
355
|
-
.expect("decode failed")
|
|
356
|
-
.expect("no packet");
|
|
357
|
-
assert_eq!(decoded, call_start);
|
|
358
|
-
|
|
359
|
-
// Test CallData encoding
|
|
360
|
-
buf.clear();
|
|
361
|
-
let call_data = Packet {
|
|
362
|
-
body: Some(Body::CallData(CallData {
|
|
363
|
-
data: vec![5, 6, 7, 8],
|
|
364
|
-
data_is_zero: false,
|
|
365
|
-
complete: true,
|
|
366
|
-
error: String::new(),
|
|
367
|
-
})),
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
codec
|
|
371
|
-
.encode(call_data.clone(), &mut buf)
|
|
372
|
-
.expect("encode failed");
|
|
373
|
-
let decoded = codec
|
|
374
|
-
.decode(&mut buf)
|
|
375
|
-
.expect("decode failed")
|
|
376
|
-
.expect("no packet");
|
|
377
|
-
assert_eq!(decoded, call_data);
|
|
378
|
-
|
|
379
|
-
// Test empty data with data_is_zero flag
|
|
380
|
-
buf.clear();
|
|
381
|
-
let empty_data = Packet {
|
|
382
|
-
body: Some(Body::CallData(CallData {
|
|
383
|
-
data: vec![],
|
|
384
|
-
data_is_zero: true,
|
|
385
|
-
complete: false,
|
|
386
|
-
error: String::new(),
|
|
387
|
-
})),
|
|
388
|
-
};
|
|
389
|
-
|
|
390
|
-
codec
|
|
391
|
-
.encode(empty_data.clone(), &mut buf)
|
|
392
|
-
.expect("encode failed");
|
|
393
|
-
let decoded = codec
|
|
394
|
-
.decode(&mut buf)
|
|
395
|
-
.expect("decode failed")
|
|
396
|
-
.expect("no packet");
|
|
397
|
-
assert_eq!(decoded, empty_data);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
#[tokio::test]
|
|
401
|
-
async fn test_packet_validation() {
|
|
402
|
-
use starpc::packet::Validate;
|
|
403
|
-
use starpc::proto::{packet::Body, CallData, CallStart, Packet};
|
|
404
|
-
|
|
405
|
-
// Valid CallStart
|
|
406
|
-
let valid_start = Packet {
|
|
407
|
-
body: Some(Body::CallStart(CallStart {
|
|
408
|
-
rpc_service: "svc".into(),
|
|
409
|
-
rpc_method: "method".into(),
|
|
410
|
-
data: vec![],
|
|
411
|
-
data_is_zero: false,
|
|
412
|
-
})),
|
|
413
|
-
};
|
|
414
|
-
assert!(valid_start.validate().is_ok());
|
|
415
|
-
|
|
416
|
-
// Invalid CallStart - empty method
|
|
417
|
-
let invalid_start = Packet {
|
|
418
|
-
body: Some(Body::CallStart(CallStart {
|
|
419
|
-
rpc_service: "svc".into(),
|
|
420
|
-
rpc_method: String::new(),
|
|
421
|
-
data: vec![],
|
|
422
|
-
data_is_zero: false,
|
|
423
|
-
})),
|
|
424
|
-
};
|
|
425
|
-
assert!(invalid_start.validate().is_err());
|
|
426
|
-
|
|
427
|
-
// Valid CallData with data
|
|
428
|
-
let valid_data = Packet {
|
|
429
|
-
body: Some(Body::CallData(CallData {
|
|
430
|
-
data: vec![1, 2, 3],
|
|
431
|
-
data_is_zero: false,
|
|
432
|
-
complete: false,
|
|
433
|
-
error: String::new(),
|
|
434
|
-
})),
|
|
435
|
-
};
|
|
436
|
-
assert!(valid_data.validate().is_ok());
|
|
437
|
-
|
|
438
|
-
// Invalid CallData - empty everything
|
|
439
|
-
let invalid_data = Packet {
|
|
440
|
-
body: Some(Body::CallData(CallData {
|
|
441
|
-
data: vec![],
|
|
442
|
-
data_is_zero: false,
|
|
443
|
-
complete: false,
|
|
444
|
-
error: String::new(),
|
|
445
|
-
})),
|
|
446
|
-
};
|
|
447
|
-
assert!(invalid_data.validate().is_err());
|
|
448
|
-
|
|
449
|
-
// Empty packet
|
|
450
|
-
let empty_packet = Packet { body: None };
|
|
451
|
-
assert!(empty_packet.validate().is_err());
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
#[tokio::test]
|
|
455
|
-
async fn test_mux_registration_and_lookup() {
|
|
456
|
-
let mux = Mux::new();
|
|
457
|
-
|
|
458
|
-
// Register handler
|
|
459
|
-
mux.register(Arc::new(EchoServer)).unwrap();
|
|
460
|
-
|
|
461
|
-
// Check service exists
|
|
462
|
-
assert!(mux.has_service("echo.Echoer"));
|
|
463
|
-
assert!(!mux.has_service("nonexistent"));
|
|
464
|
-
|
|
465
|
-
// Check methods exist
|
|
466
|
-
assert!(mux.has_service_method("echo.Echoer", "Echo"));
|
|
467
|
-
assert!(mux.has_service_method("echo.Echoer", "EchoServerStream"));
|
|
468
|
-
assert!(!mux.has_service_method("echo.Echoer", "NonExistent"));
|
|
469
|
-
|
|
470
|
-
// Empty strings should return false
|
|
471
|
-
assert!(!mux.has_service(""));
|
|
472
|
-
assert!(!mux.has_service_method("", "Echo"));
|
|
473
|
-
assert!(!mux.has_service_method("echo.Echoer", ""));
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
#[tokio::test]
|
|
477
|
-
async fn test_error_types() {
|
|
478
|
-
use starpc::error::codes;
|
|
479
|
-
|
|
480
|
-
// Test error predicates
|
|
481
|
-
assert!(Error::Aborted.is_abort());
|
|
482
|
-
assert!(Error::Cancelled.is_abort());
|
|
483
|
-
assert!(!Error::StreamClosed.is_abort());
|
|
484
|
-
|
|
485
|
-
assert!(Error::StreamClosed.is_closed());
|
|
486
|
-
assert!(Error::Cancelled.is_closed());
|
|
487
|
-
|
|
488
|
-
assert!(Error::StreamIdle.is_timeout());
|
|
489
|
-
|
|
490
|
-
assert!(Error::Unimplemented.is_unimplemented());
|
|
491
|
-
|
|
492
|
-
// Test error codes
|
|
493
|
-
assert_eq!(codes::ERR_RPC_ABORT, "ERR_RPC_ABORT");
|
|
494
|
-
assert_eq!(codes::ERR_STREAM_IDLE, "ERR_STREAM_IDLE");
|
|
495
|
-
}
|