From c5bc6bc56e72d7a1d69f9d6dc1c6a35980df514c Mon Sep 17 00:00:00 2001 From: a <a@tuxpa.in> Date: Mon, 14 Nov 2022 17:29:46 -0600 Subject: [PATCH] remove extra json handlers, add new json library to fill in gaps --- benchmark_test.go | 4 ++++ client.go | 15 ++++----------- go.mod | 1 + go.sum | 2 ++ http.go | 4 +++- http_client.go | 5 ++++- json.go | 41 +++++++++++++++++++---------------------- protocol.go | 22 +++++++--------------- subscription.go | 11 ++++++----- wire.go | 37 +++++++++++++------------------------ wsjson/wsjson.go | 25 +++++-------------------- 11 files changed, 68 insertions(+), 99 deletions(-) diff --git a/benchmark_test.go b/benchmark_test.go index 49fa35c..d7ed9d5 100644 --- a/benchmark_test.go +++ b/benchmark_test.go @@ -24,6 +24,7 @@ func BenchmarkClientHTTPEcho(b *testing.B) { b.StartTimer() for n := 0; n < b.N; n++ { eg := &errgroup.Group{} + eg.SetLimit(4) for i := 0; i < 1000; i++ { eg.Go(func() error { return client.Call(nil, "test_echoAny", []any{1, 2, 3, 4, 56, 6, wantBack, wantBack, wantBack}) @@ -44,6 +45,7 @@ func BenchmarkClientHTTPEchoEmpty(b *testing.B) { b.StartTimer() for n := 0; n < b.N; n++ { eg := &errgroup.Group{} + eg.SetLimit(4) for i := 0; i < 1000; i++ { eg.Go(func() error { return client.Call(nil, "test_echoAny", 0) @@ -71,6 +73,7 @@ func BenchmarkClientWebsocketEcho(b *testing.B) { b.StartTimer() for n := 0; n < b.N; n++ { eg := &errgroup.Group{} + eg.SetLimit(4) for i := 0; i < 1000; i++ { eg.Go(func() error { return client.Call(nil, "test_echoAny", payload) @@ -92,6 +95,7 @@ func BenchmarkClientWebsocketEchoEmpty(b *testing.B) { for n := 0; n < b.N; n++ { eg := &errgroup.Group{} + eg.SetLimit(4) for i := 0; i < 1000; i++ { eg.Go(func() error { return client.Call(nil, "test_echoAny", 0) diff --git a/client.go b/client.go index 8de58cc..efc7e1a 100644 --- a/client.go +++ b/client.go @@ -283,7 +283,7 @@ func (c *Client) call(ctx context.Context, result any, msg *jsonrpcMessage) erro case result == nil: return nil default: - return jzon.Unmarshal(resp.Result, &result) + return json.Unmarshal(resp.Result, &result) } } @@ -383,7 +383,7 @@ func (c *Client) BatchCallContext(ctx context.Context, b []BatchElem) error { elem.Error = ErrNoResult continue } - elem.Error = jzon.Unmarshal(resp.Result, elem.Result) + elem.Error = json.Unmarshal(resp.Result, elem.Result) } return err @@ -442,20 +442,13 @@ func (c *Client) Subscribe(ctx context.Context, namespace string, channel interf } func (c *Client) newMessage(method string, paramsIn ...any) (*jsonrpcMessage, error) { - msg := &jsonrpcMessage{ID: c.nextID(), Method: method} - if paramsIn != nil { // prevent sending "params":null - var err error - if msg.Params, err = jzon.Marshal(paramsIn); err != nil { - return nil, err - } - } - return msg, nil + return c.newMessageP(method, paramsIn) } func (c *Client) newMessageP(method string, paramIn any) (*jsonrpcMessage, error) { msg := &jsonrpcMessage{ID: c.nextID(), Method: method} if paramIn != nil { // prevent sending "params":null var err error - if msg.Params, err = jzon.Marshal(paramIn); err != nil { + if msg.Params, err = json.Marshal(paramIn); err != nil { return nil, err } } diff --git a/go.mod b/go.mod index ed4a32b..3f40282 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/deckarep/golang-set v1.8.0 github.com/ethereum/go-ethereum v1.10.25 github.com/gobuffalo/packr/v2 v2.8.3 + github.com/goccy/go-json v0.9.11 github.com/iancoleman/strcase v0.2.0 github.com/json-iterator/go v1.1.12 golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 diff --git a/go.sum b/go.sum index 572f2a7..d43c659 100644 --- a/go.sum +++ b/go.sum @@ -119,6 +119,8 @@ github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= diff --git a/http.go b/http.go index 0e4d784..212c893 100644 --- a/http.go +++ b/http.go @@ -29,6 +29,8 @@ import ( "time" "gfx.cafe/util/go/bufpool" + + json "github.com/goccy/go-json" ) const ( @@ -108,7 +110,7 @@ func newHTTPServerConn(r *http.Request, w http.ResponseWriter, pi PeerInfo) Serv param = pb } buf := bufpool.GetStd() - jzon.NewEncoder(buf).Encode(jsonrpcMessage{ + json.NewEncoder(buf).Encode(jsonrpcMessage{ ID: NewStringIDPtr(id), Method: method_up, Params: param, diff --git a/http_client.go b/http_client.go index 2693f34..605155d 100644 --- a/http_client.go +++ b/http_client.go @@ -3,14 +3,17 @@ package jrpc import ( "bytes" "context" - "encoding/json" "io" "net/http" "net/url" "sync" + + "github.com/goccy/go-json" ) func (hc *httpConn) doRequest(ctx context.Context, msg any) (io.ReadCloser, error) { + // TODO: + // the jsoniter encoder performs a lot better here, not sure why. (nearly 10%? maybe more) body, err := jzon.Marshal(msg) if err != nil { return nil, err diff --git a/json.go b/json.go index e982dfd..a07782d 100644 --- a/json.go +++ b/json.go @@ -3,7 +3,6 @@ package jrpc import ( "bytes" "context" - "encoding/json" "errors" "fmt" "io" @@ -13,8 +12,10 @@ import ( "sync" "time" + stdjson "encoding/json" + "gfx.cafe/open/jrpc/wsjson" - jsoniter "github.com/json-iterator/go" + "github.com/goccy/go-json" ) var jzon = wsjson.JZON @@ -35,8 +36,6 @@ type jsonrpcMessage struct { Result json.RawMessage `json:"result,omitempty"` Error *jsonError `json:"error,omitempty"` - - sortKeys bool } func MakeCall(id int, method string, params []any) *JsonRpcMessage { @@ -76,7 +75,7 @@ func (msg *jsonrpcMessage) namespace() string { } func (msg *jsonrpcMessage) String() string { - b, _ := jzon.Marshal(msg) + b, _ := json.Marshal(msg) return string(b) } @@ -90,11 +89,7 @@ func (msg *jsonrpcMessage) errorResponse(err error) *jsonrpcMessage { func (msg *jsonrpcMessage) response(result any) *jsonrpcMessage { // do a funny marshaling - jz := jzon - if msg.sortKeys { - jz = wsjson.JSON - } - enc, err := jz.Marshal(result) + enc, err := jzon.Marshal(result) if err != nil { return msg.errorResponse(err) } @@ -200,11 +195,7 @@ func NewFuncCodec( // NewCodec creates a codec on the given connection. If conn implements ConnRemoteAddr, log // messages will use it to include the remote address of the connection. func NewCodec(conn Conn) ServerCodec { - // for some reason other json decoders are incompatible with our test suite - // pretty sure its how we handle EOFs and stuff - dec := json.NewDecoder(conn) - dec.UseNumber() - return NewFuncCodec(conn, func(v any) error { + encr := func(v any) error { enc := jzon.BorrowStream(conn) defer jzon.ReturnStream(enc) enc.WriteVal(v) @@ -214,8 +205,13 @@ func NewCodec(conn Conn) ServerCodec { return enc.Error } return nil - // return jzon.NewEncoder(conn).Encode(v) - }, dec.Decode, func() error { + } + // TODO: + // for some reason other json decoders are incompatible with our test suite + // pretty sure its how we handle EOFs and stuff + dec := stdjson.NewDecoder(conn) + dec.UseNumber() + return NewFuncCodec(conn, encr, dec.Decode, func() error { return nil }) } @@ -285,7 +281,10 @@ func parseMessage(raw json.RawMessage) ([]*jsonrpcMessage, bool) { json.Unmarshal(raw, &msgs[0]) return msgs, false } - dec := json.NewDecoder(bytes.NewReader(raw)) + // TODO: + // for some reason other json decoders are incompatible with our test suite + // pretty sure its how we handle EOFs and stuff + dec := stdjson.NewDecoder(bytes.NewReader(raw)) dec.Token() // skip '[' var msgs []*jsonrpcMessage for dec.More() { @@ -336,11 +335,9 @@ func parsePositionalArguments(rawArgs json.RawMessage, types []reflect.Type) ([] return args, nil } -var jzpool = jsoniter.NewIterator(jzon).Pool() - func parseArgumentArray(p json.RawMessage, types []reflect.Type) ([]reflect.Value, error) { - dec := jzpool.BorrowIterator(p) - defer jzpool.ReturnIterator(dec) + dec := jzon.BorrowIterator(p) + defer jzon.ReturnIterator(dec) args := make([]reflect.Value, 0, len(types)) for i := 0; dec.ReadArray(); i++ { if i >= len(types) { diff --git a/protocol.go b/protocol.go index c27ef3a..635a783 100644 --- a/protocol.go +++ b/protocol.go @@ -2,10 +2,10 @@ package jrpc import ( "context" - "encoding/json" "errors" "net/http" + json "github.com/goccy/go-json" jsoniter "github.com/json-iterator/go" ) @@ -36,7 +36,7 @@ type Request struct { func NewRequest(ctx context.Context, id string, method string, params any) *Request { r := &Request{ctx: ctx} - pms, _ := jzon.Marshal(params) + pms, _ := json.Marshal(params) r.msg = jsonrpcMessage{ ID: NewStringIDPtr(id), Method: method, @@ -55,7 +55,7 @@ func (r *Request) Params() json.RawMessage { func (r *Request) ParamSlice() []any { var params []any - jzon.Unmarshal(r.msg.Params, ¶ms) + json.Unmarshal(r.msg.Params, ¶ms) return params } @@ -69,10 +69,10 @@ func (r *Request) Iter(fn func(j *jsoniter.Iterator) error) error { func (r *Request) ParamArray(a ...any) error { var params []json.RawMessage - jzon.Unmarshal(r.msg.Params, ¶ms) + json.Unmarshal(r.msg.Params, ¶ms) for idx, v := range params { if len(v) > idx { - err := jzon.Unmarshal(v, &a[idx]) + err := json.Unmarshal(v, &a[idx]) if err != nil { return err } @@ -84,7 +84,7 @@ func (r *Request) ParamArray(a ...any) error { } func (r *Request) ParamInto(v any) error { - return jzon.Unmarshal(r.msg.Params, &v) + return json.Unmarshal(r.msg.Params, &v) } func (r *Request) Context() context.Context { @@ -126,7 +126,6 @@ type ResponseWriterMsg struct { } type options struct { - sorted bool } func UpgradeToSubscription(w ResponseWriter, r *Request) (*Subscription, error) { @@ -151,12 +150,6 @@ func (w *ResponseWriterMsg) Header() http.Header { } func (w *ResponseWriterMsg) Option(k string, v any) { - switch k { - case "sorted": - w.options.sorted = true - case "unsorted": - w.options.sorted = false - } } func (w *ResponseWriterMsg) Send(args any, e error) (err error) { @@ -171,7 +164,6 @@ func (w *ResponseWriterMsg) Send(args any, e error) (err error) { default: } w.msg = cm.response(args) - w.msg.sortKeys = w.options.sorted return nil } @@ -179,7 +171,7 @@ func (w *ResponseWriterMsg) Notify(args any) (err error) { if w.s == nil || w.n == nil { return ErrSubscriptionNotFound } - bts, _ := jzon.Marshal(args) + bts, _ := json.Marshal(args) err = w.n.send(w.s, bts) if err != nil { return err diff --git a/subscription.go b/subscription.go index 313cb26..52d0900 100644 --- a/subscription.go +++ b/subscription.go @@ -4,13 +4,14 @@ import ( "container/list" "context" "encoding/hex" - "encoding/json" "errors" "reflect" "strings" "sync" "gfx.cafe/util/go/frand" + + json "github.com/goccy/go-json" ) const ( @@ -107,7 +108,7 @@ func (n *Notifier) CreateSubscription() *Subscription { // Notify sends a notification to the client with the given data as payload. // If an error occurs the RPC connection is closed and the error is returned. func (n *Notifier) Notify(id SubID, data interface{}) error { - enc, err := jzon.Marshal(data) + enc, err := json.Marshal(data) if err != nil { return err } @@ -157,7 +158,7 @@ func (n *Notifier) activate() error { } func (n *Notifier) send(sub *Subscription, data json.RawMessage) error { - params, _ := jzon.Marshal(&subscriptionResult{ID: string(sub.ID), Result: data}) + params, _ := json.Marshal(&subscriptionResult{ID: string(sub.ID), Result: data}) ctx := context.Background() return n.h.conn.WriteJSON(ctx, &jsonrpcMessage{ Method: n.namespace + notificationMethodSuffix, @@ -180,7 +181,7 @@ func (s *Subscription) Err() <-chan error { // MarshalJSON marshals a subscription as its ID. func (s *Subscription) MarshalJSON() ([]byte, error) { - return jzon.Marshal(s.ID) + return json.Marshal(s.ID) } // ClientSubscription is a subscription established through the Client's Subscribe or @@ -303,7 +304,7 @@ func (sub *ClientSubscription) forward() (unsubscribeServer bool, err error) { func (sub *ClientSubscription) unmarshal(result json.RawMessage) (interface{}, error) { val := reflect.New(sub.etype) - err := jzon.Unmarshal(result, val.Interface()) + err := json.Unmarshal(result, val.Interface()) return val.Elem().Interface(), err } diff --git a/wire.go b/wire.go index 4a13cae..0489515 100644 --- a/wire.go +++ b/wire.go @@ -1,8 +1,10 @@ package jrpc import ( - "encoding/json" "fmt" + "strconv" + + json "github.com/goccy/go-json" ) // Version represents a JSON-RPC version. @@ -21,13 +23,13 @@ var ( // MarshalJSON implements json.Marshaler. func (version) MarshalJSON() ([]byte, error) { - return jzon.Marshal(Version) + return []byte(`"` + Version + `"`), nil } // UnmarshalJSON implements json.Unmarshaler. func (version) UnmarshalJSON(data []byte) error { version := "" - if err := jzon.Unmarshal(data, &version); err != nil { + if err := json.Unmarshal(data, &version); err != nil { return fmt.Errorf("failed to Unmarshal: %w", err) } if version != Version { @@ -89,43 +91,30 @@ func (id *ID) Format(f fmt.State, r rune) { // get the raw message func (id *ID) RawMessage() json.RawMessage { + if id == nil { + return null + } if id.null { return null } if id.name != "" { - ans, err := jzon.Marshal(id.name) - if err == nil { - return ans - } - } - ans, err := jzon.Marshal(id.number) - if err == nil { - return ans + return json.RawMessage(`"` + id.name + `"`) } - return nil + return strconv.AppendInt(make([]byte, 0, 8), id.number, 10) } // MarshalJSON implements json.Marshaler. func (id *ID) MarshalJSON() ([]byte, error) { - if id == nil { - return null, nil - } - if id.null { - return null, nil - } - if id.name != "" { - return jzon.Marshal(id.name) - } - return jzon.Marshal(id.number) + return id.RawMessage(), nil } // UnmarshalJSON implements json.Unmarshaler. func (id *ID) UnmarshalJSON(data []byte) error { *id = ID{} - if err := jzon.Unmarshal(data, &id.number); err == nil { + if err := json.Unmarshal(data, &id.number); err == nil { return nil } - if err := jzon.Unmarshal(data, &id.name); err == nil { + if err := json.Unmarshal(data, &id.name); err == nil { return nil } id.null = true diff --git a/wsjson/wsjson.go b/wsjson/wsjson.go index 01f607e..8862811 100644 --- a/wsjson/wsjson.go +++ b/wsjson/wsjson.go @@ -5,26 +5,12 @@ import ( "fmt" "gfx.cafe/util/go/bufpool" + json "github.com/goccy/go-json" jsoniter "github.com/json-iterator/go" "nhooyr.io/websocket" ) -var jzon = jsoniter.Config{ - IndentionStep: 0, - MarshalFloatWith6Digits: false, - EscapeHTML: true, - SortMapKeys: true, - UseNumber: false, - DisallowUnknownFields: false, - TagKey: "", - OnlyTaggedField: false, - ValidateJsonRawMessage: false, - ObjectFieldMustBeSimpleString: false, - CaseSensitive: false, -}.Froze() - -var JZON = jzon -var JSON = jsoniter.Config{ +var JZON = jsoniter.Config{ IndentionStep: 0, MarshalFloatWith6Digits: false, EscapeHTML: true, @@ -55,11 +41,10 @@ func read(ctx context.Context, c *websocket.Conn, v interface{}) (err error) { if err != nil { return err } - err = jzon.NewDecoder(b).Decode(v) + err = json.NewDecoder(b).Decode(v) if err != nil { return fmt.Errorf("failed to unmarshal JSON: %w", err) } - return nil } @@ -74,8 +59,8 @@ func write(ctx context.Context, c *websocket.Conn, v interface{}) (err error) { if err != nil { return err } - st := jzon.BorrowStream(w) - defer jzon.ReturnStream(st) + st := JZON.BorrowStream(w) + defer JZON.ReturnStream(st) st.WriteVal(v) err = st.Flush() if err != nil { -- GitLab