diff --git a/client.go b/client.go index 9d1151b340504b54b7fdb2061556cc872be0df11..03f3394295a3da2e5a25aea2c2c64e2f8709baf1 100644 --- a/client.go +++ b/client.go @@ -27,7 +27,6 @@ import ( "time" "git.tuxpa.in/a/zlog/log" - jsoniter "github.com/json-iterator/go" ) var ( @@ -286,7 +285,7 @@ func (c *Client) call(ctx context.Context, result any, msg *jsonrpcMessage) erro case len(resp.Result) == 0: return ErrNoResult default: - return jsoniter.Unmarshal(resp.Result, &result) + return jzon.Unmarshal(resp.Result, &result) } } @@ -403,7 +402,7 @@ func (c *Client) BatchCallContext(ctx context.Context, b []BatchElem) error { elem.Error = ErrNoResult continue } - elem.Error = jsoniter.Unmarshal(resp.Result, elem.Result) + elem.Error = jzon.Unmarshal(resp.Result, elem.Result) } return err @@ -428,7 +427,7 @@ func (c *Client) newMessage(method string, paramsIn ...any) (*jsonrpcMessage, er msg := &jsonrpcMessage{ID: c.nextID(), Method: method} if paramsIn != nil { // prevent sending "params":null var err error - if msg.Params, err = jsoniter.Marshal(paramsIn); err != nil { + if msg.Params, err = jzon.Marshal(paramsIn); err != nil { return nil, err } } @@ -438,7 +437,7 @@ 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 = jsoniter.Marshal(paramIn); err != nil { + if msg.Params, err = jzon.Marshal(paramIn); err != nil { return nil, err } } diff --git a/go.mod b/go.mod index 7991cedf1ef9da2685acaf8db8cafa28444a9ee9..601e2de62bcef1dfd649b6800e74625b9fa98287 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module gfx.cafe/open/jrpc go 1.18 require ( + gfx.cafe/util/go/bufpool v0.0.0-20220917112702-95618babdf53 git.tuxpa.in/a/zlog v1.32.0 github.com/davecgh/go-spew v1.1.1 github.com/deckarep/golang-set v1.8.0 diff --git a/go.sum b/go.sum index 93485aae9d3d9021dedef6e1db43a81ae39c61f0..da5523e5bf91774704ccb91aa9881737de1dc47e 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +gfx.cafe/util/go/bufpool v0.0.0-20220917112702-95618babdf53 h1:j45c1YN77NyWrO0dN+e7lKJctXpC5TlVZWmww/PpFA0= +gfx.cafe/util/go/bufpool v0.0.0-20220917112702-95618babdf53/go.mod h1:+DiyiCOBGS9O9Ce4ewHQO3Y59h66WSWAbgZZ2O2AYYw= git.tuxpa.in/a/zlog v1.32.0 h1:KKXbRF1x8kJDSzUoGz/pivo+4TVY6xT5sVtdFZ6traY= git.tuxpa.in/a/zlog v1.32.0/go.mod h1:vUa2Qhu6DLPLqmfRy99FiPqaY2eb6/KQjtMekW3UNnA= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= diff --git a/http.go b/http.go index 5de6bc17813945458278cb7cf8b62b9b5050025b..553de3d3e72131a68f63f6897a9beaafc79aa283 100644 --- a/http.go +++ b/http.go @@ -30,8 +30,6 @@ import ( "strings" "sync" "time" - - jsoniter "github.com/json-iterator/go" ) const ( @@ -180,7 +178,7 @@ func (c *Client) sendBatchHTTP(ctx context.Context, op *requestOp, msgs []*jsonr } func (hc *httpConn) doRequest(ctx context.Context, msg any) (io.ReadCloser, error) { - body, err := jsoniter.Marshal(msg) + body, err := jzon.Marshal(msg) if err != nil { return nil, err } diff --git a/json.go b/json.go index 044466ac484a9df3e43eccfed36b126898035778..37d15eb8ed57783fa1a7afbc4fac2290c37fa283 100644 --- a/json.go +++ b/json.go @@ -31,7 +31,19 @@ import ( jsoniter "github.com/json-iterator/go" ) -var jzon = jsoniter.ConfigCompatibleWithStandardLibrary +var jzon = jsoniter.Config{ + IndentionStep: 0, + MarshalFloatWith6Digits: false, + EscapeHTML: true, + SortMapKeys: true, + UseNumber: false, + DisallowUnknownFields: false, + TagKey: "", + OnlyTaggedField: false, + ValidateJsonRawMessage: true, + ObjectFieldMustBeSimpleString: false, + CaseSensitive: false, +}.Froze() const ( vsn = "2.0" @@ -206,7 +218,7 @@ func NewFuncCodec(conn deadlineCloser, encode, decode func(v any) error) ServerC // messages will use it to include the remote address of the connection. func NewCodec(conn Conn) ServerCodec { enc := jzon.NewEncoder(conn) - dec := jzon.NewDecoder(conn) + dec := json.NewDecoder(conn) dec.UseNumber() return NewFuncCodec(conn, enc.Encode, dec.Decode) } @@ -269,7 +281,7 @@ func (c *jsonCodec) closed() <-chan any { func parseMessage(raw json.RawMessage) ([]*jsonrpcMessage, bool) { if !isBatch(raw) { msgs := []*jsonrpcMessage{{}} - jsoniter.Unmarshal(raw, &msgs[0]) + jzon.Unmarshal(raw, &msgs[0]) return msgs, false } dec := json.NewDecoder(bytes.NewReader(raw)) diff --git a/protocol.go b/protocol.go index a8fd134eed5cfa06638ef01bfa9a1240cb94df3c..40a4580a8baccad95e31e70bd3320420df09889b 100644 --- a/protocol.go +++ b/protocol.go @@ -4,8 +4,6 @@ import ( "context" "encoding/json" "io" - - jsoniter "github.com/json-iterator/go" ) type HandlerFunc func(w ResponseWriter, r *Request) @@ -31,7 +29,7 @@ type Request struct { func NewRequest(ctx context.Context, id string, method string, params any) *Request { r := &Request{ctx: ctx} - pms, _ := jsoniter.Marshal(params) + pms, _ := jzon.Marshal(params) r.msg = jsonrpcMessage{ ID: NewStringIDPtr(id), Method: method, @@ -50,16 +48,16 @@ func (r *Request) Params() json.RawMessage { func (r *Request) ParamSlice() []any { var params []any - jsoniter.Unmarshal(r.msg.Params, ¶ms) + jzon.Unmarshal(r.msg.Params, ¶ms) return params } func (r *Request) ParamArray(a ...any) error { var params []json.RawMessage - jsoniter.Unmarshal(r.msg.Params, ¶ms) + jzon.Unmarshal(r.msg.Params, ¶ms) for idx, v := range params { if len(v) > idx { - err := jsoniter.Unmarshal(v, &a[idx]) + err := jzon.Unmarshal(v, &a[idx]) if err != nil { return err } @@ -71,7 +69,7 @@ func (r *Request) ParamArray(a ...any) error { } func (r *Request) ParamInto(v any) error { - return jsoniter.Unmarshal(r.msg.Params, &v) + return jzon.Unmarshal(r.msg.Params, &v) } func (r *Request) Context() context.Context { @@ -115,7 +113,7 @@ func NewReaderResponseWriterIo(r *Request, w io.Writer) ResponseWriter { } func (w *ResponseWriterIo) Send(args any, e error) (err error) { - enc := jsoniter.ConfigCompatibleWithStandardLibrary.NewEncoder(w.w) + enc := jzon.NewEncoder(w.w) if e != nil { return enc.Encode(errorMessage(e)) } diff --git a/server_test.go b/server_test.go index 7bee4d768eabef7e42b1efe945637b450b08a741..2e3f77aca4f56cfe82be81fbf411feefbf0d3dc0 100644 --- a/server_test.go +++ b/server_test.go @@ -116,10 +116,9 @@ func TestServerShortLivedConn(t *testing.T) { conn.Write([]byte(request)) conn.(*net.TCPConn).CloseWrite() // Now try to get the response. - buf := make([]byte, 2000) - n, err := conn.Read(buf) + buf, err := io.ReadAll(conn) + n := len(buf) conn.Close() - if err != nil { t.Fatal("read error:", err) } diff --git a/websocket.go b/websocket.go index ec4813693cb777c39324679b51cbb8c3c601b78b..ff80b8140bb001ca3d86d31bd6098f3eadf8ed46 100644 --- a/websocket.go +++ b/websocket.go @@ -24,9 +24,9 @@ import ( "sync" "time" + "gfx.cafe/open/jrpc/wsjson" "git.tuxpa.in/a/zlog/log" "nhooyr.io/websocket" - "nhooyr.io/websocket/wsjson" ) const ( diff --git a/wire.go b/wire.go index 08957b56257aa621d8269254a70ae141b2b6bb03..39216c86b7bff3bd5a91806391b59576f8a1a616 100644 --- a/wire.go +++ b/wire.go @@ -3,8 +3,6 @@ package jrpc import ( "encoding/json" "fmt" - - jsoniter "github.com/json-iterator/go" ) // Version represents a JSON-RPC version. @@ -23,13 +21,13 @@ var ( // MarshalJSON implements json.Marshaler. func (version) MarshalJSON() ([]byte, error) { - return jsoniter.Marshal(Version) + return jzon.Marshal(Version) } // UnmarshalJSON implements json.Unmarshaler. func (version) UnmarshalJSON(data []byte) error { version := "" - if err := jsoniter.Unmarshal(data, &version); err != nil { + if err := jzon.Unmarshal(data, &version); err != nil { return fmt.Errorf("failed to Unmarshal: %w", err) } if version != Version { @@ -95,12 +93,12 @@ func (id *ID) RawMessage() json.RawMessage { return null } if id.name != "" { - ans, err := jsoniter.Marshal(id.name) + ans, err := jzon.Marshal(id.name) if err == nil { return ans } } - ans, err := jsoniter.Marshal(id.number) + ans, err := jzon.Marshal(id.number) if err == nil { return ans } @@ -116,18 +114,18 @@ func (id *ID) MarshalJSON() ([]byte, error) { return null, nil } if id.name != "" { - return jsoniter.Marshal(id.name) + return jzon.Marshal(id.name) } - return jsoniter.Marshal(id.number) + return jzon.Marshal(id.number) } // UnmarshalJSON implements json.Unmarshaler. func (id *ID) UnmarshalJSON(data []byte) error { *id = ID{} - if err := jsoniter.Unmarshal(data, &id.number); err == nil { + if err := jzon.Unmarshal(data, &id.number); err == nil { return nil } - if err := jsoniter.Unmarshal(data, &id.name); err == nil { + if err := jzon.Unmarshal(data, &id.name); err == nil { return nil } id.null = true diff --git a/wsjson/wsjson.go b/wsjson/wsjson.go new file mode 100644 index 0000000000000000000000000000000000000000..7394181c7a5d2d24bb73e4ec16ce3c38820181d6 --- /dev/null +++ b/wsjson/wsjson.go @@ -0,0 +1,76 @@ +package wsjson + +import ( + "context" + "fmt" + + "gfx.cafe/util/go/bufpool" + 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: true, + ObjectFieldMustBeSimpleString: false, + CaseSensitive: false, +}.Froze() + +// Read reads a JSON message from c into v. +// It will reuse buffers in between calls to avoid allocations. +func Read(ctx context.Context, c *websocket.Conn, v interface{}) error { + return read(ctx, c, v) +} + +func read(ctx context.Context, c *websocket.Conn, v interface{}) (err error) { + + _, r, err := c.Reader(ctx) + if err != nil { + return err + } + + b := bufpool.Get(512) + defer bufpool.Put(b) + + _, err = b.ReadFrom(r) + if err != nil { + return err + } + + err = jzon.Unmarshal(b.Bytes(), v) + if err != nil { + return fmt.Errorf("failed to unmarshal JSON: %w", err) + } + + return nil +} + +// Write writes the JSON message v to c. +// It will reuse buffers in between calls to avoid allocations. +func Write(ctx context.Context, c *websocket.Conn, v interface{}) error { + return write(ctx, c, v) +} + +func write(ctx context.Context, c *websocket.Conn, v interface{}) (err error) { + + w, err := c.Writer(ctx, websocket.MessageText) + if err != nil { + return err + } + + // json.Marshal cannot reuse buffers between calls as it has to return + // a copy of the byte slice but Encoder does as it directly writes to w. + err = jzon.NewEncoder(w).Encode(v) + if err != nil { + return fmt.Errorf("failed to marshal JSON: %w", err) + } + + return w.Close() +}