starpc 0.0.1 → 0.1.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/LICENSE +19 -0
- package/Makefile +140 -0
- package/README.md +128 -26
- package/dist/echo/echo.d.ts +59 -0
- package/dist/echo/echo.js +85 -0
- package/dist/srpc/broadcast-channel.d.ts +16 -0
- package/dist/srpc/broadcast-channel.js +60 -0
- package/dist/srpc/client-rpc.d.ts +31 -0
- package/dist/srpc/client-rpc.js +176 -0
- package/dist/srpc/client.d.ts +12 -0
- package/dist/srpc/client.js +129 -0
- package/dist/srpc/conn.d.ts +14 -0
- package/dist/srpc/conn.js +38 -0
- package/dist/srpc/index.d.ts +3 -0
- package/dist/srpc/index.js +2 -0
- package/dist/srpc/packet.d.ts +9 -0
- package/dist/srpc/packet.js +89 -0
- package/dist/srpc/rpcproto.d.ts +194 -0
- package/dist/srpc/rpcproto.js +322 -0
- package/dist/srpc/stream.d.ts +5 -0
- package/dist/srpc/stream.js +1 -0
- package/dist/srpc/ts-proto-rpc.d.ts +7 -0
- package/dist/srpc/ts-proto-rpc.js +1 -0
- package/dist/srpc/websocket.d.ts +7 -0
- package/dist/srpc/websocket.js +18 -0
- package/e2e/e2e.go +1 -0
- package/e2e/e2e_test.go +158 -0
- package/echo/echo.go +1 -0
- package/echo/echo.pb.go +165 -0
- package/echo/echo.proto +19 -0
- package/echo/echo.ts +191 -0
- package/echo/echo_srpc.pb.go +333 -0
- package/echo/echo_vtproto.pb.go +271 -0
- package/echo/server.go +73 -0
- package/go.mod +50 -0
- package/go.sum +210 -0
- package/integration/integration.bash +25 -0
- package/integration/integration.go +30 -0
- package/integration/integration.ts +54 -0
- package/integration/tsconfig.json +11 -0
- package/package.json +77 -9
- package/patches/@libp2p+mplex+1.2.1.patch +22 -0
- package/srpc/broadcast-channel.ts +72 -0
- package/srpc/client-rpc.go +163 -0
- package/srpc/client-rpc.ts +197 -0
- package/srpc/client.go +96 -0
- package/srpc/client.ts +182 -0
- package/srpc/conn.go +7 -0
- package/srpc/conn.ts +49 -0
- package/srpc/errors.go +20 -0
- package/srpc/handler.go +13 -0
- package/srpc/index.ts +3 -0
- package/srpc/message.go +7 -0
- package/srpc/mux.go +76 -0
- package/srpc/packet-rw.go +102 -0
- package/srpc/packet.go +71 -0
- package/srpc/packet.ts +105 -0
- package/srpc/rpc-stream.go +76 -0
- package/srpc/rpcproto.pb.go +455 -0
- package/srpc/rpcproto.proto +46 -0
- package/srpc/rpcproto.ts +467 -0
- package/srpc/rpcproto_vtproto.pb.go +1094 -0
- package/srpc/server-http.go +66 -0
- package/srpc/server-pipe.go +26 -0
- package/srpc/server-rpc.go +160 -0
- package/srpc/server.go +29 -0
- package/srpc/stream-pipe.go +86 -0
- package/srpc/stream.go +24 -0
- package/srpc/stream.ts +11 -0
- package/srpc/ts-proto-rpc.ts +29 -0
- package/srpc/websocket.go +68 -0
- package/srpc/websocket.ts +22 -0
- package/srpc/writer.go +9 -0
package/srpc/client.go
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
package srpc
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"io"
|
|
6
|
+
|
|
7
|
+
"github.com/pkg/errors"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// Client implements a SRPC client which can initiate RPC streams.
|
|
11
|
+
type Client interface {
|
|
12
|
+
// Invoke executes a unary RPC with the remote.
|
|
13
|
+
Invoke(ctx context.Context, service, method string, in, out Message) error
|
|
14
|
+
|
|
15
|
+
// NewStream starts a streaming RPC with the remote & returns the stream.
|
|
16
|
+
// firstMsg is optional.
|
|
17
|
+
NewStream(ctx context.Context, service, method string, firstMsg Message) (Stream, error)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// OpenStreamFunc opens a stream with a remote.
|
|
21
|
+
// msgHandler must not be called concurrently.
|
|
22
|
+
type OpenStreamFunc = func(ctx context.Context, msgHandler func(pkt *Packet) error) (Writer, error)
|
|
23
|
+
|
|
24
|
+
// client implements Client with a transport.
|
|
25
|
+
type client struct {
|
|
26
|
+
// openStream opens a new stream.
|
|
27
|
+
openStream OpenStreamFunc
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// NewClient constructs a client with a OpenStreamFunc.
|
|
31
|
+
func NewClient(openStream OpenStreamFunc) Client {
|
|
32
|
+
return &client{
|
|
33
|
+
openStream: openStream,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Invoke executes a unary RPC with the remote.
|
|
38
|
+
func (c *client) Invoke(rctx context.Context, service, method string, in, out Message) error {
|
|
39
|
+
ctx, ctxCancel := context.WithCancel(rctx)
|
|
40
|
+
defer ctxCancel()
|
|
41
|
+
|
|
42
|
+
firstMsg, err := in.MarshalVT()
|
|
43
|
+
if err != nil {
|
|
44
|
+
return err
|
|
45
|
+
}
|
|
46
|
+
clientRPC := NewClientRPC(ctx, service, method)
|
|
47
|
+
writer, err := c.openStream(ctx, clientRPC.HandlePacket)
|
|
48
|
+
if err != nil {
|
|
49
|
+
return err
|
|
50
|
+
}
|
|
51
|
+
if err := clientRPC.Start(writer, firstMsg); err != nil {
|
|
52
|
+
return err
|
|
53
|
+
}
|
|
54
|
+
msgs, err := clientRPC.ReadAll()
|
|
55
|
+
if err != nil {
|
|
56
|
+
// this includes any server returned error.
|
|
57
|
+
return err
|
|
58
|
+
}
|
|
59
|
+
if len(msgs) == 0 {
|
|
60
|
+
// no reply? return eof.
|
|
61
|
+
return io.EOF
|
|
62
|
+
}
|
|
63
|
+
// parse first message to out
|
|
64
|
+
if err := out.UnmarshalVT(msgs[0]); err != nil {
|
|
65
|
+
return errors.Wrap(ErrInvalidMessage, err.Error())
|
|
66
|
+
}
|
|
67
|
+
// done
|
|
68
|
+
return nil
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// NewStream starts a streaming RPC with the remote & returns the stream.
|
|
72
|
+
// firstMsg is optional.
|
|
73
|
+
func (c *client) NewStream(ctx context.Context, service, method string, firstMsg Message) (Stream, error) {
|
|
74
|
+
var firstMsgData []byte
|
|
75
|
+
if firstMsg != nil {
|
|
76
|
+
var err error
|
|
77
|
+
firstMsgData, err = firstMsg.MarshalVT()
|
|
78
|
+
if err != nil {
|
|
79
|
+
return nil, err
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
clientRPC := NewClientRPC(ctx, service, method)
|
|
84
|
+
writer, err := c.openStream(ctx, clientRPC.HandlePacket)
|
|
85
|
+
if err != nil {
|
|
86
|
+
return nil, err
|
|
87
|
+
}
|
|
88
|
+
if err := clientRPC.Start(writer, firstMsgData); err != nil {
|
|
89
|
+
return nil, err
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return NewRPCStream(ctx, clientRPC.writer, clientRPC.dataCh), nil
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// _ is a type assertion
|
|
96
|
+
var _ Client = ((*client)(nil))
|
package/srpc/client.ts
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { Observable, from as observableFrom } from 'rxjs'
|
|
2
|
+
import type { TsProtoRpc } from './ts-proto-rpc'
|
|
3
|
+
import type { OpenStreamFunc } from './stream'
|
|
4
|
+
import { DataCb, ClientRPC } from './client-rpc'
|
|
5
|
+
import { pipe } from 'it-pipe'
|
|
6
|
+
import { pushable, Pushable } from 'it-pushable'
|
|
7
|
+
import {
|
|
8
|
+
decodePacketSource,
|
|
9
|
+
encodePacketSource,
|
|
10
|
+
parseLengthPrefixTransform,
|
|
11
|
+
prependLengthPrefixTransform,
|
|
12
|
+
} from './packet'
|
|
13
|
+
|
|
14
|
+
// unaryDataCb builds a new unary request data callback.
|
|
15
|
+
function unaryDataCb(resolve: (data: Uint8Array) => void): DataCb {
|
|
16
|
+
return async (
|
|
17
|
+
data: Uint8Array
|
|
18
|
+
): Promise<boolean | void> => {
|
|
19
|
+
// resolve the promise
|
|
20
|
+
resolve(data)
|
|
21
|
+
// this is the last data we expect.
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// streamingDataCb builds a new streaming request data callback.
|
|
27
|
+
/*
|
|
28
|
+
function streamingDataCb(resolve: (data: Uint8Array) => void): DataCb {
|
|
29
|
+
return async (
|
|
30
|
+
data: Uint8Array
|
|
31
|
+
): Promise<boolean | void> => {
|
|
32
|
+
// TODO
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
// writeClientStream registers the subscriber to write the client data stream.
|
|
38
|
+
function writeClientStream(call: ClientRPC, data: Observable<Uint8Array>) {
|
|
39
|
+
data.subscribe({
|
|
40
|
+
next(value) {
|
|
41
|
+
call.writeCallData(value)
|
|
42
|
+
},
|
|
43
|
+
error(err) {
|
|
44
|
+
call.close(err)
|
|
45
|
+
},
|
|
46
|
+
complete() {
|
|
47
|
+
call.writeCallData(new Uint8Array(0), true)
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// waitCallComplete handles the call complete promise.
|
|
53
|
+
function waitCallComplete(
|
|
54
|
+
call: ClientRPC,
|
|
55
|
+
resolve: (data: Uint8Array) => void,
|
|
56
|
+
reject: (err: Error) => void,
|
|
57
|
+
) {
|
|
58
|
+
call.waitComplete().catch(reject).finally(() => {
|
|
59
|
+
// ensure we resolve it if no data was ever returned.
|
|
60
|
+
resolve(new Uint8Array())
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Client implements the ts-proto Rpc interface with the drpcproto protocol.
|
|
65
|
+
export class Client implements TsProtoRpc {
|
|
66
|
+
// openConnFn is the open connection function.
|
|
67
|
+
// called when starting RPC.
|
|
68
|
+
private openConnFn: OpenStreamFunc
|
|
69
|
+
|
|
70
|
+
constructor(openConnFn: OpenStreamFunc) {
|
|
71
|
+
this.openConnFn = openConnFn
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// request starts a non-streaming request.
|
|
75
|
+
public async request(
|
|
76
|
+
service: string,
|
|
77
|
+
method: string,
|
|
78
|
+
data: Uint8Array
|
|
79
|
+
): Promise<Uint8Array> {
|
|
80
|
+
return new Promise<Uint8Array>((resolve, reject) => {
|
|
81
|
+
const dataCb = unaryDataCb(resolve)
|
|
82
|
+
this.startRpc(service, method, data, dataCb)
|
|
83
|
+
.then((call) => {
|
|
84
|
+
waitCallComplete(call, resolve, reject)
|
|
85
|
+
})
|
|
86
|
+
.catch(reject)
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// clientStreamingRequest starts a client side streaming request.
|
|
91
|
+
public clientStreamingRequest(
|
|
92
|
+
service: string,
|
|
93
|
+
method: string,
|
|
94
|
+
data: Observable<Uint8Array>
|
|
95
|
+
): Promise<Uint8Array> {
|
|
96
|
+
return new Promise<Uint8Array>((resolve, reject) => {
|
|
97
|
+
const dataCb = unaryDataCb(resolve)
|
|
98
|
+
this.startRpc(service, method, null, dataCb)
|
|
99
|
+
.then((call) => {
|
|
100
|
+
writeClientStream(call, data)
|
|
101
|
+
waitCallComplete(call, resolve, reject)
|
|
102
|
+
})
|
|
103
|
+
.catch(reject)
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// serverStreamingRequest starts a server-side streaming request.
|
|
108
|
+
public serverStreamingRequest(
|
|
109
|
+
service: string,
|
|
110
|
+
method: string,
|
|
111
|
+
data: Uint8Array
|
|
112
|
+
): Observable<Uint8Array> {
|
|
113
|
+
const pushServerData: Pushable<Uint8Array> = pushable()
|
|
114
|
+
const serverData = observableFrom(pushServerData)
|
|
115
|
+
const dataCb: DataCb = async (data: Uint8Array): Promise<boolean | void> => {
|
|
116
|
+
// push the message to the observable
|
|
117
|
+
pushServerData.push(data)
|
|
118
|
+
// expect more messages
|
|
119
|
+
return true
|
|
120
|
+
}
|
|
121
|
+
this.startRpc(service, method, data, dataCb)
|
|
122
|
+
.then((call) => {
|
|
123
|
+
call.waitComplete().catch((err: Error) => {
|
|
124
|
+
pushServerData.throw(err)
|
|
125
|
+
}).finally(() => {
|
|
126
|
+
pushServerData.end()
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
.catch(pushServerData.throw.bind(pushServerData))
|
|
130
|
+
return serverData
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// bidirectionalStreamingRequest starts a two-way streaming request.
|
|
134
|
+
public bidirectionalStreamingRequest(
|
|
135
|
+
service: string,
|
|
136
|
+
method: string,
|
|
137
|
+
data: Observable<Uint8Array>
|
|
138
|
+
): Observable<Uint8Array> {
|
|
139
|
+
const pushServerData: Pushable<Uint8Array> = pushable()
|
|
140
|
+
const serverData = observableFrom(pushServerData)
|
|
141
|
+
const dataCb: DataCb = async (data: Uint8Array): Promise<boolean | void> => {
|
|
142
|
+
// push the message to the observable
|
|
143
|
+
pushServerData.push(data)
|
|
144
|
+
// expect more messages
|
|
145
|
+
return true
|
|
146
|
+
}
|
|
147
|
+
this.startRpc(service, method, null, dataCb)
|
|
148
|
+
.then((call) => {
|
|
149
|
+
writeClientStream(call, data)
|
|
150
|
+
call.waitComplete().catch((err: Error) => {
|
|
151
|
+
pushServerData.throw(err)
|
|
152
|
+
}).finally(() => {
|
|
153
|
+
pushServerData.end()
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
.catch(pushServerData.throw.bind(pushServerData))
|
|
157
|
+
return serverData
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// startRpc is a common utility function to begin a rpc call.
|
|
161
|
+
// throws any error starting the rpc call
|
|
162
|
+
private async startRpc(
|
|
163
|
+
rpcService: string,
|
|
164
|
+
rpcMethod: string,
|
|
165
|
+
data: Uint8Array | null,
|
|
166
|
+
dataCb: DataCb
|
|
167
|
+
): Promise<ClientRPC> {
|
|
168
|
+
const conn = await this.openConnFn()
|
|
169
|
+
const call = new ClientRPC(rpcService, rpcMethod, dataCb)
|
|
170
|
+
pipe(
|
|
171
|
+
conn,
|
|
172
|
+
parseLengthPrefixTransform(),
|
|
173
|
+
decodePacketSource,
|
|
174
|
+
call,
|
|
175
|
+
encodePacketSource,
|
|
176
|
+
prependLengthPrefixTransform(),
|
|
177
|
+
conn,
|
|
178
|
+
)
|
|
179
|
+
await call.writeCallStart(data || undefined)
|
|
180
|
+
return call
|
|
181
|
+
}
|
|
182
|
+
}
|
package/srpc/conn.go
ADDED
package/srpc/conn.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Stream } from '@libp2p/interfaces/connection'
|
|
2
|
+
import type { Duplex } from 'it-stream-types'
|
|
3
|
+
import { Components } from '@libp2p/interfaces/components'
|
|
4
|
+
import { MplexStreamMuxer } from '@libp2p/mplex'
|
|
5
|
+
import type { Stream as SRPCStream } from './stream'
|
|
6
|
+
import { Client } from './client'
|
|
7
|
+
|
|
8
|
+
// Conn implements a generic connection with a two-way stream.
|
|
9
|
+
export class Conn implements Duplex<Uint8Array> {
|
|
10
|
+
// muxer is the mplex stream muxer.
|
|
11
|
+
private muxer: MplexStreamMuxer
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
// see https://github.com/libp2p/js-libp2p-mplex/pull/179
|
|
15
|
+
this.muxer = new MplexStreamMuxer(new Components(), {
|
|
16
|
+
onIncomingStream: this.handleIncomingStream.bind(this),
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// sink returns the message sink.
|
|
21
|
+
get sink() {
|
|
22
|
+
return this.muxer.sink
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// source returns the outgoing message source.
|
|
26
|
+
get source() {
|
|
27
|
+
return this.muxer.source
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// streams returns the set of all ongoing streams.
|
|
31
|
+
get streams() {
|
|
32
|
+
return this.muxer.streams
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// buildClient builds a new client from the connection.
|
|
36
|
+
public buildClient(): Client {
|
|
37
|
+
return new Client(this.openStream.bind(this))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// openStream implements the client open stream function.
|
|
41
|
+
public async openStream(): Promise<SRPCStream> {
|
|
42
|
+
return this.muxer.newStream()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// handleIncomingStream handles an incoming stream.
|
|
46
|
+
private handleIncomingStream(strm: Stream) {
|
|
47
|
+
strm.abort(new Error('server -> client streams not implemented'))
|
|
48
|
+
}
|
|
49
|
+
}
|
package/srpc/errors.go
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
package srpc
|
|
2
|
+
|
|
3
|
+
import "errors"
|
|
4
|
+
|
|
5
|
+
var (
|
|
6
|
+
// ErrUnimplemented is returned if the RPC method was not implemented.
|
|
7
|
+
ErrUnimplemented = errors.New("unimplemented")
|
|
8
|
+
// ErrCompleted is returned if a message is received after the rpc was completed.
|
|
9
|
+
ErrCompleted = errors.New("unexpected packet after rpc was completed")
|
|
10
|
+
// ErrUnrecognizedPacket is returned if the packet type was not recognized.
|
|
11
|
+
ErrUnrecognizedPacket = errors.New("unrecognized packet type")
|
|
12
|
+
// ErrEmptyPacket is returned if nothing is specified in a packet.
|
|
13
|
+
ErrEmptyPacket = errors.New("invalid empty packet")
|
|
14
|
+
// ErrInvalidMessage indicates the message failed to parse.
|
|
15
|
+
ErrInvalidMessage = errors.New("invalid message")
|
|
16
|
+
// ErrEmptyMethodID is returned if the method id was empty.
|
|
17
|
+
ErrEmptyMethodID = errors.New("method id empty")
|
|
18
|
+
// ErrEmptyServiceID is returned if the service id was empty.
|
|
19
|
+
ErrEmptyServiceID = errors.New("service id empty")
|
|
20
|
+
)
|
package/srpc/handler.go
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
package srpc
|
|
2
|
+
|
|
3
|
+
// Handler describes a SRPC call handler implementation.
|
|
4
|
+
type Handler interface {
|
|
5
|
+
// GetServiceID returns the ID of the service.
|
|
6
|
+
GetServiceID() string
|
|
7
|
+
// GetMethodIDs returns the list of methods for the service.
|
|
8
|
+
GetMethodIDs() []string
|
|
9
|
+
// InvokeMethod invokes the method matching the service & method ID.
|
|
10
|
+
// Returns false, nil if not found.
|
|
11
|
+
// If service string is empty, ignore it.
|
|
12
|
+
InvokeMethod(serviceID, methodID string, strm Stream) (bool, error)
|
|
13
|
+
}
|
package/srpc/index.ts
ADDED
package/srpc/message.go
ADDED
package/srpc/mux.go
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
package srpc
|
|
2
|
+
|
|
3
|
+
import "sync"
|
|
4
|
+
|
|
5
|
+
// Mux contains a set of <service, method> handlers.
|
|
6
|
+
type Mux interface {
|
|
7
|
+
// Register registers a new RPC method handler (service).
|
|
8
|
+
Register(handler Handler) error
|
|
9
|
+
// InvokeMethod invokes the method matching the service & method ID.
|
|
10
|
+
// Returns false, nil if not found.
|
|
11
|
+
// If service string is empty, ignore it.
|
|
12
|
+
InvokeMethod(serviceID, methodID string, strm Stream) (bool, error)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// muxMethods is a mapping from method id to handler.
|
|
16
|
+
type muxMethods map[string]Handler
|
|
17
|
+
|
|
18
|
+
// mux is the default implementation of Mux.
|
|
19
|
+
type mux struct {
|
|
20
|
+
// rmtx guards below fields
|
|
21
|
+
rmtx sync.RWMutex
|
|
22
|
+
// services contains a mapping from services to handlers.
|
|
23
|
+
services map[string]muxMethods
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// NewMux constructs a new Mux.
|
|
27
|
+
func NewMux() Mux {
|
|
28
|
+
return &mux{services: make(map[string]muxMethods)}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Register registers a new RPC method handler (service).
|
|
32
|
+
func (m *mux) Register(handler Handler) error {
|
|
33
|
+
serviceID := handler.GetServiceID()
|
|
34
|
+
methodIDs := handler.GetMethodIDs()
|
|
35
|
+
if serviceID == "" {
|
|
36
|
+
return ErrEmptyServiceID
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
m.rmtx.Lock()
|
|
40
|
+
defer m.rmtx.Unlock()
|
|
41
|
+
|
|
42
|
+
serviceMethods := m.services[serviceID]
|
|
43
|
+
if serviceMethods == nil {
|
|
44
|
+
serviceMethods = make(muxMethods)
|
|
45
|
+
m.services[serviceID] = serviceMethods
|
|
46
|
+
}
|
|
47
|
+
for _, methodID := range methodIDs {
|
|
48
|
+
if methodID != "" {
|
|
49
|
+
serviceMethods[methodID] = handler
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return nil
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// InvokeMethod invokes the method matching the service & method ID.
|
|
57
|
+
// Returns false, nil if not found.
|
|
58
|
+
// If service string is empty, ignore it.
|
|
59
|
+
func (m *mux) InvokeMethod(serviceID, methodID string, strm Stream) (bool, error) {
|
|
60
|
+
var handler Handler
|
|
61
|
+
m.rmtx.RLock()
|
|
62
|
+
svcMethods := m.services[serviceID]
|
|
63
|
+
if svcMethods != nil {
|
|
64
|
+
handler = svcMethods[methodID]
|
|
65
|
+
}
|
|
66
|
+
m.rmtx.RUnlock()
|
|
67
|
+
|
|
68
|
+
if handler == nil {
|
|
69
|
+
return false, nil
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return handler.InvokeMethod(serviceID, methodID, strm)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// _ is a type assertion
|
|
76
|
+
var _ Mux = ((*mux)(nil))
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
package srpc
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"encoding/binary"
|
|
6
|
+
"io"
|
|
7
|
+
|
|
8
|
+
"github.com/pkg/errors"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// maxMessageSize is the max message size in bytes
|
|
12
|
+
var maxMessageSize = 1e7
|
|
13
|
+
|
|
14
|
+
// PacketReaderWriter reads and writes packets from a io.ReadWriter.
|
|
15
|
+
// Uses a LittleEndian uint32 length prefix.
|
|
16
|
+
type PacketReaderWriter struct {
|
|
17
|
+
// rw is the io.ReadWriterCloser
|
|
18
|
+
rw io.ReadWriteCloser
|
|
19
|
+
// cb is the callback
|
|
20
|
+
cb PacketHandler
|
|
21
|
+
// buf is the buffered data
|
|
22
|
+
buf bytes.Buffer
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// NewPacketReadWriter constructs a new read/writer.
|
|
26
|
+
func NewPacketReadWriter(rw io.ReadWriteCloser, cb PacketHandler) *PacketReaderWriter {
|
|
27
|
+
return &PacketReaderWriter{rw: rw, cb: cb}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// WritePacket writes a packet to the writer.
|
|
31
|
+
func (r *PacketReaderWriter) WritePacket(p *Packet) error {
|
|
32
|
+
msgSize := p.SizeVT()
|
|
33
|
+
data := make([]byte, 4+msgSize)
|
|
34
|
+
binary.LittleEndian.PutUint32(data, uint32(msgSize))
|
|
35
|
+
_, err := p.MarshalToVT(data[4:])
|
|
36
|
+
if err != nil {
|
|
37
|
+
return err
|
|
38
|
+
}
|
|
39
|
+
_, err = r.rw.Write(data)
|
|
40
|
+
return err
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ReadPump executes the read pump in a goroutine.
|
|
44
|
+
func (r *PacketReaderWriter) ReadPump() error {
|
|
45
|
+
var currLen uint32
|
|
46
|
+
buf := make([]byte, 2048)
|
|
47
|
+
for {
|
|
48
|
+
n, err := r.rw.Read(buf)
|
|
49
|
+
if err != nil {
|
|
50
|
+
if err == io.EOF {
|
|
51
|
+
err = nil
|
|
52
|
+
}
|
|
53
|
+
return err
|
|
54
|
+
}
|
|
55
|
+
_, err = r.buf.Write(buf[:n])
|
|
56
|
+
if err != nil {
|
|
57
|
+
return err
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// check if we have enough for a length prefix
|
|
61
|
+
bufLen := r.buf.Len()
|
|
62
|
+
if currLen == 0 {
|
|
63
|
+
if bufLen < 4 {
|
|
64
|
+
continue
|
|
65
|
+
}
|
|
66
|
+
currLen = r.readLengthPrefix(r.buf.Bytes())
|
|
67
|
+
if currLen == 0 {
|
|
68
|
+
return errors.New("unexpected zero len prefix")
|
|
69
|
+
}
|
|
70
|
+
if currLen > uint32(maxMessageSize) {
|
|
71
|
+
return errors.Errorf("message size %v greater than maximum %v", currLen, maxMessageSize)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if currLen != 0 && bufLen >= int(currLen)+4 {
|
|
75
|
+
pkt := r.buf.Next(int(currLen + 4))[4:]
|
|
76
|
+
currLen = 0
|
|
77
|
+
npkt := &Packet{}
|
|
78
|
+
if err := npkt.UnmarshalVT(pkt); err != nil {
|
|
79
|
+
return err
|
|
80
|
+
}
|
|
81
|
+
if err := r.cb(npkt); err != nil {
|
|
82
|
+
return err
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Close closes the packet rw.
|
|
89
|
+
func (r *PacketReaderWriter) Close() error {
|
|
90
|
+
return r.rw.Close()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// readLengthPrefix reads the length prefix.
|
|
94
|
+
func (r *PacketReaderWriter) readLengthPrefix(b []byte) uint32 {
|
|
95
|
+
if len(b) < 4 {
|
|
96
|
+
return 0
|
|
97
|
+
}
|
|
98
|
+
return binary.LittleEndian.Uint32(b)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// _ is a type assertion
|
|
102
|
+
var _ Writer = (*PacketReaderWriter)(nil)
|
package/srpc/packet.go
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
package srpc
|
|
2
|
+
|
|
3
|
+
// PacketHandler handles a packet.
|
|
4
|
+
type PacketHandler = func(pkt *Packet) error
|
|
5
|
+
|
|
6
|
+
// Validate performs cursory validation of the packet.
|
|
7
|
+
func (p *Packet) Validate() error {
|
|
8
|
+
switch b := p.GetBody().(type) {
|
|
9
|
+
case *Packet_CallStart:
|
|
10
|
+
return b.CallStart.Validate()
|
|
11
|
+
case *Packet_CallData:
|
|
12
|
+
return b.CallData.Validate()
|
|
13
|
+
case *Packet_CallStartResp:
|
|
14
|
+
return b.CallStartResp.Validate()
|
|
15
|
+
default:
|
|
16
|
+
return ErrUnrecognizedPacket
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// NewCallStartPacket constructs a new CallStart packet.
|
|
21
|
+
func NewCallStartPacket(service, method string, data []byte) *Packet {
|
|
22
|
+
return &Packet{Body: &Packet_CallStart{
|
|
23
|
+
CallStart: &CallStart{
|
|
24
|
+
RpcService: service,
|
|
25
|
+
RpcMethod: method,
|
|
26
|
+
Data: data,
|
|
27
|
+
},
|
|
28
|
+
}}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Validate performs cursory validation of the packet.
|
|
32
|
+
func (p *CallStart) Validate() error {
|
|
33
|
+
method := p.GetRpcMethod()
|
|
34
|
+
if len(method) == 0 {
|
|
35
|
+
return ErrEmptyMethodID
|
|
36
|
+
}
|
|
37
|
+
service := p.GetRpcService()
|
|
38
|
+
if len(service) == 0 {
|
|
39
|
+
return ErrEmptyServiceID
|
|
40
|
+
}
|
|
41
|
+
return nil
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// NewCallDataPacket constructs a new CallData packet.
|
|
45
|
+
func NewCallDataPacket(data []byte, complete bool, err error) *Packet {
|
|
46
|
+
var errStr string
|
|
47
|
+
if err != nil {
|
|
48
|
+
errStr = err.Error()
|
|
49
|
+
}
|
|
50
|
+
return &Packet{Body: &Packet_CallData{
|
|
51
|
+
CallData: &CallData{
|
|
52
|
+
Data: data,
|
|
53
|
+
Complete: err != nil || complete,
|
|
54
|
+
Error: errStr,
|
|
55
|
+
},
|
|
56
|
+
}}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Validate performs cursory validation of the packet.
|
|
60
|
+
func (p *CallData) Validate() error {
|
|
61
|
+
if len(p.GetData()) == 0 && !p.GetComplete() && len(p.GetError()) == 0 {
|
|
62
|
+
return ErrEmptyPacket
|
|
63
|
+
}
|
|
64
|
+
return nil
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Validate performs cursory validation of the packet.
|
|
68
|
+
func (p *CallStartResp) Validate() error {
|
|
69
|
+
// nothing to check, empty packet is valid.
|
|
70
|
+
return nil
|
|
71
|
+
}
|