starpc 0.3.3 → 0.3.6
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 +41 -31
- package/dist/echo/echo.js +0 -1
- package/dist/srpc/broadcast-channel.js +0 -10
- package/dist/srpc/client.js +2 -6
- package/dist/srpc/common-rpc.js +0 -14
- package/dist/srpc/conn-duplex.js +1 -3
- package/dist/srpc/conn.js +0 -4
- package/dist/srpc/handler.js +12 -14
- package/dist/srpc/index.d.ts +0 -1
- package/dist/srpc/index.js +0 -1
- package/dist/srpc/message-port.js +0 -10
- package/dist/srpc/mux.js +4 -2
- package/dist/srpc/server-rpc.js +0 -2
- package/dist/srpc/server.js +0 -2
- package/dist/srpc/websocket.js +1 -3
- package/e2e/debug.test +0 -0
- package/e2e/e2e_test.go +20 -2
- package/go.mod +2 -2
- package/go.sum +2 -2
- package/integration/integration.bash +3 -0
- package/integration/integration.ts +1 -1
- package/package.json +1 -1
- package/srpc/client-rpc.go +8 -3
- package/srpc/client.go +2 -2
- package/srpc/client.ts +2 -2
- package/srpc/conn-duplex.ts +1 -1
- package/srpc/handler.ts +14 -10
- package/srpc/index.ts +0 -1
- package/srpc/muxed-conn.go +31 -0
- package/srpc/packet.go +4 -3
- package/srpc/rpc-stream.go +2 -2
- package/srpc/server-http.go +1 -1
- package/srpc/server-pipe.go +2 -2
- package/srpc/server-rpc.go +2 -2
- package/srpc/server.go +32 -3
- package/srpc/websocket.go +0 -3
- package/srpc/websocket.ts +1 -1
- package/srpc/conn.go +0 -7
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Stream RPC
|
|
2
2
|
|
|
3
|
-
**starpc** implements [Proto3 services] in both TypeScript and Go.
|
|
3
|
+
**starpc** implements [Proto3 services] (server & client) in both TypeScript and Go.
|
|
4
4
|
|
|
5
5
|
[Proto3 services]: https://developers.google.com/protocol-buffers/docs/proto3#services
|
|
6
6
|
|
|
@@ -13,8 +13,6 @@ Can use any Stream multiplexer: defaults to [libp2p-mplex] over a WebSocket.
|
|
|
13
13
|
|
|
14
14
|
[libp2p-mplex]: https://github.com/libp2p/js-libp2p-mplex
|
|
15
15
|
|
|
16
|
-
Note: the server has not yet been implemented in TypeScript.
|
|
17
|
-
|
|
18
16
|
# Usage
|
|
19
17
|
|
|
20
18
|
Starting with the [protobuf-project] repository on the "starpc" branch.
|
|
@@ -30,6 +28,8 @@ The demo/boilerplate project implements the Echo example below.
|
|
|
30
28
|
|
|
31
29
|
[protobuf-project]: https://github.com/aperturerobotics/protobuf-project/tree/starpc
|
|
32
30
|
|
|
31
|
+
This repository uses protowrap, see the [Makefile](./Makefile).
|
|
32
|
+
|
|
33
33
|
## Protobuf
|
|
34
34
|
|
|
35
35
|
The following examples use the [echo](./echo/echo.proto) protobuf sample.
|
|
@@ -56,9 +56,9 @@ message EchoMsg {
|
|
|
56
56
|
}
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
## Go
|
|
59
|
+
## Go
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
This example demonstrates both the server and client:
|
|
62
62
|
|
|
63
63
|
```go
|
|
64
64
|
// construct the server
|
|
@@ -96,39 +96,58 @@ if out.GetBody() != bodyTxt {
|
|
|
96
96
|
|
|
97
97
|
See the ts-proto README to generate the TypeScript for your protobufs.
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
For an example of Go <-> TypeScript interop, see the [integration] test. For an
|
|
100
|
+
example of TypeScript <-> TypeScript interop, see the [e2e] test.
|
|
100
101
|
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
[e2e]: ./e2e/e2e.ts
|
|
103
|
+
[integration]: ./integration/integration.ts
|
|
103
104
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
Supports any AsyncIterable communication channel. `DuplexConn`,
|
|
106
|
+
`MessagePortConn`, and `WebSocketConn` use [js-libp2p-mplex] to multiplex
|
|
107
|
+
streams, but any multiplexer can be used.
|
|
107
108
|
|
|
108
109
|
[js-libp2p-mplex]: https://github.com/libp2p/js-libp2p-mplex
|
|
109
110
|
|
|
110
|
-
|
|
111
|
+
This example demonstrates both the server and client:
|
|
111
112
|
|
|
112
113
|
```typescript
|
|
113
|
-
import {
|
|
114
|
-
import {
|
|
114
|
+
import { pipe } from 'it-pipe'
|
|
115
|
+
import { createHandler, createMux, Server, Client, Conn } from 'srpc'
|
|
116
|
+
import { EchoerDefinition, EchoerServer, runClientTest } from 'srpc/echo'
|
|
115
117
|
|
|
116
118
|
const mux = createMux()
|
|
117
|
-
|
|
119
|
+
const echoer = new EchoerServer()
|
|
120
|
+
mux.register(createHandler(EchoerDefinition, echoer))
|
|
118
121
|
const server = new Server(mux)
|
|
119
122
|
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
123
|
+
const clientConn = new Conn()
|
|
124
|
+
const serverConn = new Conn(server)
|
|
125
|
+
pipe(clientConn, serverConn, clientConn)
|
|
126
|
+
const client = new Client(clientConn.buildOpenStreamFunc())
|
|
127
|
+
|
|
128
|
+
console.log('Calling Echo: unary call...')
|
|
129
|
+
let result = await demoServiceClient.Echo({
|
|
130
|
+
body: 'Hello world!',
|
|
131
|
+
})
|
|
132
|
+
console.log('success: output', result.body)
|
|
133
|
+
|
|
134
|
+
const clientRequestStream = new Observable<EchoMsg>(subscriber => {
|
|
135
|
+
subscriber.next({body: 'Hello world from streaming request.'})
|
|
136
|
+
subscriber.complete()
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
console.log('Calling EchoClientStream: client -> server...')
|
|
140
|
+
result = await demoServiceClient.EchoClientStream(clientRequestStream)
|
|
141
|
+
console.log('success: output', result.body)
|
|
124
142
|
```
|
|
125
143
|
|
|
144
|
+
## WebSocket
|
|
126
145
|
|
|
127
|
-
|
|
146
|
+
One way to integrate Go and TypeScript is over a WebSocket:
|
|
128
147
|
|
|
129
148
|
```typescript
|
|
130
|
-
import { WebSocketConn } from '
|
|
131
|
-
import { EchoerClientImpl } from '
|
|
149
|
+
import { WebSocketConn } from 'srpc'
|
|
150
|
+
import { EchoerClientImpl } from 'srpc/echo'
|
|
132
151
|
|
|
133
152
|
const ws = new WebSocket('ws://localhost:5000/demo')
|
|
134
153
|
const channel = new WebSocketConn(ws)
|
|
@@ -139,15 +158,6 @@ const result = await demoServiceClient.Echo({
|
|
|
139
158
|
body: "Hello world!"
|
|
140
159
|
})
|
|
141
160
|
console.log('output', result.body)
|
|
142
|
-
|
|
143
|
-
const clientRequestStream = new Observable<EchoMsg>(subscriber => {
|
|
144
|
-
subscriber.next({body: 'Hello world from streaming request.'})
|
|
145
|
-
subscriber.complete()
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
console.log('Calling EchoClientStream: client -> server...')
|
|
149
|
-
result = await demoServiceClient.EchoClientStream(clientRequestStream)
|
|
150
|
-
console.log('success: output', result.body)
|
|
151
161
|
```
|
|
152
162
|
|
|
153
163
|
# Attribution
|
package/dist/echo/echo.js
CHANGED
|
@@ -2,14 +2,6 @@ import { EventIterator } from 'event-iterator';
|
|
|
2
2
|
import { DuplexConn } from './conn-duplex.js';
|
|
3
3
|
// BroadcastChannelIterable is a AsyncIterable wrapper for BroadcastChannel.
|
|
4
4
|
export class BroadcastChannelIterable {
|
|
5
|
-
// readChannel is the incoming broadcast channel
|
|
6
|
-
readChannel;
|
|
7
|
-
// writeChannel is the outgoing broadcast channel
|
|
8
|
-
writeChannel;
|
|
9
|
-
// sink is the sink for incoming messages.
|
|
10
|
-
sink;
|
|
11
|
-
// source is the source for outgoing messages.
|
|
12
|
-
source;
|
|
13
5
|
constructor(readChannel, writeChannel) {
|
|
14
6
|
this.readChannel = readChannel;
|
|
15
7
|
this.writeChannel = writeChannel;
|
|
@@ -52,8 +44,6 @@ export function newBroadcastChannelIterable(readName, writeName) {
|
|
|
52
44
|
//
|
|
53
45
|
// expects Uint8Array objects over the BroadcastChannel.
|
|
54
46
|
export class BroadcastChannelConn extends DuplexConn {
|
|
55
|
-
// broadcastChannel is the broadcast channel iterable
|
|
56
|
-
broadcastChannel;
|
|
57
47
|
constructor(readChannel, writeChannel, server, connParams) {
|
|
58
48
|
const broadcastChannel = new BroadcastChannelIterable(readChannel, writeChannel);
|
|
59
49
|
super(broadcastChannel, server, connParams);
|
package/dist/srpc/client.js
CHANGED
|
@@ -19,10 +19,6 @@ function writeClientStream(call, data) {
|
|
|
19
19
|
}
|
|
20
20
|
// Client implements the ts-proto Rpc interface with the drpcproto protocol.
|
|
21
21
|
export class Client {
|
|
22
|
-
// openConnFn is a promise which contains the OpenStreamFunc.
|
|
23
|
-
openConnFn;
|
|
24
|
-
// _openConnFn resolves openConnFn.
|
|
25
|
-
_openConnFn;
|
|
26
22
|
constructor(openConnFn) {
|
|
27
23
|
this.openConnFn = this.setOpenConnFn(openConnFn);
|
|
28
24
|
}
|
|
@@ -84,7 +80,7 @@ export class Client {
|
|
|
84
80
|
}
|
|
85
81
|
// serverStreamingRequest starts a server-side streaming request.
|
|
86
82
|
serverStreamingRequest(service, method, data) {
|
|
87
|
-
const pushServerData = pushable();
|
|
83
|
+
const pushServerData = pushable({ objectMode: true });
|
|
88
84
|
const serverData = observableFrom(pushServerData);
|
|
89
85
|
this.startRpc(service, method, data)
|
|
90
86
|
.then(async (call) => {
|
|
@@ -103,7 +99,7 @@ export class Client {
|
|
|
103
99
|
}
|
|
104
100
|
// bidirectionalStreamingRequest starts a two-way streaming request.
|
|
105
101
|
bidirectionalStreamingRequest(service, method, data) {
|
|
106
|
-
const pushServerData = pushable();
|
|
102
|
+
const pushServerData = pushable({ objectMode: true });
|
|
107
103
|
const serverData = observableFrom(pushServerData);
|
|
108
104
|
this.startRpc(service, method, null)
|
|
109
105
|
.then(async (call) => {
|
package/dist/srpc/common-rpc.js
CHANGED
|
@@ -2,20 +2,6 @@ import { pushable } from 'it-pushable';
|
|
|
2
2
|
import { Packet } from './rpcproto.js';
|
|
3
3
|
// CommonRPC is common logic between server and client RPCs.
|
|
4
4
|
export class CommonRPC {
|
|
5
|
-
// sink is the data sink for incoming messages.
|
|
6
|
-
sink;
|
|
7
|
-
// source is the packet source for outgoing Packets.
|
|
8
|
-
source;
|
|
9
|
-
// _source is used to write to the source.
|
|
10
|
-
_source;
|
|
11
|
-
// rpcDataSource emits incoming client RPC messages to the caller.
|
|
12
|
-
rpcDataSource;
|
|
13
|
-
// _rpcDataSource is used to write to the rpc message source.
|
|
14
|
-
_rpcDataSource;
|
|
15
|
-
// service is the rpc service
|
|
16
|
-
service;
|
|
17
|
-
// method is the rpc method
|
|
18
|
-
method;
|
|
19
5
|
constructor() {
|
|
20
6
|
this.sink = this._createSink();
|
|
21
7
|
const sourcev = this._createSource();
|
package/dist/srpc/conn-duplex.js
CHANGED
|
@@ -4,12 +4,10 @@ import { Conn } from './conn.js';
|
|
|
4
4
|
//
|
|
5
5
|
// expects Uint8Array objects
|
|
6
6
|
export class DuplexConn extends Conn {
|
|
7
|
-
// channel is the iterable
|
|
8
|
-
channel;
|
|
9
7
|
constructor(duplex, server, connParams) {
|
|
10
8
|
super(server, connParams);
|
|
11
9
|
this.channel = duplex;
|
|
12
|
-
pipe(this, this
|
|
10
|
+
pipe(this.channel, this, this.channel);
|
|
13
11
|
}
|
|
14
12
|
// getChannelDuplex returns the Duplex channel.
|
|
15
13
|
getChannelDuplex() {
|
package/dist/srpc/conn.js
CHANGED
|
@@ -5,10 +5,6 @@ import { Client } from './client.js';
|
|
|
5
5
|
// Implements the server by handling incoming streams.
|
|
6
6
|
// If the server is unset, rejects any incoming streams.
|
|
7
7
|
export class Conn {
|
|
8
|
-
// muxer is the mplex stream muxer.
|
|
9
|
-
muxer;
|
|
10
|
-
// server is the server side, if set.
|
|
11
|
-
server;
|
|
12
8
|
constructor(server, connParams) {
|
|
13
9
|
if (server) {
|
|
14
10
|
this.server = server;
|
package/dist/srpc/handler.js
CHANGED
|
@@ -4,10 +4,6 @@ import { from as observableFrom } from 'rxjs';
|
|
|
4
4
|
import { buildDecodeMessageTransform, buildEncodeMessageTransform, } from './message.js';
|
|
5
5
|
// StaticHandler is a handler with a definition and implementation.
|
|
6
6
|
export class StaticHandler {
|
|
7
|
-
// service is the service id
|
|
8
|
-
service;
|
|
9
|
-
// methods is the map of method to invoke fn
|
|
10
|
-
methods;
|
|
11
7
|
constructor(serviceID, methods) {
|
|
12
8
|
this.service = serviceID;
|
|
13
9
|
this.methods = methods;
|
|
@@ -38,23 +34,27 @@ export function createInvokeFn(methodInfo, methodProto) {
|
|
|
38
34
|
objectMode: true,
|
|
39
35
|
});
|
|
40
36
|
// pipe responseSink to dataSink.
|
|
41
|
-
|
|
37
|
+
pipe(responseSink, buildEncodeMessageTransform(methodInfo.responseType), dataSink);
|
|
38
|
+
// requestSource is a Source of decoded request messages.
|
|
39
|
+
const requestSource = pipe(dataSource, requestDecode);
|
|
42
40
|
// build the request argument.
|
|
43
41
|
let requestArg;
|
|
44
42
|
if (methodInfo.requestStream) {
|
|
45
|
-
// requestSource is a Source of decoded request messages.
|
|
46
|
-
const requestSource = pipe(dataSource, requestDecode);
|
|
47
43
|
// convert the request data source into an Observable<T>
|
|
48
44
|
requestArg = observableFrom(requestSource);
|
|
49
45
|
}
|
|
50
46
|
else {
|
|
51
47
|
// receive a single message for the argument.
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
for await (const msg of requestSource) {
|
|
49
|
+
if (msg) {
|
|
50
|
+
requestArg = msg;
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
56
53
|
}
|
|
57
54
|
}
|
|
55
|
+
if (!requestArg) {
|
|
56
|
+
throw new Error('request object was empty');
|
|
57
|
+
}
|
|
58
58
|
// Call the implementation.
|
|
59
59
|
try {
|
|
60
60
|
const responseObj = methodProto(requestArg);
|
|
@@ -77,9 +77,7 @@ export function createInvokeFn(methodInfo, methodProto) {
|
|
|
77
77
|
},
|
|
78
78
|
complete: () => {
|
|
79
79
|
responseSink.end();
|
|
80
|
-
|
|
81
|
-
resolve();
|
|
82
|
-
});
|
|
80
|
+
resolve();
|
|
83
81
|
},
|
|
84
82
|
});
|
|
85
83
|
});
|
package/dist/srpc/index.d.ts
CHANGED
|
@@ -4,6 +4,5 @@ export { Server } from './server.js';
|
|
|
4
4
|
export { Conn, ConnParams } from './conn.js';
|
|
5
5
|
export { Handler, InvokeFn, createHandler, createInvokeFn } from './handler.js';
|
|
6
6
|
export { Mux, createMux } from './mux.js';
|
|
7
|
-
export { WebSocketConn } from './websocket.js';
|
|
8
7
|
export { BroadcastChannelIterable, newBroadcastChannelIterable, BroadcastChannelConn, } from './broadcast-channel.js';
|
|
9
8
|
export { MessagePortIterable, newMessagePortIterable, MessagePortConn, } from './message-port.js';
|
package/dist/srpc/index.js
CHANGED
|
@@ -3,6 +3,5 @@ export { Server } from './server.js';
|
|
|
3
3
|
export { Conn } from './conn.js';
|
|
4
4
|
export { createHandler, createInvokeFn } from './handler.js';
|
|
5
5
|
export { createMux } from './mux.js';
|
|
6
|
-
export { WebSocketConn } from './websocket.js';
|
|
7
6
|
export { BroadcastChannelIterable, newBroadcastChannelIterable, BroadcastChannelConn, } from './broadcast-channel.js';
|
|
8
7
|
export { MessagePortIterable, newMessagePortIterable, MessagePortConn, } from './message-port.js';
|
|
@@ -2,14 +2,6 @@ import { EventIterator } from 'event-iterator';
|
|
|
2
2
|
import { DuplexConn } from './conn-duplex.js';
|
|
3
3
|
// MessagePortIterable is a AsyncIterable wrapper for MessagePort.
|
|
4
4
|
export class MessagePortIterable {
|
|
5
|
-
// port is the message port
|
|
6
|
-
port;
|
|
7
|
-
// sink is the sink for incoming messages.
|
|
8
|
-
sink;
|
|
9
|
-
// source is the source for outgoing messages.
|
|
10
|
-
source;
|
|
11
|
-
// _source is the EventIterator for source.
|
|
12
|
-
_source;
|
|
13
5
|
constructor(port) {
|
|
14
6
|
this.port = port;
|
|
15
7
|
this.sink = this._createSink();
|
|
@@ -51,8 +43,6 @@ export function newMessagePortIterable(port) {
|
|
|
51
43
|
//
|
|
52
44
|
// expects Uint8Array objects over the MessagePort.
|
|
53
45
|
export class MessagePortConn extends DuplexConn {
|
|
54
|
-
// messagePort is the message port iterable.
|
|
55
|
-
messagePort;
|
|
56
46
|
constructor(port, server, connParams) {
|
|
57
47
|
const messagePort = new MessagePortIterable(port);
|
|
58
48
|
super(messagePort, server, connParams);
|
package/dist/srpc/mux.js
CHANGED
|
@@ -5,8 +5,10 @@ export function createMux() {
|
|
|
5
5
|
// StaticMux contains a in-memory mapping between service ID and handlers.
|
|
6
6
|
// implements Mux
|
|
7
7
|
export class StaticMux {
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
constructor() {
|
|
9
|
+
// services contains a mapping from service id to handlers.
|
|
10
|
+
this.services = {};
|
|
11
|
+
}
|
|
10
12
|
register(handler) {
|
|
11
13
|
const serviceID = handler?.getServiceID();
|
|
12
14
|
if (!serviceID) {
|
package/dist/srpc/server-rpc.js
CHANGED
package/dist/srpc/server.js
CHANGED
|
@@ -3,8 +3,6 @@ import { ServerRPC } from './server-rpc.js';
|
|
|
3
3
|
import { parseLengthPrefixTransform, prependLengthPrefixTransform, decodePacketSource, encodePacketSource, } from './packet.js';
|
|
4
4
|
// Server implements the SRPC server in TypeScript with a Mux.
|
|
5
5
|
export class Server {
|
|
6
|
-
// mux is the mux used to handle requests.
|
|
7
|
-
mux;
|
|
8
6
|
constructor(mux) {
|
|
9
7
|
this.mux = mux;
|
|
10
8
|
}
|
package/dist/srpc/websocket.js
CHANGED
|
@@ -4,15 +4,13 @@ import { Mplex } from '@libp2p/mplex';
|
|
|
4
4
|
import { Conn } from './conn.js';
|
|
5
5
|
// WebSocketConn implements a connection with a WebSocket and optional Server.
|
|
6
6
|
export class WebSocketConn extends Conn {
|
|
7
|
-
// socket is the web socket
|
|
8
|
-
socket;
|
|
9
7
|
constructor(socket, server) {
|
|
10
8
|
super(server, {
|
|
11
9
|
muxerFactory: new Mplex(),
|
|
12
10
|
});
|
|
13
11
|
this.socket = socket;
|
|
14
12
|
const socketDuplex = duplex(socket);
|
|
15
|
-
pipe(this
|
|
13
|
+
pipe(socketDuplex, this, socketDuplex);
|
|
16
14
|
}
|
|
17
15
|
// getSocket returns the websocket.
|
|
18
16
|
getSocket() {
|
package/e2e/debug.test
ADDED
|
Binary file
|
package/e2e/e2e_test.go
CHANGED
|
@@ -3,10 +3,13 @@ package e2e
|
|
|
3
3
|
import (
|
|
4
4
|
"context"
|
|
5
5
|
"io"
|
|
6
|
+
"net"
|
|
6
7
|
"testing"
|
|
7
8
|
|
|
8
9
|
"github.com/aperturerobotics/starpc/echo"
|
|
9
10
|
"github.com/aperturerobotics/starpc/srpc"
|
|
11
|
+
"github.com/libp2p/go-libp2p/p2p/muxer/mplex"
|
|
12
|
+
mp "github.com/libp2p/go-mplex"
|
|
10
13
|
"github.com/pkg/errors"
|
|
11
14
|
)
|
|
12
15
|
|
|
@@ -20,9 +23,24 @@ func RunE2E(t *testing.T, cb func(client echo.SRPCEchoerClient) error) {
|
|
|
20
23
|
}
|
|
21
24
|
server := srpc.NewServer(mux)
|
|
22
25
|
|
|
26
|
+
// note: also possible to do without mplex:
|
|
27
|
+
// openStream := srpc.NewServerPipe(server)
|
|
28
|
+
// client := srpc.NewClient(openStream)
|
|
29
|
+
|
|
23
30
|
// construct the client
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
clientPipe, serverPipe := net.Pipe()
|
|
32
|
+
|
|
33
|
+
ctx := context.Background()
|
|
34
|
+
go func() {
|
|
35
|
+
serverMp, _ := mp.NewMultiplex(serverPipe, false, nil)
|
|
36
|
+
_ = server.AcceptMuxedConn(ctx, mplex.NewMuxedConn(serverMp))
|
|
37
|
+
}()
|
|
38
|
+
|
|
39
|
+
clientMp, err := mp.NewMultiplex(clientPipe, true, nil)
|
|
40
|
+
if err != nil {
|
|
41
|
+
t.Fatal(err.Error())
|
|
42
|
+
}
|
|
43
|
+
client := srpc.NewClientWithMuxedConn(mplex.NewMuxedConn(clientMp))
|
|
26
44
|
|
|
27
45
|
// construct the client rpc interface
|
|
28
46
|
clientEcho := echo.NewSRPCEchoerClient(client)
|
package/go.mod
CHANGED
|
@@ -9,8 +9,9 @@ require (
|
|
|
9
9
|
)
|
|
10
10
|
|
|
11
11
|
require (
|
|
12
|
-
github.com/libp2p/go-libp2p v0.20.1
|
|
12
|
+
github.com/libp2p/go-libp2p v0.20.1-0.20220622205512-3cf611ad8c9c
|
|
13
13
|
github.com/libp2p/go-libp2p-core v0.16.1
|
|
14
|
+
github.com/libp2p/go-mplex v0.7.0
|
|
14
15
|
github.com/sirupsen/logrus v1.8.2-0.20220112234510-85981c045988
|
|
15
16
|
)
|
|
16
17
|
|
|
@@ -25,7 +26,6 @@ require (
|
|
|
25
26
|
github.com/klauspost/compress v1.15.1 // indirect
|
|
26
27
|
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
|
|
27
28
|
github.com/libp2p/go-buffer-pool v0.0.2 // indirect
|
|
28
|
-
github.com/libp2p/go-mplex v0.7.0 // indirect
|
|
29
29
|
github.com/libp2p/go-openssl v0.0.7 // indirect
|
|
30
30
|
github.com/mattn/go-isatty v0.0.14 // indirect
|
|
31
31
|
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect
|
package/go.sum
CHANGED
|
@@ -65,8 +65,8 @@ github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
|
|
65
65
|
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
|
66
66
|
github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs=
|
|
67
67
|
github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM=
|
|
68
|
-
github.com/libp2p/go-libp2p v0.20.1 h1:
|
|
69
|
-
github.com/libp2p/go-libp2p v0.20.1/go.mod h1:
|
|
68
|
+
github.com/libp2p/go-libp2p v0.20.1-0.20220622205512-3cf611ad8c9c h1:BTR12zXVGQtdk9bT5GLOmdzJrhyFO6YUH3/e8lnkjQ0=
|
|
69
|
+
github.com/libp2p/go-libp2p v0.20.1-0.20220622205512-3cf611ad8c9c/go.mod h1:tqyre7jPbLfhzjQfpiFX6x7wj/sqt1074riPM0Xn/qI=
|
|
70
70
|
github.com/libp2p/go-libp2p-core v0.16.1 h1:bWoiEBqVkpJ13hbv/f69tHODp86t6mvc4fBN4DkK73M=
|
|
71
71
|
github.com/libp2p/go-libp2p-core v0.16.1/go.mod h1:O3i/7y+LqUb0N+qhzXjBjjpchgptWAVMG1Voegk7b4c=
|
|
72
72
|
github.com/libp2p/go-libp2p-testing v0.9.2 h1:dCpODRtRaDZKF8HXT9qqqgON+OMEB423Knrgeod8j84=
|
package/package.json
CHANGED
package/srpc/client-rpc.go
CHANGED
|
@@ -46,9 +46,8 @@ func NewClientRPC(ctx context.Context, service, method string) *ClientRPC {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
// Start sets the writer and writes the MsgSend message.
|
|
49
|
-
// firstMsg can be nil, but is usually set if this is a non-streaming rpc.
|
|
50
49
|
// must only be called once!
|
|
51
|
-
func (r *ClientRPC) Start(writer Writer, firstMsg []byte) error {
|
|
50
|
+
func (r *ClientRPC) Start(writer Writer, writeFirstMsg bool, firstMsg []byte) error {
|
|
52
51
|
select {
|
|
53
52
|
case <-r.ctx.Done():
|
|
54
53
|
r.Close()
|
|
@@ -56,7 +55,13 @@ func (r *ClientRPC) Start(writer Writer, firstMsg []byte) error {
|
|
|
56
55
|
default:
|
|
57
56
|
}
|
|
58
57
|
r.writer = writer
|
|
59
|
-
|
|
58
|
+
var firstMsgEmpty bool
|
|
59
|
+
if writeFirstMsg {
|
|
60
|
+
firstMsgEmpty = len(firstMsg) == 0
|
|
61
|
+
} else {
|
|
62
|
+
firstMsg = nil
|
|
63
|
+
}
|
|
64
|
+
pkt := NewCallStartPacket(r.service, r.method, firstMsg, firstMsgEmpty)
|
|
60
65
|
if err := writer.WritePacket(pkt); err != nil {
|
|
61
66
|
r.Close()
|
|
62
67
|
return err
|
package/srpc/client.go
CHANGED
|
@@ -48,7 +48,7 @@ func (c *client) Invoke(rctx context.Context, service, method string, in, out Me
|
|
|
48
48
|
if err != nil {
|
|
49
49
|
return err
|
|
50
50
|
}
|
|
51
|
-
if err := clientRPC.Start(writer, firstMsg); err != nil {
|
|
51
|
+
if err := clientRPC.Start(writer, true, firstMsg); err != nil {
|
|
52
52
|
return err
|
|
53
53
|
}
|
|
54
54
|
msgs, err := clientRPC.ReadAll()
|
|
@@ -85,7 +85,7 @@ func (c *client) NewStream(ctx context.Context, service, method string, firstMsg
|
|
|
85
85
|
if err != nil {
|
|
86
86
|
return nil, err
|
|
87
87
|
}
|
|
88
|
-
if err := clientRPC.Start(writer, firstMsgData); err != nil {
|
|
88
|
+
if err := clientRPC.Start(writer, firstMsg != nil, firstMsgData); err != nil {
|
|
89
89
|
return nil, err
|
|
90
90
|
}
|
|
91
91
|
|
package/srpc/client.ts
CHANGED
|
@@ -109,7 +109,7 @@ export class Client implements TsProtoRpc {
|
|
|
109
109
|
method: string,
|
|
110
110
|
data: Uint8Array
|
|
111
111
|
): Observable<Uint8Array> {
|
|
112
|
-
const pushServerData: Pushable<Uint8Array> = pushable()
|
|
112
|
+
const pushServerData: Pushable<Uint8Array> = pushable({ objectMode: true })
|
|
113
113
|
const serverData = observableFrom(pushServerData)
|
|
114
114
|
this.startRpc(service, method, data)
|
|
115
115
|
.then(async (call) => {
|
|
@@ -132,7 +132,7 @@ export class Client implements TsProtoRpc {
|
|
|
132
132
|
method: string,
|
|
133
133
|
data: Observable<Uint8Array>
|
|
134
134
|
): Observable<Uint8Array> {
|
|
135
|
-
const pushServerData: Pushable<Uint8Array> = pushable()
|
|
135
|
+
const pushServerData: Pushable<Uint8Array> = pushable({ objectMode: true })
|
|
136
136
|
const serverData = observableFrom(pushServerData)
|
|
137
137
|
this.startRpc(service, method, null)
|
|
138
138
|
.then(async (call) => {
|
package/srpc/conn-duplex.ts
CHANGED
package/srpc/handler.ts
CHANGED
|
@@ -84,28 +84,34 @@ export function createInvokeFn(
|
|
|
84
84
|
})
|
|
85
85
|
|
|
86
86
|
// pipe responseSink to dataSink.
|
|
87
|
-
|
|
87
|
+
pipe(
|
|
88
88
|
responseSink,
|
|
89
89
|
buildEncodeMessageTransform(methodInfo.responseType),
|
|
90
90
|
dataSink
|
|
91
91
|
)
|
|
92
92
|
|
|
93
|
+
// requestSource is a Source of decoded request messages.
|
|
94
|
+
const requestSource = pipe(dataSource, requestDecode)
|
|
95
|
+
|
|
93
96
|
// build the request argument.
|
|
94
97
|
let requestArg: any
|
|
95
98
|
if (methodInfo.requestStream) {
|
|
96
|
-
// requestSource is a Source of decoded request messages.
|
|
97
|
-
const requestSource = pipe(dataSource, requestDecode)
|
|
98
99
|
// convert the request data source into an Observable<T>
|
|
99
100
|
requestArg = observableFrom(requestSource)
|
|
100
101
|
} else {
|
|
101
102
|
// receive a single message for the argument.
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
for await (const msg of requestSource) {
|
|
104
|
+
if (msg) {
|
|
105
|
+
requestArg = msg
|
|
106
|
+
break
|
|
107
|
+
}
|
|
106
108
|
}
|
|
107
109
|
}
|
|
108
110
|
|
|
111
|
+
if (!requestArg) {
|
|
112
|
+
throw new Error('request object was empty')
|
|
113
|
+
}
|
|
114
|
+
|
|
109
115
|
// Call the implementation.
|
|
110
116
|
try {
|
|
111
117
|
const responseObj = methodProto(requestArg)
|
|
@@ -128,9 +134,7 @@ export function createInvokeFn(
|
|
|
128
134
|
},
|
|
129
135
|
complete: () => {
|
|
130
136
|
responseSink.end()
|
|
131
|
-
|
|
132
|
-
resolve()
|
|
133
|
-
})
|
|
137
|
+
resolve()
|
|
134
138
|
},
|
|
135
139
|
})
|
|
136
140
|
})
|
package/srpc/index.ts
CHANGED
|
@@ -4,7 +4,6 @@ export { Server } from './server.js'
|
|
|
4
4
|
export { Conn, ConnParams } from './conn.js'
|
|
5
5
|
export { Handler, InvokeFn, createHandler, createInvokeFn } from './handler.js'
|
|
6
6
|
export { Mux, createMux } from './mux.js'
|
|
7
|
-
export { WebSocketConn } from './websocket.js'
|
|
8
7
|
export {
|
|
9
8
|
BroadcastChannelIterable,
|
|
10
9
|
newBroadcastChannelIterable,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
package srpc
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
|
|
6
|
+
"github.com/libp2p/go-libp2p-core/network"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
// NewClientWithMuxedConn constructs a new client with a MuxedConn.
|
|
10
|
+
func NewClientWithMuxedConn(conn network.MuxedConn) Client {
|
|
11
|
+
openStreamFn := NewOpenStreamWithMuxedConn(conn)
|
|
12
|
+
return NewClient(openStreamFn)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// NewOpenStreamWithMuxedConn constructs a OpenStream func with a MuxedConn.
|
|
16
|
+
func NewOpenStreamWithMuxedConn(conn network.MuxedConn) OpenStreamFunc {
|
|
17
|
+
return func(ctx context.Context, msgHandler func(pkt *Packet) error) (Writer, error) {
|
|
18
|
+
mstrm, err := conn.OpenStream(ctx)
|
|
19
|
+
if err != nil {
|
|
20
|
+
return nil, err
|
|
21
|
+
}
|
|
22
|
+
rw := NewPacketReadWriter(mstrm, msgHandler)
|
|
23
|
+
go func() {
|
|
24
|
+
err := rw.ReadPump()
|
|
25
|
+
if err != nil {
|
|
26
|
+
_ = rw.Close()
|
|
27
|
+
}
|
|
28
|
+
}()
|
|
29
|
+
return rw, nil
|
|
30
|
+
}
|
|
31
|
+
}
|
package/srpc/packet.go
CHANGED
|
@@ -16,12 +16,13 @@ func (p *Packet) Validate() error {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
// NewCallStartPacket constructs a new CallStart packet.
|
|
19
|
-
func NewCallStartPacket(service, method string, data []byte) *Packet {
|
|
19
|
+
func NewCallStartPacket(service, method string, data []byte, dataIsZero bool) *Packet {
|
|
20
20
|
return &Packet{Body: &Packet_CallStart{
|
|
21
21
|
CallStart: &CallStart{
|
|
22
22
|
RpcService: service,
|
|
23
23
|
RpcMethod: method,
|
|
24
24
|
Data: data,
|
|
25
|
+
DataIsZero: dataIsZero,
|
|
25
26
|
},
|
|
26
27
|
}}
|
|
27
28
|
}
|
|
@@ -40,7 +41,7 @@ func (p *CallStart) Validate() error {
|
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
// NewCallDataPacket constructs a new CallData packet.
|
|
43
|
-
func NewCallDataPacket(data []byte, complete bool, err error) *Packet {
|
|
44
|
+
func NewCallDataPacket(data []byte, dataIsZero bool, complete bool, err error) *Packet {
|
|
44
45
|
var errStr string
|
|
45
46
|
if err != nil {
|
|
46
47
|
errStr = err.Error()
|
|
@@ -48,7 +49,7 @@ func NewCallDataPacket(data []byte, complete bool, err error) *Packet {
|
|
|
48
49
|
return &Packet{Body: &Packet_CallData{
|
|
49
50
|
CallData: &CallData{
|
|
50
51
|
Data: data,
|
|
51
|
-
DataIsZero:
|
|
52
|
+
DataIsZero: dataIsZero,
|
|
52
53
|
Complete: err != nil || complete,
|
|
53
54
|
Error: errStr,
|
|
54
55
|
},
|
package/srpc/rpc-stream.go
CHANGED
|
@@ -42,7 +42,7 @@ func (r *RPCStream) MsgSend(msg Message) error {
|
|
|
42
42
|
if err != nil {
|
|
43
43
|
return err
|
|
44
44
|
}
|
|
45
|
-
outPkt := NewCallDataPacket(msgData, false, nil)
|
|
45
|
+
outPkt := NewCallDataPacket(msgData, len(msgData) == 0, false, nil)
|
|
46
46
|
return r.writer.WritePacket(outPkt)
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -62,7 +62,7 @@ func (r *RPCStream) MsgRecv(msg Message) error {
|
|
|
62
62
|
|
|
63
63
|
// CloseSend signals to the remote that we will no longer send any messages.
|
|
64
64
|
func (r *RPCStream) CloseSend() error {
|
|
65
|
-
outPkt := NewCallDataPacket(nil, true, nil)
|
|
65
|
+
outPkt := NewCallDataPacket(nil, false, true, nil)
|
|
66
66
|
return r.writer.WritePacket(outPkt)
|
|
67
67
|
}
|
|
68
68
|
|
package/srpc/server-http.go
CHANGED
|
@@ -57,7 +57,7 @@ func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
57
57
|
return
|
|
58
58
|
}
|
|
59
59
|
go func() {
|
|
60
|
-
err := s.srpc.
|
|
60
|
+
err := s.srpc.HandleStream(ctx, strm)
|
|
61
61
|
_ = err
|
|
62
62
|
// TODO: handle / log error?
|
|
63
63
|
// err != nil && err != io.EOF && err != context.Canceled
|
package/srpc/server-pipe.go
CHANGED
|
@@ -7,12 +7,12 @@ import (
|
|
|
7
7
|
|
|
8
8
|
// NewServerPipe constructs a open stream func which creates an in-memory Pipe
|
|
9
9
|
// Stream with the given Server. Starts read pumps for both. Starts the
|
|
10
|
-
//
|
|
10
|
+
// HandleStream function on the server in a separate goroutine.
|
|
11
11
|
func NewServerPipe(server *Server) OpenStreamFunc {
|
|
12
12
|
return func(ctx context.Context, msgHandler func(pkt *Packet) error) (Writer, error) {
|
|
13
13
|
srvPipe, clientPipe := net.Pipe()
|
|
14
14
|
go func() {
|
|
15
|
-
_ = server.
|
|
15
|
+
_ = server.HandleStream(ctx, srvPipe)
|
|
16
16
|
}()
|
|
17
17
|
clientPrw := NewPacketReadWriter(clientPipe, msgHandler)
|
|
18
18
|
go func() {
|
package/srpc/server-rpc.go
CHANGED
|
@@ -138,7 +138,7 @@ func (r *ServerRPC) invokeRPC() {
|
|
|
138
138
|
if err == nil && !ok {
|
|
139
139
|
err = ErrUnimplemented
|
|
140
140
|
}
|
|
141
|
-
outPkt := NewCallDataPacket(nil, true, err)
|
|
141
|
+
outPkt := NewCallDataPacket(nil, false, true, err)
|
|
142
142
|
_ = r.writer.WritePacket(outPkt)
|
|
143
143
|
r.ctxCancel()
|
|
144
144
|
_ = r.writer.Close()
|
|
@@ -149,7 +149,7 @@ func (r *ServerRPC) invokeRPC() {
|
|
|
149
149
|
func (r *ServerRPC) Close() {
|
|
150
150
|
r.ctxCancel()
|
|
151
151
|
if r.service == "" {
|
|
152
|
-
// invokeRPC has not been called
|
|
152
|
+
// invokeRPC has not been called, otherwise it would call Close()
|
|
153
153
|
_ = r.writer.Close()
|
|
154
154
|
}
|
|
155
155
|
}
|
package/srpc/server.go
CHANGED
|
@@ -3,6 +3,8 @@ package srpc
|
|
|
3
3
|
import (
|
|
4
4
|
"context"
|
|
5
5
|
"io"
|
|
6
|
+
|
|
7
|
+
"github.com/libp2p/go-libp2p-core/network"
|
|
6
8
|
)
|
|
7
9
|
|
|
8
10
|
// Server handles incoming RPC streams with a mux.
|
|
@@ -18,12 +20,39 @@ func NewServer(mux Mux) *Server {
|
|
|
18
20
|
}
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
//
|
|
22
|
-
func (s *Server)
|
|
23
|
+
// HandleStream handles an incoming ReadWriteCloser stream.
|
|
24
|
+
func (s *Server) HandleStream(ctx context.Context, rwc io.ReadWriteCloser) error {
|
|
23
25
|
subCtx, subCtxCancel := context.WithCancel(ctx)
|
|
24
26
|
defer subCtxCancel()
|
|
25
27
|
serverRPC := NewServerRPC(subCtx, s.mux)
|
|
26
28
|
prw := NewPacketReadWriter(rwc, serverRPC.HandlePacket)
|
|
27
29
|
serverRPC.SetWriter(prw)
|
|
28
|
-
|
|
30
|
+
err := prw.ReadPump()
|
|
31
|
+
_ = rwc.Close()
|
|
32
|
+
return err
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// AcceptMuxedConn runs a loop which calls Accept on a muxer to handle streams.
|
|
36
|
+
//
|
|
37
|
+
// Starts HandleStream in a separate goroutine to handle the stream.
|
|
38
|
+
// Returns context.Canceled or io.EOF when the loop is complete / closed.
|
|
39
|
+
func (s *Server) AcceptMuxedConn(ctx context.Context, mplex network.MuxedConn) error {
|
|
40
|
+
for {
|
|
41
|
+
select {
|
|
42
|
+
case <-ctx.Done():
|
|
43
|
+
return context.Canceled
|
|
44
|
+
default:
|
|
45
|
+
if mplex.IsClosed() {
|
|
46
|
+
return io.EOF
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
muxedStream, err := mplex.AcceptStream()
|
|
51
|
+
if err != nil {
|
|
52
|
+
return err
|
|
53
|
+
}
|
|
54
|
+
go func() {
|
|
55
|
+
_ = s.HandleStream(ctx, muxedStream)
|
|
56
|
+
}()
|
|
57
|
+
}
|
|
29
58
|
}
|
package/srpc/websocket.go
CHANGED
package/srpc/websocket.ts
CHANGED