diff --git a/contrib/codecs/rdwr/codec.go b/contrib/codecs/rdwr/codec.go index 9235b1252d899e744357c3813880d3d5b594bcf0..e75a245a11c5e9ae497b852f1e94330187b535b8 100644 --- a/contrib/codecs/rdwr/codec.go +++ b/contrib/codecs/rdwr/codec.go @@ -3,11 +3,11 @@ package rdwr import ( "bufio" "context" + "encoding/json" "io" "sync" - "github.com/goccy/go-json" - + "gfx.cafe/open/jrpc/pkg/jjson" "gfx.cafe/open/jrpc/pkg/jsonrpc" "gfx.cafe/open/jrpc/pkg/serverutil" ) @@ -19,8 +19,6 @@ type Codec struct { rd io.Reader wr *bufio.Writer - dec *json.Decoder - decBuf json.RawMessage decLock sync.Mutex } @@ -31,7 +29,6 @@ func NewCodec(rd io.Reader, wr io.Writer) *Codec { cn: cn, rd: bufio.NewReader(rd), wr: bufio.NewWriter(wr), - dec: json.NewDecoder(rd), } return c } @@ -48,13 +45,12 @@ func (c *Codec) PeerInfo() jsonrpc.PeerInfo { func (c *Codec) decodeSingleMessage(ctx context.Context) (*serverutil.Bundle, error) { c.decLock.Lock() defer c.decLock.Unlock() - //c.decBuf = c.decBuf[:0] - c.decBuf = json.RawMessage{} - err := c.dec.DecodeContext(ctx, &c.decBuf) + decBuf := make(json.RawMessage, 0) + err := jjson.Decode(c.rd, &decBuf) if err != nil { return nil, err } - return serverutil.ParseBundle(c.decBuf), nil + return serverutil.ParseBundle(decBuf), nil } func (c *Codec) ReadBatch(ctx context.Context) ([]*jsonrpc.Message, bool, error) { diff --git a/contrib/codecs/websocket/codec.go b/contrib/codecs/websocket/codec.go index 35fc49472f8a22fd4a9692ba49d6da67e457afe2..3ad099fe5be580fc7832bcd11bec678aef4f51cc 100644 --- a/contrib/codecs/websocket/codec.go +++ b/contrib/codecs/websocket/codec.go @@ -2,16 +2,17 @@ package websocket import ( "context" + "encoding/json" "io" "net/http" "sync" "time" "gfx.cafe/open/websocket" - "github.com/goccy/go-json" _ "net/http/pprof" + "gfx.cafe/open/jrpc/pkg/jjson" "gfx.cafe/open/jrpc/pkg/jsonrpc" "gfx.cafe/open/jrpc/pkg/serverutil" ) @@ -81,7 +82,7 @@ func (c *Codec) decodeSingleMessage(ctx context.Context) (*serverutil.Bundle, er return nil, err } defer io.Copy(io.Discard, r) - err = json.NewDecoder(r).DecodeContext(ctx, &c.decBuf) + err = jjson.Decode(r, &c.decBuf) if err != nil { return nil, err } diff --git a/contrib/extension/subscription/subscription.go b/contrib/extension/subscription/subscription.go index 91d53d9c77937412e2368de0599638eb52b3d40e..61b1f346e3a91d019751311b451903894fa2f2ed 100644 --- a/contrib/extension/subscription/subscription.go +++ b/contrib/extension/subscription/subscription.go @@ -4,15 +4,15 @@ import ( "context" "encoding/binary" "encoding/hex" + "encoding/json" "errors" "strings" "sync" "sync/atomic" + "gfx.cafe/open/jrpc/pkg/jjson" "gfx.cafe/open/jrpc/pkg/jsonrpc" "gfx.cafe/util/go/frand" - - json "github.com/goccy/go-json" ) var serviceMethodSeparator = "/" @@ -95,7 +95,7 @@ type Notifier struct { // 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(data any) error { - enc, err := json.Marshal(data) + enc, err := jjson.Marshal(data) if err != nil { return err } @@ -109,7 +109,7 @@ func (n *Notifier) Err() <-chan error { } func (n *Notifier) send(data json.RawMessage) error { - params, _ := json.Marshal(&subscriptionResult{ID: string(n.id), Result: data}) + params, _ := jjson.Marshal(&subscriptionResult{ID: string(n.id), Result: data}) return n.h.Notify( n.namespace+ serviceMethodSeparator+ diff --git a/contrib/handlers/argreflect/json.go b/contrib/handlers/argreflect/json.go index b66015518138268615cb0c3a0e5fa2b049291f70..31572361cef8cc2faf3a855268bc471876cf7de8 100644 --- a/contrib/handlers/argreflect/json.go +++ b/contrib/handlers/argreflect/json.go @@ -1,12 +1,13 @@ package argreflect import ( + "encoding/json" "fmt" "reflect" + "gfx.cafe/open/jrpc/pkg/jjson" "gfx.cafe/open/jrpc/pkg/jsonrpc" "github.com/go-faster/jx" - "github.com/goccy/go-json" ) // parsePositionalArguments tries to parse the given args to an array of values with the @@ -59,7 +60,7 @@ func parseArgumentArray(p json.RawMessage, types []reflect.Type) ([]reflect.Valu if err != nil { return args, jsonrpc.NewInvalidParamsError(fmt.Sprintf("invalid raw argument %d: %v", i, err)) } - err = json.Unmarshal(raw, argval.Interface()) + err = jjson.Unmarshal(raw, argval.Interface()) if err != nil { return args, jsonrpc.NewInvalidParamsError(fmt.Sprintf("invalid argument %d: %v", i, err)) } diff --git a/go.mod b/go.mod index a548c85bfde972ab85a26358913950cbbb4c7281..50d74b46f969c36438852b32cae143ed73a62ae2 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,6 @@ module gfx.cafe/open/jrpc go 1.21 -replace github.com/goccy/go-json v0.10.2 => github.com/elee1766/go-json v0.10.2-1 - require ( gfx.cafe/open/websocket v1.9.2 gfx.cafe/util/go/bufpool v0.0.0-20230721185457-c559e86c829c @@ -11,7 +9,7 @@ require ( gfx.cafe/util/go/generic v0.0.0-20230721185457-c559e86c829c github.com/davecgh/go-spew v1.1.1 github.com/go-faster/jx v1.1.0 - github.com/goccy/go-json v0.10.2 + github.com/json-iterator/go v1.1.12 github.com/stretchr/testify v1.8.4 golang.org/x/net v0.17.0 golang.org/x/sync v0.4.0 @@ -23,6 +21,8 @@ require ( github.com/go-faster/errors v0.6.1 // indirect github.com/klauspost/compress v1.17.0 // indirect github.com/kr/pretty v0.3.1 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/segmentio/asm v1.2.0 // indirect golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect diff --git a/go.sum b/go.sum index e9b7c2dc9dfd0997b722d6d37de144795c5d167e..b251c09085d2d179c150af353d1af88d9183fa1b 100644 --- a/go.sum +++ b/go.sum @@ -9,10 +9,9 @@ gfx.cafe/util/go/generic v0.0.0-20230721185457-c559e86c829c/go.mod h1:WvSX4JsCRB github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/elee1766/go-json v0.10.2-1 h1:AW2+8FAs2wVgRo90yz1l7pXKEiPxC71jquZNzgebkkc= -github.com/elee1766/go-json v0.10.2-1/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/go-faster/errors v0.6.1 h1:nNIPOBkprlKzkThvS/0YaX8Zs9KewLCOSFQS5BU06FI= github.com/go-faster/errors v0.6.1/go.mod h1:5MGV2/2T9yvlrbhe9pD9LO5Z/2zCSq2T8j+Jpi2LAyY= github.com/go-faster/jx v1.1.0 h1:ZsW3wD+snOdmTDy9eIVgQdjUpXRRV4rqW8NS3t+20bg= @@ -20,6 +19,9 @@ github.com/go-faster/jx v1.1.0/go.mod h1:vKDNikrKoyUmpzaJ0OkIkRQClNHFX/nF3dnTJZb github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= @@ -30,6 +32,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -37,6 +43,8 @@ github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZV github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg= diff --git a/pkg/jjson/json.go b/pkg/jjson/json.go new file mode 100644 index 0000000000000000000000000000000000000000..5537beaf83d4f73a3c36e2f821a1d6cc1e019bb8 --- /dev/null +++ b/pkg/jjson/json.go @@ -0,0 +1,44 @@ +package jjson + +import ( + "bytes" + "io" + + jsoniter "github.com/json-iterator/go" +) + +var jConfig = jsoniter.Config{ + ValidateJsonRawMessage: false, + EscapeHTML: false, + SortMapKeys: true, +}.Froze() + +func Encode(w io.Writer, v any) error { + s := jConfig.BorrowStream(w) + defer jConfig.ReturnStream(s) + s.WriteVal(v) + return s.Flush() +} + +func Decode(r io.Reader, v any) error { + d := jConfig.NewDecoder(r) + return d.Decode(v) +} + +func Unmarshal(xs []byte, v any) error { + d := jConfig.NewDecoder(bytes.NewBuffer(xs)) + return d.Decode(v) +} + +func Marshal(v any) ([]byte, error) { + + out := &bytes.Buffer{} + s := jConfig.BorrowStream(out) + defer jConfig.ReturnStream(s) + s.WriteVal(v) + err := s.Flush() + if err != nil { + return nil, err + } + return out.Bytes(), nil +} diff --git a/pkg/jsonrpc/reqresp.go b/pkg/jsonrpc/reqresp.go index 4959e25c0e88ecc1b716489628237fc9e88aeec6..3bd59ef358cecf63b6a8a74b7534dea9c5dbe9fb 100644 --- a/pkg/jsonrpc/reqresp.go +++ b/pkg/jsonrpc/reqresp.go @@ -2,8 +2,7 @@ package jsonrpc import ( "context" - - json "github.com/goccy/go-json" + "encoding/json" ) // http.ResponseWriter interface, but for jrpc diff --git a/pkg/jsonrpc/wire.go b/pkg/jsonrpc/wire.go index b844d5a353bf9229539eec956433c8d7f88af72d..38c1e7d84bd1282bc57919b572eb3f4d95cb1552 100644 --- a/pkg/jsonrpc/wire.go +++ b/pkg/jsonrpc/wire.go @@ -2,11 +2,12 @@ package jsonrpc import ( "bytes" + "encoding/json" "fmt" "reflect" "strconv" - "github.com/goccy/go-json" + "gfx.cafe/open/jrpc/pkg/jjson" ) // Version represents a JSON-RPC version. @@ -137,12 +138,12 @@ func (id *ID) UnmarshalJSON(data []byte) error { } // it has to be a string or number var num int - err := json.Unmarshal(data, &num) + err := jjson.Unmarshal(data, &num) if err == nil { return nil } var str string - err = json.Unmarshal(data, &str) + err = jjson.Unmarshal(data, &str) if err == nil { return nil } diff --git a/pkg/server/rw_batch.go b/pkg/server/rw_batch.go index e102594ca7423aa43b80a84b84eea7974ed32b99..e5bb473db52c721ce79d392941d124ebc146b77b 100644 --- a/pkg/server/rw_batch.go +++ b/pkg/server/rw_batch.go @@ -3,10 +3,11 @@ package server import ( "bytes" "context" + "encoding/json" "sync" + "gfx.cafe/open/jrpc/pkg/jjson" "gfx.cafe/open/jrpc/pkg/jsonrpc" - "github.com/goccy/go-json" ) // batchingRespWriter is NOT thread safe @@ -48,7 +49,7 @@ func (c *batchingRespWriter) Send(v any, e error) (err error) { if v != nil && c.err == nil { buf := &bytes.Buffer{} w := newWriter(buf, maxBatchSizeBytes, false) - err = json.NewEncoder(w).Encode(v) + err = jjson.Encode(w, v) if err != nil { // the user just gets a generic error saying that the json is bad c.err = jsonrpc.NewInternalError("server sent bad json") diff --git a/pkg/server/server.go b/pkg/server/server.go index 7ce37c85d7bffdd2498f415e3d6df3cb7eab58a1..dad905358967aaad91092a863f9a9d53bbd7121a 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -2,18 +2,19 @@ package server import ( "context" + "encoding/json" "errors" "io" "sync" "golang.org/x/sync/semaphore" + "gfx.cafe/open/jrpc/pkg/jjson" "gfx.cafe/open/jrpc/pkg/jsonrpc" "gfx.cafe/util/go/bufpool" "github.com/go-faster/jx" - "github.com/goccy/go-json" ) // Server is an RPC server. @@ -330,9 +331,7 @@ func (c *callResponder) send(ctx context.Context, env *callEnv) (err error) { case func(e *jx.Writer) error: err = cast(enc) default: - err = json.NewEncoder(w).EncodeWithOption(cast, func(eo *json.EncodeOption) { - eo.DisableNewline = true - }) + err = jjson.Encode(w, cast) } } else { enc.Null() @@ -361,7 +360,7 @@ func (c *callResponder) notify(ctx context.Context, env *notifyEnv) (err error) // allocate a temp buffer for this packet buf := bufpool.GetStd() defer bufpool.PutStd(buf) - err = json.NewEncoder(buf).Encode(env.dat) + err = jjson.Encode(buf, env.dat) if err != nil { msg.Error = err } else { diff --git a/readme.md b/readme.md index 6385cfd6990cbc0b6c112ea486e5a3784c98b22b..49fbdaf6f2c99e3b83c399bc8f3db939499c4ad2 100644 --- a/readme.md +++ b/readme.md @@ -22,7 +22,7 @@ it is currently being used in the oku.trade api in proxy, client, and server app - simple but powerful middleware framework - subscription framework used by go-ethereum/rpc is implemented as middleware. - http (with rest-like access via RPC verb), websocket, io.Reader/io.Writer (tcp, any net.Conn, etc), inproc codecs. - - using faster json packages (goccy/go-json and jx) + - using faster json packages (jsoniter, jx) - extensions, which allow setting arbitrary fields on the parent object, like in sourcegraph jsonrpc2 - jmux, which allows for http-like routing, implemented like `go-chi/v5`, except for jsonrpc2 paths - argreflect, which allows mounting methods on structs to the rpc engine, like go-ethereum/rpc