starpc 0.0.1 → 0.1.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.
Files changed (63) hide show
  1. package/LICENSE +19 -0
  2. package/README.md +122 -27
  3. package/dist/echo/echo.d.ts +59 -0
  4. package/dist/echo/echo.js +85 -0
  5. package/dist/srpc/broadcast-channel.d.ts +16 -0
  6. package/dist/srpc/broadcast-channel.js +60 -0
  7. package/dist/srpc/client-rpc.d.ts +31 -0
  8. package/dist/srpc/client-rpc.js +176 -0
  9. package/dist/srpc/client.d.ts +12 -0
  10. package/dist/srpc/client.js +129 -0
  11. package/dist/srpc/conn.d.ts +14 -0
  12. package/dist/srpc/conn.js +38 -0
  13. package/dist/srpc/index.d.ts +3 -0
  14. package/dist/srpc/index.js +2 -0
  15. package/dist/srpc/packet.d.ts +9 -0
  16. package/dist/srpc/packet.js +89 -0
  17. package/dist/srpc/rpcproto.d.ts +194 -0
  18. package/dist/srpc/rpcproto.js +322 -0
  19. package/dist/srpc/stream.d.ts +5 -0
  20. package/dist/srpc/stream.js +1 -0
  21. package/dist/srpc/ts-proto-rpc.d.ts +7 -0
  22. package/dist/srpc/ts-proto-rpc.js +1 -0
  23. package/dist/srpc/websocket.d.ts +7 -0
  24. package/dist/srpc/websocket.js +18 -0
  25. package/echo/echo.go +1 -0
  26. package/echo/echo.pb.go +165 -0
  27. package/echo/echo.proto +19 -0
  28. package/echo/echo.ts +191 -0
  29. package/echo/echo_srpc.pb.go +333 -0
  30. package/echo/echo_vtproto.pb.go +271 -0
  31. package/echo/server.go +73 -0
  32. package/package.json +71 -9
  33. package/srpc/broadcast-channel.ts +72 -0
  34. package/srpc/client-rpc.go +163 -0
  35. package/srpc/client-rpc.ts +197 -0
  36. package/srpc/client.go +96 -0
  37. package/srpc/client.ts +182 -0
  38. package/srpc/conn.go +7 -0
  39. package/srpc/conn.ts +49 -0
  40. package/srpc/errors.go +20 -0
  41. package/srpc/handler.go +13 -0
  42. package/srpc/index.ts +3 -0
  43. package/srpc/message.go +7 -0
  44. package/srpc/mux.go +76 -0
  45. package/srpc/packet-rw.go +102 -0
  46. package/srpc/packet.go +71 -0
  47. package/srpc/packet.ts +105 -0
  48. package/srpc/rpc-stream.go +76 -0
  49. package/srpc/rpcproto.pb.go +455 -0
  50. package/srpc/rpcproto.proto +46 -0
  51. package/srpc/rpcproto.ts +467 -0
  52. package/srpc/rpcproto_vtproto.pb.go +1094 -0
  53. package/srpc/server-http.go +66 -0
  54. package/srpc/server-pipe.go +26 -0
  55. package/srpc/server-rpc.go +160 -0
  56. package/srpc/server.go +29 -0
  57. package/srpc/stream-pipe.go +86 -0
  58. package/srpc/stream.go +24 -0
  59. package/srpc/stream.ts +11 -0
  60. package/srpc/ts-proto-rpc.ts +29 -0
  61. package/srpc/websocket.go +68 -0
  62. package/srpc/websocket.ts +22 -0
  63. package/srpc/writer.go +9 -0
