starpc 0.6.2 → 0.8.0
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 +1 -1
- package/dist/e2e/e2e.d.ts +1 -0
- package/dist/e2e/e2e.js +24 -0
- package/dist/echo/client-test.js +2 -0
- package/dist/integration/integration.d.ts +1 -0
- package/dist/integration/integration.js +22 -0
- package/dist/srpc/mux.d.ts +7 -2
- package/dist/srpc/mux.js +31 -3
- package/dist/srpc/server-rpc.d.ts +3 -3
- package/dist/srpc/server-rpc.js +3 -3
- package/dist/srpc/server.d.ts +3 -3
- package/dist/srpc/server.js +3 -3
- package/e2e/e2e.ts +1 -1
- package/echo/client-test.ts +4 -0
- package/package.json +3 -2
- package/patches/ts-poet+4.15.0.patch +13 -3
- package/srpc/client-rpc.go +12 -0
- package/srpc/client.go +7 -3
- package/srpc/mux.ts +41 -6
- package/srpc/muxed-conn.go +2 -7
- package/srpc/packet-rw.go +23 -5
- package/srpc/packet.go +7 -1
- package/srpc/server-pipe.go +2 -7
- package/srpc/server-rpc.go +27 -1
- package/srpc/server-rpc.ts +6 -6
- package/srpc/server.go +2 -3
- package/srpc/server.ts +6 -6
- package/srpc/websocket.go +2 -7
package/README.md
CHANGED
|
@@ -121,7 +121,7 @@ import { pushable } from 'it-pushable'
|
|
|
121
121
|
const mux = createMux()
|
|
122
122
|
const echoer = new EchoerServer()
|
|
123
123
|
mux.register(createHandler(EchoerDefinition, echoer))
|
|
124
|
-
const server = new Server(mux)
|
|
124
|
+
const server = new Server(mux.lookupMethodFunc)
|
|
125
125
|
|
|
126
126
|
const clientConn = new Conn()
|
|
127
127
|
const serverConn = new Conn(server)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/e2e/e2e.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { pipe } from 'it-pipe';
|
|
2
|
+
import { createHandler, createMux, Server, Client, Conn } from '../srpc';
|
|
3
|
+
import { EchoerDefinition, EchoerServer, runClientTest } from '../echo';
|
|
4
|
+
import { runRpcStreamTest } from '../echo/client-test';
|
|
5
|
+
async function runRPC() {
|
|
6
|
+
const mux = createMux();
|
|
7
|
+
const server = new Server(mux.lookupMethodFunc);
|
|
8
|
+
const echoer = new EchoerServer(server);
|
|
9
|
+
mux.register(createHandler(EchoerDefinition, echoer));
|
|
10
|
+
const clientConn = new Conn();
|
|
11
|
+
const serverConn = new Conn(server);
|
|
12
|
+
pipe(clientConn, serverConn, clientConn);
|
|
13
|
+
const client = new Client(clientConn.buildOpenStreamFunc());
|
|
14
|
+
await runRpcStreamTest(client);
|
|
15
|
+
await runClientTest(client);
|
|
16
|
+
}
|
|
17
|
+
runRPC()
|
|
18
|
+
.then(() => {
|
|
19
|
+
console.log('finished successfully');
|
|
20
|
+
})
|
|
21
|
+
.catch((err) => {
|
|
22
|
+
console.error(err);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
});
|
package/dist/echo/client-test.js
CHANGED
|
@@ -34,4 +34,6 @@ export async function runRpcStreamTest(client) {
|
|
|
34
34
|
console.log('Calling Echo via RPC stream...');
|
|
35
35
|
const resp = await proxiedService.Echo({ body: 'hello world via proxy' });
|
|
36
36
|
console.log('rpc stream test: succeeded: response: ' + resp.body);
|
|
37
|
+
console.log('Running client test over RPC stream...');
|
|
38
|
+
await runClientTest(proxiedClient);
|
|
37
39
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { WebSocketConn } from '../srpc/websocket.js';
|
|
2
|
+
import { runClientTest, runRpcStreamTest } from '../echo/client-test.js';
|
|
3
|
+
import WebSocket from 'isomorphic-ws';
|
|
4
|
+
async function runRPC() {
|
|
5
|
+
const addr = 'ws://localhost:5000/demo';
|
|
6
|
+
console.log(`Connecting to ${addr}`);
|
|
7
|
+
const ws = new WebSocket(addr);
|
|
8
|
+
const channel = new WebSocketConn(ws);
|
|
9
|
+
const client = channel.buildClient();
|
|
10
|
+
console.log('Running client test via WebSocket..');
|
|
11
|
+
await runClientTest(client);
|
|
12
|
+
console.log('Running RpcStream test via WebSocket..');
|
|
13
|
+
await runRpcStreamTest(client);
|
|
14
|
+
}
|
|
15
|
+
runRPC()
|
|
16
|
+
.then(() => {
|
|
17
|
+
process.exit(0);
|
|
18
|
+
})
|
|
19
|
+
.catch((err) => {
|
|
20
|
+
console.error(err);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
});
|
package/dist/srpc/mux.d.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { InvokeFn, Handler } from './handler';
|
|
2
|
+
export declare type LookupMethod = (serviceID: string, methodID: string) => Promise<InvokeFn | null>;
|
|
2
3
|
export interface Mux {
|
|
3
|
-
register(handler: Handler): void;
|
|
4
4
|
lookupMethod(serviceID: string, methodID: string): Promise<InvokeFn | null>;
|
|
5
5
|
}
|
|
6
|
-
export declare function createMux():
|
|
6
|
+
export declare function createMux(): StaticMux;
|
|
7
7
|
export declare class StaticMux implements Mux {
|
|
8
8
|
private services;
|
|
9
|
+
private lookups;
|
|
10
|
+
get lookupMethodFunc(): LookupMethod;
|
|
9
11
|
register(handler: Handler): void;
|
|
12
|
+
registerLookupMethod(lookupMethod: LookupMethod): void;
|
|
10
13
|
lookupMethod(serviceID: string, methodID: string): Promise<InvokeFn | null>;
|
|
14
|
+
private lookupViaMap;
|
|
15
|
+
private lookupViaLookups;
|
|
11
16
|
}
|
package/dist/srpc/mux.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// createMux builds a new
|
|
1
|
+
// createMux builds a new StaticMux.
|
|
2
2
|
export function createMux() {
|
|
3
3
|
return new StaticMux();
|
|
4
4
|
}
|
|
@@ -8,6 +8,13 @@ export class StaticMux {
|
|
|
8
8
|
constructor() {
|
|
9
9
|
// services contains a mapping from service id to handlers.
|
|
10
10
|
this.services = {};
|
|
11
|
+
// lookups is the list of lookup methods to call.
|
|
12
|
+
// called if the method is not resolved by the services list.
|
|
13
|
+
this.lookups = [];
|
|
14
|
+
}
|
|
15
|
+
// lookupMethodFunc implements the LookupMethod type.
|
|
16
|
+
get lookupMethodFunc() {
|
|
17
|
+
return this.lookupMethod.bind(this);
|
|
11
18
|
}
|
|
12
19
|
register(handler) {
|
|
13
20
|
const serviceID = handler?.getServiceID();
|
|
@@ -21,10 +28,21 @@ export class StaticMux {
|
|
|
21
28
|
}
|
|
22
29
|
this.services[serviceID] = serviceMethods;
|
|
23
30
|
}
|
|
31
|
+
// registerLookupMethod registers a extra lookup function to the mux.
|
|
32
|
+
registerLookupMethod(lookupMethod) {
|
|
33
|
+
this.lookups.push(lookupMethod);
|
|
34
|
+
}
|
|
24
35
|
async lookupMethod(serviceID, methodID) {
|
|
25
|
-
if (
|
|
26
|
-
|
|
36
|
+
if (serviceID) {
|
|
37
|
+
const invokeFn = await this.lookupViaMap(serviceID, methodID);
|
|
38
|
+
if (invokeFn) {
|
|
39
|
+
return invokeFn;
|
|
40
|
+
}
|
|
27
41
|
}
|
|
42
|
+
return await this.lookupViaLookups(serviceID, methodID);
|
|
43
|
+
}
|
|
44
|
+
// lookupViaMap looks up the method via the services map.
|
|
45
|
+
async lookupViaMap(serviceID, methodID) {
|
|
28
46
|
const serviceMethods = this.services[serviceID];
|
|
29
47
|
if (!serviceMethods) {
|
|
30
48
|
return null;
|
|
@@ -35,4 +53,14 @@ export class StaticMux {
|
|
|
35
53
|
}
|
|
36
54
|
return await handler.lookupMethod(serviceID, methodID);
|
|
37
55
|
}
|
|
56
|
+
// lookupViaLookups looks up the method via the lookup funcs.
|
|
57
|
+
async lookupViaLookups(serviceID, methodID) {
|
|
58
|
+
for (const lookupMethod of this.lookups) {
|
|
59
|
+
const invokeFn = await lookupMethod(serviceID, methodID);
|
|
60
|
+
if (invokeFn) {
|
|
61
|
+
return invokeFn;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
38
66
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { CallData, CallStart } from './rpcproto.pb.js';
|
|
2
2
|
import { CommonRPC } from './common-rpc.js';
|
|
3
|
-
import {
|
|
3
|
+
import { LookupMethod } from './mux.js';
|
|
4
4
|
export declare class ServerRPC extends CommonRPC {
|
|
5
|
-
private
|
|
6
|
-
constructor(
|
|
5
|
+
private lookupMethod;
|
|
6
|
+
constructor(lookupMethod: LookupMethod);
|
|
7
7
|
handleCallStart(packet: Partial<CallStart>): Promise<void>;
|
|
8
8
|
handleCallData(packet: Partial<CallData>): Promise<void>;
|
|
9
9
|
private invokeRPC;
|
package/dist/srpc/server-rpc.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { CommonRPC } from './common-rpc.js';
|
|
2
2
|
// ServerRPC is an ongoing RPC from the server side.
|
|
3
3
|
export class ServerRPC extends CommonRPC {
|
|
4
|
-
constructor(
|
|
4
|
+
constructor(lookupMethod) {
|
|
5
5
|
super();
|
|
6
|
-
this.
|
|
6
|
+
this.lookupMethod = lookupMethod;
|
|
7
7
|
}
|
|
8
8
|
// handleCallStart handles a CallStart cket.
|
|
9
9
|
async handleCallStart(packet) {
|
|
@@ -15,7 +15,7 @@ export class ServerRPC extends CommonRPC {
|
|
|
15
15
|
if (!this.service || !this.method) {
|
|
16
16
|
throw new Error('rpcService and rpcMethod cannot be empty');
|
|
17
17
|
}
|
|
18
|
-
const methodDef = await this.
|
|
18
|
+
const methodDef = await this.lookupMethod(this.service, this.method);
|
|
19
19
|
if (!methodDef) {
|
|
20
20
|
throw new Error(`not found: ${this.service}/${this.method}`);
|
|
21
21
|
}
|
package/dist/srpc/server.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Stream } from '@libp2p/interface-connection';
|
|
2
2
|
import { Duplex } from 'it-stream-types';
|
|
3
|
-
import {
|
|
3
|
+
import { LookupMethod } from './mux.js';
|
|
4
4
|
import { ServerRPC } from './server-rpc.js';
|
|
5
5
|
import { Packet } from './rpcproto.pb.js';
|
|
6
6
|
import { StreamHandler } from './conn.js';
|
|
7
7
|
import { RpcStreamHandler } from '../rpcstream/rpcstream.js';
|
|
8
8
|
export declare class Server implements StreamHandler {
|
|
9
|
-
private
|
|
10
|
-
constructor(
|
|
9
|
+
private lookupMethod;
|
|
10
|
+
constructor(lookupMethod: LookupMethod);
|
|
11
11
|
get rpcStreamHandler(): RpcStreamHandler;
|
|
12
12
|
startRpc(): ServerRPC;
|
|
13
13
|
handleStream(stream: Stream): ServerRPC;
|
package/dist/srpc/server.js
CHANGED
|
@@ -3,8 +3,8 @@ 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
|
-
constructor(
|
|
7
|
-
this.
|
|
6
|
+
constructor(lookupMethod) {
|
|
7
|
+
this.lookupMethod = lookupMethod;
|
|
8
8
|
}
|
|
9
9
|
// rpcStreamHandler implements the RpcStreamHandler interface.
|
|
10
10
|
get rpcStreamHandler() {
|
|
@@ -13,7 +13,7 @@ export class Server {
|
|
|
13
13
|
// startRpc starts a new server-side RPC.
|
|
14
14
|
// the returned RPC handles incoming Packets.
|
|
15
15
|
startRpc() {
|
|
16
|
-
return new ServerRPC(this.
|
|
16
|
+
return new ServerRPC(this.lookupMethod);
|
|
17
17
|
}
|
|
18
18
|
// handleStream handles an incoming Uint8Array message duplex.
|
|
19
19
|
handleStream(stream) {
|
package/e2e/e2e.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { runRpcStreamTest } from '../echo/client-test'
|
|
|
5
5
|
|
|
6
6
|
async function runRPC() {
|
|
7
7
|
const mux = createMux()
|
|
8
|
-
const server = new Server(mux)
|
|
8
|
+
const server = new Server(mux.lookupMethodFunc)
|
|
9
9
|
const echoer = new EchoerServer(server)
|
|
10
10
|
mux.register(createHandler(EchoerDefinition, echoer))
|
|
11
11
|
|
package/echo/client-test.ts
CHANGED
|
@@ -40,7 +40,11 @@ export async function runRpcStreamTest(client: Client) {
|
|
|
40
40
|
)
|
|
41
41
|
const proxiedClient = new Client(openStreamFn)
|
|
42
42
|
const proxiedService = new EchoerClientImpl(proxiedClient)
|
|
43
|
+
|
|
43
44
|
console.log('Calling Echo via RPC stream...')
|
|
44
45
|
const resp = await proxiedService.Echo({ body: 'hello world via proxy' })
|
|
45
46
|
console.log('rpc stream test: succeeded: response: ' + resp.body)
|
|
47
|
+
|
|
48
|
+
console.log('Running client test over RPC stream...')
|
|
49
|
+
await runClientTest(proxiedClient)
|
|
46
50
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "starpc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Streaming protobuf RPC service protocol over any two-way channel.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
36
|
"build": "rimraf ./dist && tsc --project tsconfig.build.json --outDir ./dist/",
|
|
37
|
-
"check": "
|
|
37
|
+
"check": "npm run typecheck",
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
38
39
|
"deps": "depcheck --ignores 'bufferutil,utf-8-validate'",
|
|
39
40
|
"codegen": "npm run gen",
|
|
40
41
|
"ci": "npm run build && npm run lint:js && npm run lint:go",
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
diff --git a/node_modules/ts-poet/build/Import.js b/node_modules/ts-poet/build/Import.js
|
|
2
|
-
index b92bea3..
|
|
2
|
+
index b92bea3..d35c845 100644
|
|
3
3
|
--- a/node_modules/ts-poet/build/Import.js
|
|
4
4
|
+++ b/node_modules/ts-poet/build/Import.js
|
|
5
|
-
@@ -
|
|
5
|
+
@@ -26,6 +26,7 @@ exports.sameModule = exports.maybeRelativePath = exports.emitImports = exports.S
|
|
6
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
7
|
+
const path = __importStar(require("path"));
|
|
8
|
+
const Node_1 = require("./Node");
|
|
9
|
+
+const fs = require('fs');
|
|
10
|
+
const typeImportMarker = '(?:t:)?';
|
|
11
|
+
const fileNamePattern = '(?:[a-zA-Z0-9._-]+)';
|
|
12
|
+
const modulePattern = `@?(?:(?:${fileNamePattern}(?:/${fileNamePattern})*))`;
|
|
13
|
+
@@ -293,6 +294,16 @@ function emitImports(imports, ourModulePath, importMappings) {
|
|
6
14
|
return '';
|
|
7
15
|
}
|
|
8
16
|
let result = '';
|
|
@@ -13,11 +21,13 @@ index b92bea3..608add9 100644
|
|
|
13
21
|
+ // github.com/aperturerobotics/protobuf-project/example/example -> ./example/example
|
|
14
22
|
+ if (thisProject && ourModuleImportPath.startsWith(thisProject)) {
|
|
15
23
|
+ ourModuleImportPath = './' + ourModuleImportPath.substr(thisProject.length + 1);
|
|
24
|
+
+ } else {
|
|
25
|
+
+ ourModuleImportPath = './vendor/' + ourModuleImportPath;
|
|
16
26
|
+ }
|
|
17
27
|
const augmentImports = lodash_1.default.groupBy(filterInstances(imports, Augmented), (a) => a.augmented);
|
|
18
28
|
// Group the imports by source module they're imported from
|
|
19
29
|
const importsByModule = lodash_1.default.groupBy(imports.filter((it) => it.source !== undefined &&
|
|
20
|
-
@@ -307,7 +
|
|
30
|
+
@@ -307,7 +318,14 @@ function emitImports(imports, ourModulePath, importMappings) {
|
|
21
31
|
if (modulePath in importMappings) {
|
|
22
32
|
modulePath = importMappings[modulePath];
|
|
23
33
|
}
|
package/srpc/client-rpc.go
CHANGED
|
@@ -4,6 +4,7 @@ import (
|
|
|
4
4
|
"context"
|
|
5
5
|
|
|
6
6
|
"github.com/pkg/errors"
|
|
7
|
+
"github.com/sirupsen/logrus"
|
|
7
8
|
)
|
|
8
9
|
|
|
9
10
|
// ClientRPC represents the client side of an on-going RPC call message stream.
|
|
@@ -101,6 +102,16 @@ func (r *ClientRPC) HandlePacketData(data []byte) error {
|
|
|
101
102
|
return r.HandlePacket(pkt)
|
|
102
103
|
}
|
|
103
104
|
|
|
105
|
+
// HandleStreamClose handles the incoming stream closing w/ optional error.
|
|
106
|
+
func (r *ClientRPC) HandleStreamClose(closeErr error) {
|
|
107
|
+
if closeErr != nil {
|
|
108
|
+
if r.serverErr == nil {
|
|
109
|
+
r.serverErr = closeErr
|
|
110
|
+
}
|
|
111
|
+
r.Close()
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
104
115
|
// HandlePacket handles an incoming parsed message packet.
|
|
105
116
|
// Not concurrency safe: use a mutex if calling concurrently.
|
|
106
117
|
func (r *ClientRPC) HandlePacket(msg *Packet) error {
|
|
@@ -130,6 +141,7 @@ func (r *ClientRPC) HandleCallData(pkt *CallData) error {
|
|
|
130
141
|
return ErrCompleted
|
|
131
142
|
}
|
|
132
143
|
|
|
144
|
+
logrus.Infof("DEBUG: handling call data for %s/%s: %s", r.service, r.method, pkt)
|
|
133
145
|
if data := pkt.GetData(); len(data) != 0 || pkt.GetDataIsZero() {
|
|
134
146
|
select {
|
|
135
147
|
case <-r.ctx.Done():
|
package/srpc/client.go
CHANGED
|
@@ -19,7 +19,11 @@ type Client interface {
|
|
|
19
19
|
|
|
20
20
|
// OpenStreamFunc opens a stream with a remote.
|
|
21
21
|
// msgHandler must not be called concurrently.
|
|
22
|
-
type OpenStreamFunc = func(
|
|
22
|
+
type OpenStreamFunc = func(
|
|
23
|
+
ctx context.Context,
|
|
24
|
+
msgHandler PacketHandler,
|
|
25
|
+
closeHandler CloseHandler,
|
|
26
|
+
) (Writer, error)
|
|
23
27
|
|
|
24
28
|
// client implements Client with a transport.
|
|
25
29
|
type client struct {
|
|
@@ -44,7 +48,7 @@ func (c *client) Invoke(rctx context.Context, service, method string, in, out Me
|
|
|
44
48
|
return err
|
|
45
49
|
}
|
|
46
50
|
clientRPC := NewClientRPC(ctx, service, method)
|
|
47
|
-
writer, err := c.openStream(ctx, clientRPC.HandlePacket)
|
|
51
|
+
writer, err := c.openStream(ctx, clientRPC.HandlePacket, clientRPC.HandleStreamClose)
|
|
48
52
|
if err != nil {
|
|
49
53
|
return err
|
|
50
54
|
}
|
|
@@ -81,7 +85,7 @@ func (c *client) NewStream(ctx context.Context, service, method string, firstMsg
|
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
clientRPC := NewClientRPC(ctx, service, method)
|
|
84
|
-
writer, err := c.openStream(ctx, clientRPC.HandlePacket)
|
|
88
|
+
writer, err := c.openStream(ctx, clientRPC.HandlePacket, clientRPC.HandleStreamClose)
|
|
85
89
|
if err != nil {
|
|
86
90
|
return nil, err
|
|
87
91
|
}
|
package/srpc/mux.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { InvokeFn, Handler } from './handler'
|
|
2
2
|
|
|
3
|
+
// LookupMethod is a function to lookup a RPC method.
|
|
4
|
+
export type LookupMethod = (serviceID: string, methodID: string) => Promise<InvokeFn | null>
|
|
5
|
+
|
|
3
6
|
// Mux contains a set of <service, method> handlers.
|
|
4
7
|
export interface Mux {
|
|
5
|
-
// register registers a new RPC method handler.
|
|
6
|
-
register(handler: Handler): void
|
|
7
8
|
// lookupMethod looks up the method matching the service & method ID.
|
|
8
9
|
// returns null if not found.
|
|
9
10
|
lookupMethod(serviceID: string, methodID: string): Promise<InvokeFn | null>
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
// createMux builds a new
|
|
13
|
-
export function createMux():
|
|
13
|
+
// createMux builds a new StaticMux.
|
|
14
|
+
export function createMux(): StaticMux {
|
|
14
15
|
return new StaticMux()
|
|
15
16
|
}
|
|
16
17
|
|
|
@@ -22,6 +23,14 @@ type staticMuxMethods = { [methodID: string]: Handler }
|
|
|
22
23
|
export class StaticMux implements Mux {
|
|
23
24
|
// services contains a mapping from service id to handlers.
|
|
24
25
|
private services: { [id: string]: staticMuxMethods } = {}
|
|
26
|
+
// lookups is the list of lookup methods to call.
|
|
27
|
+
// called if the method is not resolved by the services list.
|
|
28
|
+
private lookups: LookupMethod[] = []
|
|
29
|
+
|
|
30
|
+
// lookupMethodFunc implements the LookupMethod type.
|
|
31
|
+
public get lookupMethodFunc(): LookupMethod {
|
|
32
|
+
return this.lookupMethod.bind(this)
|
|
33
|
+
}
|
|
25
34
|
|
|
26
35
|
public register(handler: Handler): void {
|
|
27
36
|
const serviceID = handler?.getServiceID()
|
|
@@ -36,13 +45,27 @@ export class StaticMux implements Mux {
|
|
|
36
45
|
this.services[serviceID] = serviceMethods
|
|
37
46
|
}
|
|
38
47
|
|
|
48
|
+
// registerLookupMethod registers a extra lookup function to the mux.
|
|
49
|
+
public registerLookupMethod(lookupMethod: LookupMethod) {
|
|
50
|
+
this.lookups.push(lookupMethod)
|
|
51
|
+
}
|
|
52
|
+
|
|
39
53
|
public async lookupMethod(
|
|
40
54
|
serviceID: string,
|
|
41
55
|
methodID: string
|
|
42
56
|
): Promise<InvokeFn | null> {
|
|
43
|
-
if (
|
|
44
|
-
|
|
57
|
+
if (serviceID) {
|
|
58
|
+
const invokeFn = await this.lookupViaMap(serviceID, methodID)
|
|
59
|
+
if (invokeFn) {
|
|
60
|
+
return invokeFn
|
|
61
|
+
}
|
|
45
62
|
}
|
|
63
|
+
|
|
64
|
+
return await this.lookupViaLookups(serviceID, methodID)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// lookupViaMap looks up the method via the services map.
|
|
68
|
+
private async lookupViaMap(serviceID: string, methodID: string): Promise<InvokeFn | null> {
|
|
46
69
|
const serviceMethods = this.services[serviceID]
|
|
47
70
|
if (!serviceMethods) {
|
|
48
71
|
return null
|
|
@@ -53,4 +76,16 @@ export class StaticMux implements Mux {
|
|
|
53
76
|
}
|
|
54
77
|
return await handler.lookupMethod(serviceID, methodID)
|
|
55
78
|
}
|
|
79
|
+
|
|
80
|
+
// lookupViaLookups looks up the method via the lookup funcs.
|
|
81
|
+
private async lookupViaLookups(serviceID: string, methodID: string): Promise<InvokeFn | null> {
|
|
82
|
+
for (const lookupMethod of this.lookups) {
|
|
83
|
+
const invokeFn = await lookupMethod(serviceID, methodID)
|
|
84
|
+
if (invokeFn) {
|
|
85
|
+
return invokeFn
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return null
|
|
90
|
+
}
|
|
56
91
|
}
|
package/srpc/muxed-conn.go
CHANGED
|
@@ -14,18 +14,13 @@ func NewClientWithMuxedConn(conn network.MuxedConn) Client {
|
|
|
14
14
|
|
|
15
15
|
// NewOpenStreamWithMuxedConn constructs a OpenStream func with a MuxedConn.
|
|
16
16
|
func NewOpenStreamWithMuxedConn(conn network.MuxedConn) OpenStreamFunc {
|
|
17
|
-
return func(ctx context.Context, msgHandler PacketHandler) (Writer, error) {
|
|
17
|
+
return func(ctx context.Context, msgHandler PacketHandler, closeHandler CloseHandler) (Writer, error) {
|
|
18
18
|
mstrm, err := conn.OpenStream(ctx)
|
|
19
19
|
if err != nil {
|
|
20
20
|
return nil, err
|
|
21
21
|
}
|
|
22
22
|
rw := NewPacketReadWriter(mstrm)
|
|
23
|
-
go
|
|
24
|
-
err := rw.ReadPump(msgHandler)
|
|
25
|
-
if err != nil {
|
|
26
|
-
_ = rw.Close()
|
|
27
|
-
}
|
|
28
|
-
}()
|
|
23
|
+
go rw.ReadPump(msgHandler, closeHandler)
|
|
29
24
|
return rw, nil
|
|
30
25
|
}
|
|
31
26
|
}
|
package/srpc/packet-rw.go
CHANGED
|
@@ -39,10 +39,23 @@ func (r *PacketReaderWriter) WritePacket(p *Packet) error {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// ReadPump executes the read pump in a goroutine.
|
|
42
|
-
|
|
42
|
+
//
|
|
43
|
+
// calls the handler when closed or returning an error
|
|
44
|
+
func (r *PacketReaderWriter) ReadPump(cb PacketHandler, closed CloseHandler) {
|
|
45
|
+
err := r.ReadToHandler(cb)
|
|
46
|
+
// signal that the stream is now closed.
|
|
47
|
+
if closed != nil {
|
|
48
|
+
closed(err)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ReadToHandler reads data to the given handler.
|
|
53
|
+
// Does not handle closing the stream, use ReadPump instead.
|
|
54
|
+
func (r *PacketReaderWriter) ReadToHandler(cb PacketHandler) error {
|
|
43
55
|
var currLen uint32
|
|
44
56
|
buf := make([]byte, 2048)
|
|
45
57
|
for {
|
|
58
|
+
// read some data into the buffer
|
|
46
59
|
n, err := r.rw.Read(buf)
|
|
47
60
|
if err != nil {
|
|
48
61
|
if err == io.EOF {
|
|
@@ -50,17 +63,20 @@ func (r *PacketReaderWriter) ReadPump(cb PacketHandler) error {
|
|
|
50
63
|
}
|
|
51
64
|
return err
|
|
52
65
|
}
|
|
66
|
+
// push the data to r.buf
|
|
53
67
|
_, err = r.buf.Write(buf[:n])
|
|
54
68
|
if err != nil {
|
|
55
69
|
return err
|
|
56
70
|
}
|
|
57
71
|
|
|
58
|
-
// check if we have enough for a length prefix
|
|
72
|
+
// check if we have enough data for a length prefix
|
|
59
73
|
bufLen := r.buf.Len()
|
|
74
|
+
if bufLen < 4 {
|
|
75
|
+
continue
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// parse the length prefix if not done already
|
|
60
79
|
if currLen == 0 {
|
|
61
|
-
if bufLen < 4 {
|
|
62
|
-
continue
|
|
63
|
-
}
|
|
64
80
|
currLen = r.readLengthPrefix(r.buf.Bytes())
|
|
65
81
|
if currLen == 0 {
|
|
66
82
|
return errors.New("unexpected zero len prefix")
|
|
@@ -69,6 +85,8 @@ func (r *PacketReaderWriter) ReadPump(cb PacketHandler) error {
|
|
|
69
85
|
return errors.Errorf("message size %v greater than maximum %v", currLen, maxMessageSize)
|
|
70
86
|
}
|
|
71
87
|
}
|
|
88
|
+
|
|
89
|
+
// emit the packet if fully buffered
|
|
72
90
|
if currLen != 0 && bufLen >= int(currLen)+4 {
|
|
73
91
|
pkt := r.buf.Next(int(currLen + 4))[4:]
|
|
74
92
|
currLen = 0
|
package/srpc/packet.go
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
package srpc
|
|
2
2
|
|
|
3
3
|
// PacketHandler handles a packet.
|
|
4
|
+
//
|
|
5
|
+
// pkt is optional (can be nil)
|
|
6
|
+
// if closeErr is set, the stream is closed after pkt.
|
|
4
7
|
type PacketHandler = func(pkt *Packet) error
|
|
5
8
|
|
|
9
|
+
// CloseHandler handles the stream closing with an optional error.
|
|
10
|
+
type CloseHandler = func(closeErr error)
|
|
11
|
+
|
|
6
12
|
// Validate performs cursory validation of the packet.
|
|
7
13
|
func (p *Packet) Validate() error {
|
|
8
14
|
switch b := p.GetBody().(type) {
|
|
@@ -58,7 +64,7 @@ func NewCallDataPacket(data []byte, dataIsZero bool, complete bool, err error) *
|
|
|
58
64
|
|
|
59
65
|
// Validate performs cursory validation of the packet.
|
|
60
66
|
func (p *CallData) Validate() error {
|
|
61
|
-
if len(p.GetData()) == 0 && !p.GetComplete() && len(p.GetError()) == 0 {
|
|
67
|
+
if len(p.GetData()) == 0 && !p.GetComplete() && len(p.GetError()) == 0 && !p.GetDataIsZero() {
|
|
62
68
|
return ErrEmptyPacket
|
|
63
69
|
}
|
|
64
70
|
return nil
|
package/srpc/server-pipe.go
CHANGED
|
@@ -9,18 +9,13 @@ import (
|
|
|
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
|
-
return func(ctx context.Context, msgHandler PacketHandler) (Writer, error) {
|
|
12
|
+
return func(ctx context.Context, msgHandler PacketHandler, closeHandler CloseHandler) (Writer, error) {
|
|
13
13
|
srvPipe, clientPipe := net.Pipe()
|
|
14
14
|
go func() {
|
|
15
15
|
_ = server.HandleStream(ctx, srvPipe)
|
|
16
16
|
}()
|
|
17
17
|
clientPrw := NewPacketReadWriter(clientPipe)
|
|
18
|
-
go
|
|
19
|
-
err := clientPrw.ReadPump(msgHandler)
|
|
20
|
-
if err != nil {
|
|
21
|
-
_ = clientPrw.Close()
|
|
22
|
-
}
|
|
23
|
-
}()
|
|
18
|
+
go clientPrw.ReadPump(msgHandler, closeHandler)
|
|
24
19
|
return clientPrw, nil
|
|
25
20
|
}
|
|
26
21
|
}
|
package/srpc/server-rpc.go
CHANGED
|
@@ -29,7 +29,7 @@ type ServerRPC struct {
|
|
|
29
29
|
dataChClosed bool
|
|
30
30
|
// clientErr is an error set by the client.
|
|
31
31
|
// before dataCh is closed, managed by HandlePacket.
|
|
32
|
-
// immutable after dataCh is closed
|
|
32
|
+
// immutable after dataCh is closed or ctxCancel
|
|
33
33
|
clientErr error
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -54,9 +54,32 @@ func (r *ServerRPC) Context() context.Context {
|
|
|
54
54
|
return r.ctx
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
// Wait waits for the RPC to finish.
|
|
58
|
+
func (r *ServerRPC) Wait(ctx context.Context) error {
|
|
59
|
+
select {
|
|
60
|
+
case <-ctx.Done():
|
|
61
|
+
return context.Canceled
|
|
62
|
+
case <-r.ctx.Done():
|
|
63
|
+
}
|
|
64
|
+
return r.clientErr
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// HandleStreamClose handles the incoming stream closing w/ optional error.
|
|
68
|
+
func (r *ServerRPC) HandleStreamClose(closeErr error) {
|
|
69
|
+
if closeErr != nil {
|
|
70
|
+
if r.clientErr == nil {
|
|
71
|
+
r.clientErr = closeErr
|
|
72
|
+
}
|
|
73
|
+
r.Close()
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
57
77
|
// HandlePacket handles an incoming parsed message packet.
|
|
58
78
|
// Not concurrency safe: use a mutex if calling concurrently.
|
|
59
79
|
func (r *ServerRPC) HandlePacket(msg *Packet) error {
|
|
80
|
+
if msg == nil {
|
|
81
|
+
return nil
|
|
82
|
+
}
|
|
60
83
|
if err := msg.Validate(); err != nil {
|
|
61
84
|
return err
|
|
62
85
|
}
|
|
@@ -147,6 +170,9 @@ func (r *ServerRPC) invokeRPC() {
|
|
|
147
170
|
// Close releases any resources held by the ServerRPC.
|
|
148
171
|
// not concurrency safe with HandlePacket.
|
|
149
172
|
func (r *ServerRPC) Close() {
|
|
173
|
+
if r.clientErr == nil {
|
|
174
|
+
r.clientErr = context.Canceled
|
|
175
|
+
}
|
|
150
176
|
r.ctxCancel()
|
|
151
177
|
if r.service == "" {
|
|
152
178
|
// invokeRPC has not been called, otherwise it would call Close()
|
package/srpc/server-rpc.ts
CHANGED
|
@@ -3,16 +3,16 @@ import type { Sink } from 'it-stream-types'
|
|
|
3
3
|
import type { CallData, CallStart } from './rpcproto.pb.js'
|
|
4
4
|
import { CommonRPC } from './common-rpc.js'
|
|
5
5
|
import { InvokeFn } from './handler.js'
|
|
6
|
-
import {
|
|
6
|
+
import { LookupMethod } from './mux.js'
|
|
7
7
|
|
|
8
8
|
// ServerRPC is an ongoing RPC from the server side.
|
|
9
9
|
export class ServerRPC extends CommonRPC {
|
|
10
|
-
//
|
|
11
|
-
private
|
|
10
|
+
// lookupMethod looks up the incoming RPC methods.
|
|
11
|
+
private lookupMethod: LookupMethod
|
|
12
12
|
|
|
13
|
-
constructor(
|
|
13
|
+
constructor(lookupMethod: LookupMethod) {
|
|
14
14
|
super()
|
|
15
|
-
this.
|
|
15
|
+
this.lookupMethod = lookupMethod
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
// handleCallStart handles a CallStart cket.
|
|
@@ -25,7 +25,7 @@ export class ServerRPC extends CommonRPC {
|
|
|
25
25
|
if (!this.service || !this.method) {
|
|
26
26
|
throw new Error('rpcService and rpcMethod cannot be empty')
|
|
27
27
|
}
|
|
28
|
-
const methodDef = await this.
|
|
28
|
+
const methodDef = await this.lookupMethod(this.service, this.method)
|
|
29
29
|
if (!methodDef) {
|
|
30
30
|
throw new Error(`not found: ${this.service}/${this.method}`)
|
|
31
31
|
}
|
package/srpc/server.go
CHANGED
|
@@ -32,9 +32,8 @@ func (s *Server) HandleStream(ctx context.Context, rwc io.ReadWriteCloser) error
|
|
|
32
32
|
serverRPC := NewServerRPC(subCtx, s.mux)
|
|
33
33
|
prw := NewPacketReadWriter(rwc)
|
|
34
34
|
serverRPC.SetWriter(prw)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return err
|
|
35
|
+
go prw.ReadPump(serverRPC.HandlePacket, serverRPC.HandleStreamClose)
|
|
36
|
+
return serverRPC.Wait(ctx)
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
// AcceptMuxedConn runs a loop which calls Accept on a muxer to handle streams.
|
package/srpc/server.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Stream } from '@libp2p/interface-connection'
|
|
|
2
2
|
import { Duplex } from 'it-stream-types'
|
|
3
3
|
import { pipe } from 'it-pipe'
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { LookupMethod } from './mux.js'
|
|
6
6
|
import { ServerRPC } from './server-rpc.js'
|
|
7
7
|
import { Packet } from './rpcproto.pb.js'
|
|
8
8
|
import {
|
|
@@ -16,11 +16,11 @@ import { RpcStreamHandler } from '../rpcstream/rpcstream.js'
|
|
|
16
16
|
|
|
17
17
|
// Server implements the SRPC server in TypeScript with a Mux.
|
|
18
18
|
export class Server implements StreamHandler {
|
|
19
|
-
//
|
|
20
|
-
private
|
|
19
|
+
// lookupMethod looks up the incoming RPC methods.
|
|
20
|
+
private lookupMethod: LookupMethod
|
|
21
21
|
|
|
22
|
-
constructor(
|
|
23
|
-
this.
|
|
22
|
+
constructor(lookupMethod: LookupMethod) {
|
|
23
|
+
this.lookupMethod = lookupMethod
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
// rpcStreamHandler implements the RpcStreamHandler interface.
|
|
@@ -31,7 +31,7 @@ export class Server implements StreamHandler {
|
|
|
31
31
|
// startRpc starts a new server-side RPC.
|
|
32
32
|
// the returned RPC handles incoming Packets.
|
|
33
33
|
public startRpc(): ServerRPC {
|
|
34
|
-
return new ServerRPC(this.
|
|
34
|
+
return new ServerRPC(this.lookupMethod)
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
// handleStream handles an incoming Uint8Array message duplex.
|
package/srpc/websocket.go
CHANGED
|
@@ -43,19 +43,14 @@ func (w *WebSocketConn) AcceptStream() (io.ReadWriteCloser, error) {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
// OpenStream tries to open a stream with the remote.
|
|
46
|
-
func (w *WebSocketConn) OpenStream(ctx context.Context, msgHandler PacketHandler) (Writer, error) {
|
|
46
|
+
func (w *WebSocketConn) OpenStream(ctx context.Context, msgHandler PacketHandler, closeHandler CloseHandler) (Writer, error) {
|
|
47
47
|
muxedStream, err := w.mconn.OpenStream(ctx)
|
|
48
48
|
if err != nil {
|
|
49
49
|
return nil, err
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
rw := NewPacketReadWriter(muxedStream)
|
|
53
|
-
go
|
|
54
|
-
err := rw.ReadPump(msgHandler)
|
|
55
|
-
if err != nil {
|
|
56
|
-
_ = rw.Close()
|
|
57
|
-
}
|
|
58
|
-
}()
|
|
53
|
+
go rw.ReadPump(msgHandler, closeHandler)
|
|
59
54
|
return rw, nil
|
|
60
55
|
}
|
|
61
56
|
|