starpc 0.46.0 → 0.46.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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,67 @@
1
+ import net from 'net';
2
+ import { pipe } from 'it-pipe';
3
+ import { pushable } from 'it-pushable';
4
+ import { Client } from '../../srpc/client.js';
5
+ import { parseLengthPrefixTransform, prependLengthPrefixTransform, } from '../../srpc/packet.js';
6
+ import { combineUint8ArrayListTransform } from '../../srpc/array-list.js';
7
+ import { runClientTest } from '../../echo/client-test.js';
8
+ // tcpSocketToPacketStream wraps a Node.js TCP socket into a PacketStream.
9
+ function tcpSocketToPacketStream(socket) {
10
+ const socketSource = async function* () {
11
+ const source = pushable({ objectMode: true });
12
+ socket.on('data', (data) => {
13
+ source.push(new Uint8Array(data));
14
+ });
15
+ socket.on('end', () => source.end());
16
+ socket.on('error', (err) => source.end(err));
17
+ socket.on('close', () => source.end());
18
+ yield* pipe(source, parseLengthPrefixTransform(), combineUint8ArrayListTransform());
19
+ };
20
+ return {
21
+ source: socketSource(),
22
+ sink: async (source) => {
23
+ for await (const chunk of pipe(source, prependLengthPrefixTransform())) {
24
+ const data = chunk instanceof Uint8Array ? chunk : chunk.subarray();
25
+ await new Promise((resolve, reject) => {
26
+ socket.write(data, (err) => {
27
+ if (err)
28
+ reject(err);
29
+ else
30
+ resolve();
31
+ });
32
+ });
33
+ }
34
+ socket.end();
35
+ },
36
+ };
37
+ }
38
+ async function main() {
39
+ const addr = process.argv[2];
40
+ if (!addr) {
41
+ console.error('usage: ts-client <host:port>');
42
+ process.exit(1);
43
+ }
44
+ const [host, portStr] = addr.split(':');
45
+ const port = parseInt(portStr, 10);
46
+ const openStream = async () => {
47
+ return new Promise((resolve, reject) => {
48
+ const socket = net.connect(port, host, () => {
49
+ resolve(tcpSocketToPacketStream(socket));
50
+ });
51
+ socket.on('error', reject);
52
+ });
53
+ };
54
+ const client = new Client(openStream);
55
+ console.log('Running client test via TCP...');
56
+ await runClientTest(client);
57
+ console.log('All tests passed.');
58
+ process.exit(0);
59
+ }
60
+ process.on('unhandledRejection', (ev) => {
61
+ console.error('Unhandled rejection', ev);
62
+ process.exit(1);
63
+ });
64
+ main().catch((err) => {
65
+ console.error('Error:', err);
66
+ process.exit(1);
67
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,60 @@
1
+ import net from 'net';
2
+ import { pipe } from 'it-pipe';
3
+ import { pushable } from 'it-pushable';
4
+ import { createMux, createHandler, Server } from '../../srpc/index.js';
5
+ import { parseLengthPrefixTransform, prependLengthPrefixTransform, } from '../../srpc/packet.js';
6
+ import { combineUint8ArrayListTransform } from '../../srpc/array-list.js';
7
+ import { EchoerServer } from '../../echo/server.js';
8
+ import { EchoerDefinition } from '../../echo/echo_srpc.pb.js';
9
+ // tcpSocketToPacketStream wraps a Node.js TCP socket into a PacketStream.
10
+ // Each Uint8Array in source/sink is one packet (no length prefix).
11
+ function tcpSocketToPacketStream(socket) {
12
+ // Source: read from socket, strip length prefix, yield individual packets.
13
+ const socketSource = async function* () {
14
+ const source = pushable({ objectMode: true });
15
+ socket.on('data', (data) => {
16
+ source.push(new Uint8Array(data));
17
+ });
18
+ socket.on('end', () => source.end());
19
+ socket.on('error', (err) => source.end(err));
20
+ socket.on('close', () => source.end());
21
+ yield* pipe(source, parseLengthPrefixTransform(), combineUint8ArrayListTransform());
22
+ };
23
+ return {
24
+ source: socketSource(),
25
+ sink: async (source) => {
26
+ for await (const chunk of pipe(source, prependLengthPrefixTransform())) {
27
+ const data = chunk instanceof Uint8Array ? chunk : chunk.subarray();
28
+ await new Promise((resolve, reject) => {
29
+ socket.write(data, (err) => {
30
+ if (err)
31
+ reject(err);
32
+ else
33
+ resolve();
34
+ });
35
+ });
36
+ }
37
+ socket.end();
38
+ },
39
+ };
40
+ }
41
+ const mux = createMux();
42
+ const server = new Server(mux.lookupMethod);
43
+ const echoer = new EchoerServer(server);
44
+ mux.register(createHandler(EchoerDefinition, echoer));
45
+ const tcpServer = net.createServer((socket) => {
46
+ const stream = tcpSocketToPacketStream(socket);
47
+ server.handlePacketStream(stream);
48
+ });
49
+ tcpServer.listen(0, '127.0.0.1', () => {
50
+ const addr = tcpServer.address();
51
+ console.log(`LISTENING ${addr.address}:${addr.port}`);
52
+ });
53
+ process.on('SIGINT', () => {
54
+ tcpServer.close();
55
+ process.exit(0);
56
+ });
57
+ process.on('SIGTERM', () => {
58
+ tcpServer.close();
59
+ process.exit(0);
60
+ });
package/echo/Cargo.toml CHANGED
@@ -10,6 +10,14 @@ publish = false
10
10
  name = "echo-example"
11
11
  path = "main.rs"
12
12
 
13
+ [[bin]]
14
+ name = "integration-server"
15
+ path = "integration_server.rs"
16
+
17
+ [[bin]]
18
+ name = "integration-client"
19
+ path = "integration_client.rs"
20
+
13
21
  [dependencies]
14
22
  starpc = { workspace = true }
15
23
  prost = { workspace = true }
@@ -182,9 +182,9 @@ public:
182
182
 
183
183
  starpc::Error RpcStream(echo::SRPCEchoer_RpcStreamStream *strm) override {
184
184
  // Wrap stream to implement rpcstream::RpcStream interface
185
- RpcStreamAdapter adapter(strm);
185
+ auto adapter = std::make_shared<RpcStreamAdapter>(strm);
186
186
  return rpcstream::HandleRpcStream(
187
- &adapter, [this](const std::string &component_id) {
187
+ adapter, [this](const std::string &component_id) {
188
188
  if (!rpc_stream_mux_) {
189
189
  return std::make_tuple(static_cast<starpc::Invoker *>(nullptr),
190
190
  std::function<void()>(),
@@ -0,0 +1,168 @@
1
+ #[allow(dead_code)]
2
+ mod gen;
3
+
4
+ use std::sync::Arc;
5
+
6
+ use async_trait::async_trait;
7
+ use starpc::client::{OpenStream, PacketReceiver, SrpcClient};
8
+ use starpc::rpc::PacketWriter;
9
+ use starpc::transport::create_packet_channel;
10
+ use starpc::{Error, Result};
11
+ // Use Error::Remote for test assertion errors since there's no Error::Remote.
12
+ use tokio::net::TcpStream;
13
+
14
+ use gen::{EchoMsg, EchoerClient, EchoerClientImpl};
15
+
16
+ const BODY_TXT: &str = "hello world via starpc cross-language e2e test";
17
+
18
+ /// Opens a new TCP connection per RPC call.
19
+ struct TcpStreamOpener {
20
+ addr: String,
21
+ }
22
+
23
+ impl TcpStreamOpener {
24
+ fn new(addr: String) -> Self {
25
+ Self { addr }
26
+ }
27
+ }
28
+
29
+ #[async_trait]
30
+ impl OpenStream for TcpStreamOpener {
31
+ async fn open_stream(&self) -> Result<(Arc<dyn PacketWriter>, PacketReceiver)> {
32
+ let stream = TcpStream::connect(&self.addr).await?;
33
+ let (read, write) = tokio::io::split(stream);
34
+ Ok(create_packet_channel(read, write))
35
+ }
36
+ }
37
+
38
+ #[tokio::main]
39
+ async fn main() {
40
+ let addr = std::env::args()
41
+ .nth(1)
42
+ .expect("usage: integration-client <addr>");
43
+
44
+ let opener = TcpStreamOpener::new(addr);
45
+ let client = SrpcClient::new(opener);
46
+ let echo = EchoerClientImpl::new(client);
47
+
48
+ if let Err(e) = test_unary(&echo).await {
49
+ eprintln!("unary test failed: {}", e);
50
+ std::process::exit(1);
51
+ }
52
+
53
+ if let Err(e) = test_server_stream(&echo).await {
54
+ eprintln!("server stream test failed: {}", e);
55
+ std::process::exit(1);
56
+ }
57
+
58
+ if let Err(e) = test_client_stream(&echo).await {
59
+ eprintln!("client stream test failed: {}", e);
60
+ std::process::exit(1);
61
+ }
62
+
63
+ if let Err(e) = test_bidi_stream(&echo).await {
64
+ eprintln!("bidi stream test failed: {}", e);
65
+ std::process::exit(1);
66
+ }
67
+
68
+ println!("All tests passed.");
69
+ }
70
+
71
+ async fn test_unary(echo: &dyn EchoerClient) -> Result<()> {
72
+ println!("Testing Unary RPC...");
73
+ let req = EchoMsg {
74
+ body: BODY_TXT.to_string(),
75
+ };
76
+ let resp = echo.echo(&req).await?;
77
+ if resp.body != BODY_TXT {
78
+ return Err(Error::Remote(format!(
79
+ "expected {:?} got {:?}",
80
+ BODY_TXT, resp.body
81
+ )));
82
+ }
83
+ println!(" PASSED");
84
+ Ok(())
85
+ }
86
+
87
+ async fn test_server_stream(echo: &dyn EchoerClient) -> Result<()> {
88
+ println!("Testing ServerStream RPC...");
89
+ let req = EchoMsg {
90
+ body: BODY_TXT.to_string(),
91
+ };
92
+ let stream = echo.echo_server_stream(&req).await?;
93
+ let mut received = 0;
94
+ loop {
95
+ match stream.recv().await {
96
+ Ok(msg) => {
97
+ if msg.body != BODY_TXT {
98
+ return Err(Error::Remote(format!(
99
+ "expected {:?} got {:?}",
100
+ BODY_TXT, msg.body
101
+ )));
102
+ }
103
+ received += 1;
104
+ }
105
+ Err(Error::StreamClosed) => break,
106
+ Err(e) => return Err(e),
107
+ }
108
+ }
109
+ if received != 5 {
110
+ return Err(Error::Remote(format!(
111
+ "expected 5 messages, got {}",
112
+ received
113
+ )));
114
+ }
115
+ println!(" PASSED");
116
+ Ok(())
117
+ }
118
+
119
+ async fn test_client_stream(echo: &dyn EchoerClient) -> Result<()> {
120
+ println!("Testing ClientStream RPC...");
121
+ let stream = echo.echo_client_stream().await?;
122
+ stream
123
+ .send(&EchoMsg {
124
+ body: BODY_TXT.to_string(),
125
+ })
126
+ .await?;
127
+ let resp = stream.close_and_recv().await?;
128
+ if resp.body != BODY_TXT {
129
+ return Err(Error::Remote(format!(
130
+ "expected {:?} got {:?}",
131
+ BODY_TXT, resp.body
132
+ )));
133
+ }
134
+ println!(" PASSED");
135
+ Ok(())
136
+ }
137
+
138
+ async fn test_bidi_stream(echo: &dyn EchoerClient) -> Result<()> {
139
+ println!("Testing BidiStream RPC...");
140
+ let stream = echo.echo_bidi_stream().await?;
141
+
142
+ // Receive initial message from server.
143
+ let msg = stream.recv().await?;
144
+ if msg.body != "hello from server" {
145
+ return Err(Error::Remote(format!(
146
+ "expected {:?} got {:?}",
147
+ "hello from server", msg.body
148
+ )));
149
+ }
150
+
151
+ // Send a message and expect echo.
152
+ stream
153
+ .send(&EchoMsg {
154
+ body: BODY_TXT.to_string(),
155
+ })
156
+ .await?;
157
+ let resp = stream.recv().await?;
158
+ if resp.body != BODY_TXT {
159
+ return Err(Error::Remote(format!(
160
+ "expected {:?} got {:?}",
161
+ BODY_TXT, resp.body
162
+ )));
163
+ }
164
+
165
+ stream.close().await?;
166
+ println!(" PASSED");
167
+ Ok(())
168
+ }
@@ -0,0 +1,87 @@
1
+ #[allow(dead_code)]
2
+ mod gen;
3
+
4
+ use std::sync::Arc;
5
+
6
+ use async_trait::async_trait;
7
+ use starpc::{Error, Mux, Result, Server, Stream, StreamExt};
8
+ use tokio::net::TcpListener;
9
+
10
+ use gen::{EchoMsg, EchoerHandler, EchoerServer, Empty};
11
+
12
+ struct EchoServerImpl;
13
+
14
+ #[async_trait]
15
+ impl EchoerServer for EchoServerImpl {
16
+ async fn echo(&self, request: EchoMsg) -> Result<EchoMsg> {
17
+ Ok(EchoMsg {
18
+ body: request.body,
19
+ })
20
+ }
21
+
22
+ async fn echo_server_stream(
23
+ &self,
24
+ request: EchoMsg,
25
+ stream: Box<dyn Stream>,
26
+ ) -> Result<()> {
27
+ for _ in 0..5 {
28
+ let response = EchoMsg {
29
+ body: request.body.clone(),
30
+ };
31
+ stream.msg_send(&response).await?;
32
+ }
33
+ Ok(())
34
+ }
35
+
36
+ async fn echo_client_stream(&self, stream: &dyn Stream) -> Result<EchoMsg> {
37
+ match stream.msg_recv::<EchoMsg>().await {
38
+ Ok(msg) => Ok(msg),
39
+ Err(e) => Err(e),
40
+ }
41
+ }
42
+
43
+ async fn echo_bidi_stream(&self, stream: Box<dyn Stream>) -> Result<()> {
44
+ // Send initial message (matches Go server behavior).
45
+ stream
46
+ .msg_send(&EchoMsg {
47
+ body: "hello from server".to_string(),
48
+ })
49
+ .await?;
50
+ loop {
51
+ match stream.msg_recv::<EchoMsg>().await {
52
+ Ok(msg) => {
53
+ stream.msg_send(&msg).await?;
54
+ }
55
+ Err(Error::StreamClosed) => break,
56
+ Err(e) => return Err(e),
57
+ }
58
+ }
59
+ Ok(())
60
+ }
61
+
62
+ async fn rpc_stream(&self, _stream: Box<dyn Stream>) -> Result<()> {
63
+ Err(Error::Unimplemented)
64
+ }
65
+
66
+ async fn do_nothing(&self, _request: Empty) -> Result<Empty> {
67
+ Ok(Empty {})
68
+ }
69
+ }
70
+
71
+ #[tokio::main]
72
+ async fn main() -> Result<()> {
73
+ let listener = TcpListener::bind("127.0.0.1:0").await?;
74
+ let addr = listener.local_addr()?;
75
+ println!("LISTENING {}", addr);
76
+
77
+ let mux = Arc::new(Mux::new());
78
+ mux.register(Arc::new(EchoerHandler::new(EchoServerImpl)))?;
79
+
80
+ loop {
81
+ let (stream, _) = listener.accept().await?;
82
+ let server = Server::with_arc(mux.clone());
83
+ tokio::spawn(async move {
84
+ let _ = server.handle_stream(stream).await;
85
+ });
86
+ }
87
+ }