@@ -0,0 +1,197 @@
1
+ import type { CallData, CallStart, CallStartResp } from './rpcproto'
2
+ import { Packet } from './rpcproto'
3
+ import type { Sink } from 'it-stream-types'
4
+ import { pushable } from 'it-pushable'
5
+
6
+ // DataCb is a callback to handle incoming RPC messages.
7
+ // Returns true if more data is expected, false otherwise.
8
+ // If returns undefined, assumes more data is expected.
9
+ export type DataCb = (data: Uint8Array) => Promise<boolean | void>
10
+
11
+ // ClientRPC is an ongoing RPC from the client side.
12
+ export class ClientRPC {
13
+ // sink is the data sink for incoming messages.
14
+ public sink: Sink<Packet>
15
+ // source is the packet source for outgoing Packets.
16
+ public source: AsyncIterable<Packet>
17
+ // _source is used to write to the source.
18
+ private readonly _source: {
19
+ push: (val: Packet) => void
20
+ end: (err?: Error) => void
21
+ }
22
+ // service is the rpc service
23
+ private service: string
24
+ // method is the rpc method
25
+ private method: string
26
+ // dataCb is called with any incoming data.
27
+ private dataCb?: DataCb
28
+ // started is resolved when the request starts.
29
+ private started: Promise<void>
30
+ // onStarted is called by the message handler when the request starts.
31
+ private onStarted?: (err?: Error) => void
32
+ // complete is resolved when the request completes.
33
+ // rejected with an error if the call encountered any error.
34
+ private complete: Promise<void>
35
+ // onComplete is called by the message handler when the call completes.
36
+ private onComplete?: (err?: Error) => void
37
+ // closed indicates close has been called
38
+ private closed: boolean
39
+
40
+ constructor(service: string, method: string, dataCb: DataCb | null) {
41
+ this.closed = false
42
+ this.sink = this._createSink()
43
+ const sourcev = this._createSource()
44
+ this.source = sourcev
45
+ this._source = sourcev
46
+ this.service = service
47
+ this.method = method
48
+ if (dataCb) {
49
+ this.dataCb = dataCb
50
+ }
51
+ this.started = new Promise<void>((resolveStarted, rejectStarted) => {
52
+ this.onStarted = (err?: Error) => {
53
+ if (err) {
54
+ rejectStarted(err)
55
+ } else {
56
+ resolveStarted()
57
+ }
58
+ }
59
+ })
60
+ this.complete = new Promise<void>((resolveComplete, rejectComplete) => {
61
+ this.onComplete = (err?: Error) => {
62
+ this.closed = true
63
+ if (err) {
64
+ rejectComplete(err)
65
+ } else {
66
+ resolveComplete()
67
+ }
68
+ }
69
+ })
70
+ }
71
+
72
+ // waitStarted returns the started promise.
73
+ public waitStarted(): Promise<void> {
74
+ return this.started
75
+ }
76
+
77
+ // waitComplete returns the complete promise.
78
+ public waitComplete(): Promise<void> {
79
+ return this.complete
80
+ }
81
+
82
+ // writeCallStart writes the call start packet.
83
+ public async writeCallStart(data?: Uint8Array) {
84
+ const callStart: CallStart = {
85
+ rpcService: this.service,
86
+ rpcMethod: this.method,
87
+ data: data || new Uint8Array(0),
88
+ }
89
+ await this.writePacket({
90
+ body: {
91
+ $case: 'callStart',
92
+ callStart,
93
+ },
94
+ })
95
+ }
96
+
97
+ // writeCallData writes the call data packet.
98
+ public async writeCallData(data: Uint8Array, complete?: boolean, error?: string) {
99
+ const callData: CallData = {
100
+ data,
101
+ complete: complete || false,
102
+ error: error || "",
103
+ }
104
+ await this.writePacket({
105
+ body: {
106
+ $case: 'callData',
107
+ callData,
108
+ },
109
+ })
110
+ }
111
+
112
+ // writePacket writes a packet to the stream.
113
+ private async writePacket(packet: Packet) {
114
+ this._source.push(packet)
115
+ }
116
+
117
+ // handleMessage handles an incoming encoded Packet.
118
+ //
119
+ // note: may throw an error if the message was unexpected or invalid.
120
+ public async handleMessage(message: Uint8Array) {
121
+ return this.handlePacket(Packet.decode(message))
122
+ }
123
+
124
+ // handlePacket handles an incoming packet.
125
+ public async handlePacket(packet: Partial<Packet>) {
126
+ switch (packet?.body?.$case) {
127
+ case 'callStart':
128
+ return this.handleCallStart(packet.body.callStart)
129
+ case 'callStartResp':
130
+ return this.handleCallStartResp(packet.body.callStartResp)
131
+ case 'callData':
132
+ return this.handleCallData(packet.body.callData)
133
+ }
134
+ }
135
+
136
+ // handleCallStart handles a CallStart packet.
137
+ public async handleCallStart(packet: Partial<CallStart>) {
138
+ // we do not implement server -> client RPCs.
139
+ throw new Error(`unexpected server to client rpc: ${packet.rpcService}/${packet.rpcMethod}`)
140
+ }
141
+
142
+ // handleCallStartResp handles a CallStartResp packet.
143
+ public async handleCallStartResp(packet: Partial<CallStartResp>) {
144
+ if (packet.error && packet.error.length) {
145
+ const err = new Error(packet.error)
146
+ this.onStarted!(err)
147
+ this.onComplete!(err)
148
+ }
149
+ }
150
+
151
+ // handleCallData handles a CallData packet.
152
+ public async handleCallData(packet: Partial<CallData>) {
153
+ const data = packet.data
154
+ if (this.dataCb && data?.length) {
155
+ await this.dataCb(data)
156
+ }
157
+ if (packet.error && packet.error.length) {
158
+ this.onComplete!(new Error(packet.error))
159
+ } else if (packet.complete) {
160
+ this.onComplete!()
161
+ }
162
+ }
163
+
164
+ // close closes the active call if not already completed.
165
+ public async close(err?: Error) {
166
+ if (!this.closed) {
167
+ await this.writeCallData(new Uint8Array(0), true, err ? err.message : "")
168
+ }
169
+ if (!err) {
170
+ err = new Error('call closed')
171
+ }
172
+ this.onComplete!(err)
173
+ }
174
+
175
+ // _createSink initializes the sink field.
176
+ private _createSink(): Sink<Packet> {
177
+ return async (source) => {
178
+ try {
179
+ for await (const msg of source) {
180
+ await this.handlePacket(msg)
181
+ }
182
+ } catch (err) {
183
+ this.close(err as Error)
184
+ }
185
+ }
186
+ }
187
+
188
+ // _createSource initializes the source field.
189
+ private _createSource() {
190
+ return pushable<Packet>({
191
+ objectMode: true,
192
+ onEnd: (err?: Error): void => {
193
+ this.onComplete!(err)
194
+ },
195
+ })
196
+ }
197
+ }
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
@@ -0,0 +1,7 @@
1
+ package srpc
2
+
3
+ // Conn represents a connection to a remote.
4
+ type Conn interface {
5
+ // GetOpenStreamFunc returns the OpenStream func.
6
+ GetOpenStreamFunc() OpenStreamFunc
7
+ }
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
+ )
@@ -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
@@ -0,0 +1,3 @@
1
+ export { Client } from './client'
2
+ export type { OpenStreamFunc } from './stream'
3
+ export { WebSocketConn } from './websocket'
@@ -0,0 +1,7 @@
1
+ package srpc
2
+
3
+ // Message is the vtprotobuf message interface.
4
+ type Message interface {
5
+ MarshalVT() ([]byte, error)
6
+ UnmarshalVT([]byte) error
7
+ }
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))