Skip to content

Commit 37e3f56

Browse files
committed
rpc: support subscriptions under custom namespaces
1 parent ba3bcd1 commit 37e3f56

File tree

6 files changed

+217
-55
lines changed

6 files changed

+217
-55
lines changed

rpc/client.go

+19-16
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"net/url"
2828
"reflect"
2929
"strconv"
30+
"strings"
3031
"sync"
3132
"sync/atomic"
3233
"time"
@@ -373,14 +374,14 @@ func (c *Client) EthSubscribe(ctx context.Context, channel interface{}, args ...
373374
return nil, ErrNotificationsUnsupported
374375
}
375376

376-
msg, err := c.newMessage(subscribeMethod, args...)
377+
msg, err := c.newMessage("eth"+subscribeMethodSuffix, args...)
377378
if err != nil {
378379
return nil, err
379380
}
380381
op := &requestOp{
381382
ids: []json.RawMessage{msg.ID},
382383
resp: make(chan *jsonrpcMessage),
383-
sub: newClientSubscription(c, chanVal),
384+
sub: newClientSubscription(c, "eth", chanVal),
384385
}
385386

386387
// Send the subscription request.
@@ -575,7 +576,7 @@ func (c *Client) closeRequestOps(err error) {
575576
}
576577

577578
func (c *Client) handleNotification(msg *jsonrpcMessage) {
578-
if msg.Method != notificationMethod {
579+
if !strings.HasSuffix(msg.Method, notificationMethodSuffix) {
579580
log.Debug(fmt.Sprint("dropping non-subscription message: ", msg))
580581
return
581582
}
@@ -653,26 +654,28 @@ func (c *Client) read(conn net.Conn) error {
653654

654655
// A ClientSubscription represents a subscription established through EthSubscribe.
655656
type ClientSubscription struct {
656-
client *Client
657-
etype reflect.Type
658-
channel reflect.Value
659-
subid string
660-
in chan json.RawMessage
657+
client *Client
658+
etype reflect.Type
659+
channel reflect.Value
660+
namespace string
661+
subid string
662+
in chan json.RawMessage
661663

662664
quitOnce sync.Once // ensures quit is closed once
663665
quit chan struct{} // quit is closed when the subscription exits
664666
errOnce sync.Once // ensures err is closed once
665667
err chan error
666668
}
667669

668-
func newClientSubscription(c *Client, channel reflect.Value) *ClientSubscription {
670+
func newClientSubscription(c *Client, namespace string, channel reflect.Value) *ClientSubscription {
669671
sub := &ClientSubscription{
670-
client: c,
671-
etype: channel.Type().Elem(),
672-
channel: channel,
673-
quit: make(chan struct{}),
674-
err: make(chan error, 1),
675-
in: make(chan json.RawMessage),
672+
client: c,
673+
namespace: namespace,
674+
etype: channel.Type().Elem(),
675+
channel: channel,
676+
quit: make(chan struct{}),
677+
err: make(chan error, 1),
678+
in: make(chan json.RawMessage),
676679
}
677680
return sub
678681
}
@@ -774,5 +777,5 @@ func (sub *ClientSubscription) unmarshal(result json.RawMessage) (interface{}, e
774777

775778
func (sub *ClientSubscription) requestUnsubscribe() error {
776779
var result interface{}
777-
return sub.client.Call(&result, unsubscribeMethod, sub.subid)
780+
return sub.client.Call(&result, sub.namespace+unsubscribeMethodSuffix, sub.subid)
778781
}

rpc/json.go

+17-19
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ import (
3030
)
3131

3232
const (
33-
jsonrpcVersion = "2.0"
34-
serviceMethodSeparator = "_"
35-
subscribeMethod = "eth_subscribe"
36-
unsubscribeMethod = "eth_unsubscribe"
37-
notificationMethod = "eth_subscription"
33+
jsonrpcVersion = "2.0"
34+
serviceMethodSeparator = "_"
35+
subscribeMethodSuffix = "_subscribe"
36+
unsubscribeMethodSuffix = "_unsubscribe"
37+
notificationMethodSuffix = "_subscription"
3838
)
3939

4040
type jsonRequest struct {
@@ -164,7 +164,7 @@ func parseRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, Error) {
164164
}
165165

166166
// subscribe are special, they will always use `subscribeMethod` as first param in the payload
167-
if in.Method == subscribeMethod {
167+
if strings.HasSuffix(in.Method, subscribeMethodSuffix) {
168168
reqs := []rpcRequest{{id: &in.Id, isPubSub: true}}
169169
if len(in.Payload) > 0 {
170170
// first param must be subscription name
@@ -174,17 +174,16 @@ func parseRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, Error) {
174174
return nil, false, &invalidRequestError{"Unable to parse subscription request"}
175175
}
176176

177-
// all subscriptions are made on the eth service
178-
reqs[0].service, reqs[0].method = "eth", subscribeMethod[0]
177+
reqs[0].service, reqs[0].method = strings.TrimSuffix(in.Method, subscribeMethodSuffix), subscribeMethod[0]
179178
reqs[0].params = in.Payload
180179
return reqs, false, nil
181180
}
182181
return nil, false, &invalidRequestError{"Unable to parse subscription request"}
183182
}
184183

185-
if in.Method == unsubscribeMethod {
184+
if strings.HasSuffix(in.Method, unsubscribeMethodSuffix) {
186185
return []rpcRequest{{id: &in.Id, isPubSub: true,
187-
method: unsubscribeMethod, params: in.Payload}}, false, nil
186+
method: in.Method, params: in.Payload}}, false, nil
188187
}
189188

190189
elems := strings.Split(in.Method, serviceMethodSeparator)
@@ -216,8 +215,8 @@ func parseBatchRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, Error)
216215

217216
id := &in[i].Id
218217

219-
// subscribe are special, they will always use `subscribeMethod` as first param in the payload
220-
if r.Method == subscribeMethod {
218+
// subscribe are special, they will always use `subscriptionMethod` as first param in the payload
219+
if strings.HasSuffix(r.Method, subscribeMethodSuffix) {
221220
requests[i] = rpcRequest{id: id, isPubSub: true}
222221
if len(r.Payload) > 0 {
223222
// first param must be subscription name
@@ -227,17 +226,16 @@ func parseBatchRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, Error)
227226
return nil, false, &invalidRequestError{"Unable to parse subscription request"}
228227
}
229228

230-
// all subscriptions are made on the eth service
231-
requests[i].service, requests[i].method = "eth", subscribeMethod[0]
229+
requests[i].service, requests[i].method = strings.TrimSuffix(r.Method, subscribeMethodSuffix), subscribeMethod[0]
232230
requests[i].params = r.Payload
233231
continue
234232
}
235233

236234
return nil, true, &invalidRequestError{"Unable to parse (un)subscribe request arguments"}
237235
}
238236

239-
if r.Method == unsubscribeMethod {
240-
requests[i] = rpcRequest{id: id, isPubSub: true, method: unsubscribeMethod, params: r.Payload}
237+
if strings.HasSuffix(r.Method, unsubscribeMethodSuffix) {
238+
requests[i] = rpcRequest{id: id, isPubSub: true, method: r.Method, params: r.Payload}
241239
continue
242240
}
243241

@@ -325,13 +323,13 @@ func (c *jsonCodec) CreateErrorResponseWithInfo(id interface{}, err Error, info
325323
}
326324

327325
// CreateNotification will create a JSON-RPC notification with the given subscription id and event as params.
328-
func (c *jsonCodec) CreateNotification(subid string, event interface{}) interface{} {
326+
func (c *jsonCodec) CreateNotification(subid, namespace string, event interface{}) interface{} {
329327
if isHexNum(reflect.TypeOf(event)) {
330-
return &jsonNotification{Version: jsonrpcVersion, Method: notificationMethod,
328+
return &jsonNotification{Version: jsonrpcVersion, Method: namespace + notificationMethodSuffix,
331329
Params: jsonSubscription{Subscription: subid, Result: fmt.Sprintf(`%#x`, event)}}
332330
}
333331

334-
return &jsonNotification{Version: jsonrpcVersion, Method: notificationMethod,
332+
return &jsonNotification{Version: jsonrpcVersion, Method: namespace + notificationMethodSuffix,
335333
Params: jsonSubscription{Subscription: subid, Result: event}}
336334
}
337335

rpc/server.go

+8-9
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"fmt"
2222
"reflect"
2323
"runtime"
24+
"strings"
2425
"sync"
2526
"sync/atomic"
2627

@@ -96,32 +97,30 @@ func (s *Server) RegisterName(name string, rcvr interface{}) error {
9697
return fmt.Errorf("%s is not exported", reflect.Indirect(rcvrVal).Type().Name())
9798
}
9899

100+
methods, subscriptions := suitableCallbacks(rcvrVal, svc.typ)
101+
99102
// already a previous service register under given sname, merge methods/subscriptions
100103
if regsvc, present := s.services[name]; present {
101-
methods, subscriptions := suitableCallbacks(rcvrVal, svc.typ)
102104
if len(methods) == 0 && len(subscriptions) == 0 {
103105
return fmt.Errorf("Service %T doesn't have any suitable methods/subscriptions to expose", rcvr)
104106
}
105-
106107
for _, m := range methods {
107108
regsvc.callbacks[formatName(m.method.Name)] = m
108109
}
109110
for _, s := range subscriptions {
110111
regsvc.subscriptions[formatName(s.method.Name)] = s
111112
}
112-
113113
return nil
114114
}
115115

116116
svc.name = name
117-
svc.callbacks, svc.subscriptions = suitableCallbacks(rcvrVal, svc.typ)
117+
svc.callbacks, svc.subscriptions = methods, subscriptions
118118

119119
if len(svc.callbacks) == 0 && len(svc.subscriptions) == 0 {
120120
return fmt.Errorf("Service %T doesn't have any suitable methods/subscriptions to expose", rcvr)
121121
}
122122

123123
s.services[svc.name] = svc
124-
125124
return nil
126125
}
127126

@@ -303,7 +302,7 @@ func (s *Server) handle(ctx context.Context, codec ServerCodec, req *serverReque
303302
// active the subscription after the sub id was successfully sent to the client
304303
activateSub := func() {
305304
notifier, _ := NotifierFromContext(ctx)
306-
notifier.activate(subid)
305+
notifier.activate(subid, req.svcname)
307306
}
308307

309308
return codec.CreateResponse(req.id, subid), activateSub
@@ -383,7 +382,7 @@ func (s *Server) execBatch(ctx context.Context, codec ServerCodec, requests []*s
383382
codec.Close()
384383
}
385384

386-
// when request holds one of more subscribe requests this allows these subscriptions to be actived
385+
// when request holds one of more subscribe requests this allows these subscriptions to be activated
387386
for _, c := range callbacks {
388387
c()
389388
}
@@ -410,7 +409,7 @@ func (s *Server) readRequest(codec ServerCodec) ([]*serverRequest, bool, Error)
410409
continue
411410
}
412411

413-
if r.isPubSub && r.method == unsubscribeMethod {
412+
if r.isPubSub && strings.HasSuffix(r.method, unsubscribeMethodSuffix) {
414413
requests[i] = &serverRequest{id: r.id, isUnsubscribe: true}
415414
argTypes := []reflect.Type{reflect.TypeOf("")} // expect subscription id as first arg
416415
if args, err := codec.ParseRequestArguments(argTypes, r.params); err == nil {
@@ -439,7 +438,7 @@ func (s *Server) readRequest(codec ServerCodec) ([]*serverRequest, bool, Error)
439438
}
440439
}
441440
} else {
442-
requests[i] = &serverRequest{id: r.id, err: &methodNotFoundError{subscribeMethod, r.method}}
441+
requests[i] = &serverRequest{id: r.id, err: &methodNotFoundError{r.method, r.method}}
443442
}
444443
continue
445444
}

rpc/subscription.go

+8-6
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ type ID string
3535
// a Subscription is created by a notifier and tight to that notifier. The client can use
3636
// this subscription to wait for an unsubscribe request for the client, see Err().
3737
type Subscription struct {
38-
ID ID
39-
err chan error // closed on unsubscribe
38+
ID ID
39+
namespace string
40+
err chan error // closed on unsubscribe
4041
}
4142

4243
// Err returns a channel that is closed when the client send an unsubscribe request.
@@ -78,7 +79,7 @@ func NotifierFromContext(ctx context.Context) (*Notifier, bool) {
7879
// are dropped until the subscription is marked as active. This is done
7980
// by the RPC server after the subscription ID is send to the client.
8081
func (n *Notifier) CreateSubscription() *Subscription {
81-
s := &Subscription{NewID(), make(chan error)}
82+
s := &Subscription{ID: NewID(), err: make(chan error)}
8283
n.subMu.Lock()
8384
n.inactive[s.ID] = s
8485
n.subMu.Unlock()
@@ -91,9 +92,9 @@ func (n *Notifier) Notify(id ID, data interface{}) error {
9192
n.subMu.RLock()
9293
defer n.subMu.RUnlock()
9394

94-
_, active := n.active[id]
95+
sub, active := n.active[id]
9596
if active {
96-
notification := n.codec.CreateNotification(string(id), data)
97+
notification := n.codec.CreateNotification(string(id), sub.namespace, data)
9798
if err := n.codec.Write(notification); err != nil {
9899
n.codec.Close()
99100
return err
@@ -124,10 +125,11 @@ func (n *Notifier) unsubscribe(id ID) error {
124125
// notifications are dropped. This method is called by the RPC server after
125126
// the subscription ID was sent to client. This prevents notifications being
126127
// send to the client before the subscription ID is send to the client.
127-
func (n *Notifier) activate(id ID) {
128+
func (n *Notifier) activate(id ID, namespace string) {
128129
n.subMu.Lock()
129130
defer n.subMu.Unlock()
130131
if sub, found := n.inactive[id]; found {
132+
sub.namespace = namespace
131133
n.active[id] = sub
132134
delete(n.inactive, id)
133135
}

0 commit comments

Comments
 (0)