Skip to content

Commit 19366d3

Browse files
committed
Message ID can be int, string, or null as per OpenRPC spec
Fixes: filecoin-project#28
1 parent 45ea43a commit 19366d3

File tree

5 files changed

+144
-40
lines changed

5 files changed

+144
-40
lines changed

Diff for: client.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func (e *ErrClient) Unwrap(err error) error {
6767
type clientResponse struct {
6868
Jsonrpc string `json:"jsonrpc"`
6969
Result json.RawMessage `json:"result"`
70-
ID int64 `json:"id"`
70+
ID requestID `json:"id"`
7171
Error *respError `json:"error,omitempty"`
7272
}
7373

@@ -170,7 +170,7 @@ func httpClient(ctx context.Context, addr string, namespace string, outs []inter
170170
return clientResponse{}, xerrors.Errorf("unmarshaling response: %w", err)
171171
}
172172

173-
if resp.ID != *cr.req.ID {
173+
if cr.req.ID.actual != resp.ID.actual {
174174
return clientResponse{}, xerrors.New("request and response id didn't match")
175175
}
176176

@@ -240,7 +240,7 @@ func websocketClient(ctx context.Context, addr string, namespace string, outs []
240240
req: request{
241241
Jsonrpc: "2.0",
242242
Method: wsCancel,
243-
Params: []param{{v: reflect.ValueOf(*cr.req.ID)}},
243+
Params: []param{{v: reflect.ValueOf(cr.req.ID.actual)}},
244244
},
245245
}
246246
select {
@@ -498,7 +498,7 @@ func (fn *rpcFunc) handleRpcCall(args []reflect.Value) (results []reflect.Value)
498498

499499
req := request{
500500
Jsonrpc: "2.0",
501-
ID: &id,
501+
ID: requestID{id},
502502
Method: fn.client.namespace + "." + fn.name,
503503
Params: params,
504504
}
@@ -528,7 +528,7 @@ func (fn *rpcFunc) handleRpcCall(args []reflect.Value) (results []reflect.Value)
528528
return fn.processError(fmt.Errorf("sendRequest failed: %w", err))
529529
}
530530

531-
if resp.ID != *req.ID {
531+
if req.ID.actual != resp.ID.actual {
532532
return fn.processError(xerrors.New("request and response id didn't match"))
533533
}
534534

Diff for: handler.go

+33-6
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,39 @@ type rpcHandler struct {
3737

3838
type request struct {
3939
Jsonrpc string `json:"jsonrpc"`
40-
ID *int64 `json:"id,omitempty"`
40+
ID requestID `json:"id,omitempty"`
4141
Method string `json:"method"`
4242
Params []param `json:"params"`
4343
Meta map[string]string `json:"meta,omitempty"`
4444
}
4545

46+
type requestID struct {
47+
actual interface{} // nil, int64, or string
48+
}
49+
50+
func (r *requestID) UnmarshalJSON(data []byte) error {
51+
switch data[0] {
52+
case 'n': // null
53+
case '"': // string
54+
var s string
55+
if err := json.Unmarshal(data, &s); err != nil {
56+
return err
57+
}
58+
r.actual = s
59+
default: // number
60+
var n int64
61+
if err := json.Unmarshal(data, &n); err != nil {
62+
return err
63+
}
64+
r.actual = n
65+
}
66+
return nil
67+
}
68+
69+
func (r requestID) MarshalJSON() ([]byte, error) {
70+
return json.Marshal(r.actual)
71+
}
72+
4673
// Limit request size. Ideally this limit should be specific for each field
4774
// in the JSON request but as a simple defensive measure we just limit the
4875
// entire HTTP body.
@@ -64,7 +91,7 @@ func (e *respError) Error() string {
6491
type response struct {
6592
Jsonrpc string `json:"jsonrpc"`
6693
Result interface{} `json:"result,omitempty"`
67-
ID int64 `json:"id"`
94+
ID requestID `json:"id"`
6895
Error *respError `json:"error,omitempty"`
6996
}
7097

@@ -109,7 +136,7 @@ func (s *RPCServer) register(namespace string, r interface{}) {
109136
// Handle
110137

111138
type rpcErrFunc func(w func(func(io.Writer)), req *request, code int, err error)
112-
type chanOut func(reflect.Value, int64) error
139+
type chanOut func(reflect.Value, requestID) error
113140

114141
func (s *RPCServer) handleReader(ctx context.Context, r io.Reader, w io.Writer, rpcError rpcErrFunc) {
115142
wf := func(cb func(io.Writer)) {
@@ -262,15 +289,15 @@ func (s *RPCServer) handle(ctx context.Context, req request, w func(func(io.Writ
262289
stats.Record(ctx, metrics.RPCRequestError.M(1))
263290
return
264291
}
265-
if req.ID == nil {
292+
if req.ID.actual == nil {
266293
return // notification
267294
}
268295

269296
///////////////////
270297

271298
resp := response{
272299
Jsonrpc: "2.0",
273-
ID: *req.ID,
300+
ID: req.ID,
274301
}
275302

276303
if handler.errOut != -1 {
@@ -302,7 +329,7 @@ func (s *RPCServer) handle(ctx context.Context, req request, w func(func(io.Writ
302329
// sending channel messages before this rpc call returns
303330

304331
//noinspection GoNilness // already checked above
305-
err = chOut(callResult[handler.valOut], *req.ID)
332+
err = chOut(callResult[handler.valOut], req.ID)
306333
if err == nil {
307334
return // channel goroutine handles responding
308335
}

Diff for: rpc_test.go

+77
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package jsonrpc
22

33
import (
4+
"bytes"
45
"context"
56
"encoding/json"
67
"errors"
78
"fmt"
89
"io"
910
"io/ioutil"
1011
"net"
12+
"net/http"
1113
"net/http/httptest"
1214
"reflect"
1315
"strconv"
@@ -360,6 +362,81 @@ func TestRPCHttpClient(t *testing.T) {
360362
closer()
361363
}
362364

365+
func TestRPCCustomHttpClient(t *testing.T) {
366+
// setup server
367+
serverHandler := &SimpleServerHandler{}
368+
rpcServer := NewServer()
369+
rpcServer.Register("SimpleServerHandler", serverHandler)
370+
testServ := httptest.NewServer(rpcServer)
371+
defer testServ.Close()
372+
373+
// setup custom client
374+
addr := "http://" + testServ.Listener.Addr().String()
375+
doReq := func(reqStr string) string {
376+
hreq, err := http.NewRequest("POST", addr, bytes.NewReader([]byte(reqStr)))
377+
require.NoError(t, err)
378+
379+
hreq.Header = http.Header{}
380+
hreq.Header.Set("Content-Type", "application/json")
381+
382+
httpResp, err := testServ.Client().Do(hreq)
383+
defer httpResp.Body.Close()
384+
385+
respBytes, err := ioutil.ReadAll(httpResp.Body)
386+
require.NoError(t, err)
387+
388+
return string(respBytes)
389+
}
390+
391+
// Add(2)
392+
reqStr := `{"jsonrpc":"2.0","method":"SimpleServerHandler.Add","params":[2],"id":100}"`
393+
respBytes := doReq(reqStr)
394+
require.Equal(t, `{"jsonrpc":"2.0","id":100}`+"\n", string(respBytes))
395+
require.Equal(t, 2, serverHandler.n)
396+
397+
// Add(-3546) error
398+
reqStr = `{"jsonrpc":"2.0","method":"SimpleServerHandler.Add","params":[-3546],"id":1010102}"`
399+
respBytes = doReq(reqStr)
400+
require.Equal(t, `{"jsonrpc":"2.0","id":1010102,"error":{"code":1,"message":"test"}}`+"\n", string(respBytes))
401+
require.Equal(t, 2, serverHandler.n)
402+
403+
// AddGet(3)
404+
reqStr = `{"jsonrpc":"2.0","method":"SimpleServerHandler.AddGet","params":[3],"id":0}"`
405+
respBytes = doReq(reqStr)
406+
require.Equal(t, `{"jsonrpc":"2.0","result":5,"id":0}`+"\n", string(respBytes))
407+
require.Equal(t, 5, serverHandler.n)
408+
409+
// StringMatch("0", 0, 0)
410+
reqStr = `{"jsonrpc":"2.0","method":"SimpleServerHandler.StringMatch","params":[{"S":"0","I":0},0],"id":1}"`
411+
respBytes = doReq(reqStr)
412+
require.Equal(t, `{"jsonrpc":"2.0","result":{"S":"0","I":0,"Ok":true},"id":1}`+"\n", string(respBytes))
413+
require.Equal(t, 5, serverHandler.n)
414+
415+
// StringMatch("5", 0, 5) error
416+
reqStr = `{"jsonrpc":"2.0","method":"SimpleServerHandler.StringMatch","params":[{"S":"5","I":0},5],"id":2}"`
417+
respBytes = doReq(reqStr)
418+
require.Equal(t, `{"jsonrpc":"2.0","id":2,"error":{"code":1,"message":":("}}`+"\n", string(respBytes))
419+
require.Equal(t, 5, serverHandler.n)
420+
421+
// StringMatch("8", 8, 8) error
422+
reqStr = `{"jsonrpc":"2.0","method":"SimpleServerHandler.StringMatch","params":[{"S":"8","I":8},8],"id":3}"`
423+
respBytes = doReq(reqStr)
424+
require.Equal(t, `{"jsonrpc":"2.0","result":{"S":"8","I":8,"Ok":true},"id":3}`+"\n", string(respBytes))
425+
require.Equal(t, 5, serverHandler.n)
426+
427+
// Add(int) string ID
428+
reqStr = `{"jsonrpc":"2.0","method":"SimpleServerHandler.Add","params":[2],"id":"100"}"`
429+
respBytes = doReq(reqStr)
430+
require.Equal(t, `{"jsonrpc":"2.0","id":"100"}`+"\n", string(respBytes))
431+
require.Equal(t, 7, serverHandler.n)
432+
433+
// Add(int) random string ID
434+
reqStr = `{"jsonrpc":"2.0","method":"SimpleServerHandler.Add","params":[2],"id":"OpenRPC says this can be whatever you want"}"`
435+
respBytes = doReq(reqStr)
436+
require.Equal(t, `{"jsonrpc":"2.0","id":"OpenRPC says this can be whatever you want"}`+"\n", string(respBytes))
437+
require.Equal(t, 9, serverHandler.n)
438+
}
439+
363440
type CtxHandler struct {
364441
lk sync.Mutex
365442

Diff for: server.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,13 @@ func rpcError(wf func(func(io.Writer)), req *request, code int, err error) {
100100

101101
log.Warnf("rpc error: %s", err)
102102

103-
if req.ID == nil { // notification
103+
if req.ID.actual == nil { // notification
104104
return
105105
}
106106

107107
resp := response{
108108
Jsonrpc: "2.0",
109-
ID: *req.ID,
109+
ID: req.ID,
110110
Error: &respError{
111111
Code: code,
112112
Message: err.Error(),

0 commit comments

Comments
 (0)