starpc 0.46.1 → 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.
- package/dist/integration/cross-language/ts-client.d.ts +1 -0
- package/dist/integration/cross-language/ts-client.js +67 -0
- package/dist/integration/cross-language/ts-server.d.ts +1 -0
- package/dist/integration/cross-language/ts-server.js +60 -0
- package/echo/Cargo.toml +8 -0
- package/echo/integration_client.rs +168 -0
- package/echo/integration_server.rs +87 -0
- package/integration/cross-language/cpp-client.cpp +448 -0
- package/integration/cross-language/cpp-server.cpp +221 -0
- package/integration/cross-language/go-client/main.go +151 -0
- package/integration/cross-language/go-server/main.go +47 -0
- package/integration/cross-language/run.bash +193 -0
- package/integration/cross-language/ts-client.ts +84 -0
- package/integration/cross-language/ts-server.ts +75 -0
- package/integration/integration.bash +2 -0
- package/package.json +8 -3
- package/srpc/client-invoker.go +1 -1
|
@@ -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 }
|
|
@@ -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
|
+
}
|