mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-06 15:08:39 +00:00
rpc: separate encoding from I/O in JSON-RPC wire format and address other feedback nits
This commit is contained in:
parent
d5ba7b0957
commit
3688824db3
6 changed files with 66 additions and 42 deletions
|
|
@ -364,7 +364,7 @@ func (c *Client) CallContext(ctx context.Context, result interface{}, method str
|
||||||
resp := batchresp[0]
|
resp := batchresp[0]
|
||||||
switch {
|
switch {
|
||||||
case resp.Error != nil:
|
case resp.Error != nil:
|
||||||
return resp.jsonErr()
|
return resp.decodeError()
|
||||||
case len(resp.Result) == 0:
|
case len(resp.Result) == 0:
|
||||||
return ErrNoResult
|
return ErrNoResult
|
||||||
default:
|
default:
|
||||||
|
|
@ -449,7 +449,7 @@ func (c *Client) BatchCallContext(ctx context.Context, b []BatchElem) error {
|
||||||
elem := &b[index]
|
elem := &b[index]
|
||||||
switch {
|
switch {
|
||||||
case resp.Error != nil:
|
case resp.Error != nil:
|
||||||
elem.Error = resp.jsonErr()
|
elem.Error = resp.decodeError()
|
||||||
case resp.Result == nil:
|
case resp.Result == nil:
|
||||||
elem.Error = ErrNoResult
|
elem.Error = ErrNoResult
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -415,7 +415,7 @@ func (h *handler) handleResponses(batch []*jsonrpcMessage, handleCall func(*json
|
||||||
// the op.resp channel.
|
// the op.resp channel.
|
||||||
if op.sub != nil {
|
if op.sub != nil {
|
||||||
if msg.Error != nil {
|
if msg.Error != nil {
|
||||||
op.err = msg.jsonErr()
|
op.err = msg.decodeError()
|
||||||
} else {
|
} else {
|
||||||
op.err = json.Unmarshal(msg.Result, &op.sub.subid)
|
op.err = json.Unmarshal(msg.Result, &op.sub.subid)
|
||||||
if op.err == nil {
|
if op.err == nil {
|
||||||
|
|
@ -481,7 +481,7 @@ func (h *handler) handleCallMsg(ctx *callProc, msg *jsonrpcMessage) *jsonrpcMess
|
||||||
var logctx []any
|
var logctx []any
|
||||||
logctx = append(logctx, "reqid", idForLog{msg.ID}, "duration", time.Since(start))
|
logctx = append(logctx, "reqid", idForLog{msg.ID}, "duration", time.Since(start))
|
||||||
if resp.Error != nil {
|
if resp.Error != nil {
|
||||||
je := resp.jsonErr()
|
je := resp.decodeError()
|
||||||
logctx = append(logctx, "err", je.Message)
|
logctx = append(logctx, "err", je.Message)
|
||||||
if je.Data != nil {
|
if je.Data != nil {
|
||||||
logctx = append(logctx, "errdata", formatErrorData(je.Data))
|
logctx = append(logctx, "errdata", formatErrorData(je.Data))
|
||||||
|
|
@ -551,7 +551,7 @@ func (h *handler) handleCall(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage
|
||||||
answer := h.runMethod(rctx, msg, callb, args)
|
answer := h.runMethod(rctx, msg, callb, args)
|
||||||
var rErr error
|
var rErr error
|
||||||
if answer.Error != nil {
|
if answer.Error != nil {
|
||||||
rErr = errors.New(answer.jsonErr().Message)
|
rErr = errors.New(answer.decodeError().Message)
|
||||||
}
|
}
|
||||||
rSpanEnd(&rErr)
|
rSpanEnd(&rErr)
|
||||||
|
|
||||||
|
|
@ -624,7 +624,7 @@ func (h *handler) runMethod(ctx context.Context, msg *jsonrpcMessage, callb *cal
|
||||||
_, _, spanEnd := telemetry.StartSpanWithTracer(ctx, h.tracer(), "rpc.encodeJSONResponse", attributes...)
|
_, _, spanEnd := telemetry.StartSpanWithTracer(ctx, h.tracer(), "rpc.encodeJSONResponse", attributes...)
|
||||||
response := msg.response(result)
|
response := msg.response(result)
|
||||||
if response.Error != nil {
|
if response.Error != nil {
|
||||||
err = errors.New(response.jsonErr().Message)
|
err = errors.New(response.decodeError().Message)
|
||||||
}
|
}
|
||||||
spanEnd(&err)
|
spanEnd(&err)
|
||||||
return response
|
return response
|
||||||
|
|
|
||||||
34
rpc/http.go
34
rpc/http.go
|
|
@ -183,9 +183,9 @@ func cleanlyCloseBody(body io.ReadCloser) error {
|
||||||
return body.Close()
|
return body.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) error {
|
func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg *jsonrpcMessage) error {
|
||||||
hc := c.writeConn.(*httpConn)
|
hc := c.writeConn.(*httpConn)
|
||||||
respBody, err := hc.doRequest(ctx, msg)
|
respBody, err := hc.doRequest(ctx, appendMessage(nil, msg))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -202,7 +202,7 @@ func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) e
|
||||||
|
|
||||||
func (c *Client) sendBatchHTTP(ctx context.Context, op *requestOp, msgs []*jsonrpcMessage) error {
|
func (c *Client) sendBatchHTTP(ctx context.Context, op *requestOp, msgs []*jsonrpcMessage) error {
|
||||||
hc := c.writeConn.(*httpConn)
|
hc := c.writeConn.(*httpConn)
|
||||||
respBody, err := hc.doRequest(ctx, msgs)
|
respBody, err := hc.doRequest(ctx, appendBatch(nil, msgs))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -216,11 +216,7 @@ func (c *Client) sendBatchHTTP(ctx context.Context, op *requestOp, msgs []*jsonr
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadCloser, error) {
|
func (hc *httpConn) doRequest(ctx context.Context, body []byte) (io.ReadCloser, error) {
|
||||||
body, err := json.Marshal(msg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, hc.url, io.NopCloser(bytes.NewReader(body)))
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, hc.url, io.NopCloser(bytes.NewReader(body)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -272,11 +268,14 @@ func (s *Server) newHTTPServerConn(r *http.Request, w http.ResponseWriter) Serve
|
||||||
body := io.LimitReader(r.Body, int64(s.httpBodyLimit))
|
body := io.LimitReader(r.Body, int64(s.httpBodyLimit))
|
||||||
conn := &httpServerConn{Reader: body, Writer: w, r: r}
|
conn := &httpServerConn{Reader: body, Writer: w, r: r}
|
||||||
|
|
||||||
|
var buf []byte
|
||||||
encodeMsg := func(msg *jsonrpcMessage, isError bool) error {
|
encodeMsg := func(msg *jsonrpcMessage, isError bool) error {
|
||||||
return httpEncodeValue(conn, w, msg, isError)
|
buf = appendMessage(buf[:0], msg)
|
||||||
|
return httpWriteResult(w, buf, isError)
|
||||||
}
|
}
|
||||||
encodeBatch := func(msgs []*jsonrpcMessage, isError bool) error {
|
encodeBatch := func(msgs []*jsonrpcMessage, isError bool) error {
|
||||||
return httpEncodeValue(conn, w, msgs, isError)
|
buf = appendBatch(buf[:0], msgs)
|
||||||
|
return httpWriteResult(w, buf, isError)
|
||||||
}
|
}
|
||||||
|
|
||||||
dec := json.NewDecoder(conn)
|
dec := json.NewDecoder(conn)
|
||||||
|
|
@ -285,12 +284,13 @@ func (s *Server) newHTTPServerConn(r *http.Request, w http.ResponseWriter) Serve
|
||||||
return NewFuncCodec(conn, encodeMsg, encodeBatch, dec.Decode)
|
return NewFuncCodec(conn, encodeMsg, encodeBatch, dec.Decode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// httpEncodeValue handles the HTTP-specific JSON encoding logic for responses.
|
// httpWriteResult writes pre-encoded response data over HTTP.
|
||||||
// For error responses, it sets Content-Length and flushes to ensure the response
|
// For error responses, it sets Content-Length and flushes to ensure the response
|
||||||
// is fully written before any HTTP server write timeout occurs.
|
// is fully written before any HTTP server write timeout occurs.
|
||||||
func httpEncodeValue(conn *httpServerConn, w http.ResponseWriter, v any, isError bool) error {
|
func httpWriteResult(w http.ResponseWriter, data []byte, isError bool) error {
|
||||||
if !isError {
|
if !isError {
|
||||||
return json.NewEncoder(conn).Encode(v)
|
_, err := w.Write(data)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// It's an error response and requires special treatment.
|
// It's an error response and requires special treatment.
|
||||||
|
|
@ -299,11 +299,7 @@ func httpEncodeValue(conn *httpServerConn, w http.ResponseWriter, v any, isError
|
||||||
// server's write timeout occurs. So we need to flush the response. The
|
// server's write timeout occurs. So we need to flush the response. The
|
||||||
// Content-Length header also needs to be set to ensure the client knows
|
// Content-Length header also needs to be set to ensure the client knows
|
||||||
// when it has the full response.
|
// when it has the full response.
|
||||||
encdata, err := json.Marshal(v)
|
w.Header().Set("content-length", strconv.Itoa(len(data)))
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w.Header().Set("content-length", strconv.Itoa(len(encdata)))
|
|
||||||
|
|
||||||
// If this request is wrapped in a handler that might remove Content-Length (such
|
// If this request is wrapped in a handler that might remove Content-Length (such
|
||||||
// as the automatic gzip we do in package node), we need to ensure the HTTP server
|
// as the automatic gzip we do in package node), we need to ensure the HTTP server
|
||||||
|
|
@ -312,7 +308,7 @@ func httpEncodeValue(conn *httpServerConn, w http.ResponseWriter, v any, isError
|
||||||
// the final chunk is missing.
|
// the final chunk is missing.
|
||||||
w.Header().Set("transfer-encoding", "identity")
|
w.Header().Set("transfer-encoding", "identity")
|
||||||
|
|
||||||
_, err = w.Write(encdata)
|
_, err := w.Write(data)
|
||||||
if f, ok := w.(http.Flusher); ok {
|
if f, ok := w.(http.Flusher); ok {
|
||||||
f.Flush()
|
f.Flush()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
43
rpc/json.go
43
rpc/json.go
|
|
@ -108,8 +108,8 @@ func (msg *jsonrpcMessage) errorResponse(err error) *jsonrpcMessage {
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
// jsonErr decodes the Error field into a jsonError struct.
|
// decodeError decodes the Error field into a jsonError struct.
|
||||||
func (msg *jsonrpcMessage) jsonErr() *jsonError {
|
func (msg *jsonrpcMessage) decodeError() *jsonError {
|
||||||
if msg.Error == nil {
|
if msg.Error == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -123,6 +123,9 @@ func (msg *jsonrpcMessage) response(result interface{}) *jsonrpcMessage {
|
||||||
enc []byte
|
enc []byte
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
// Call MarshalJSON directly for types that implement it. This avoids the
|
||||||
|
// expensive validation/compaction pass that json.Marshal performs on
|
||||||
|
// encoder output.
|
||||||
if m, ok := result.(json.Marshaler); ok {
|
if m, ok := result.(json.Marshaler); ok {
|
||||||
enc, err = m.MarshalJSON()
|
enc, err = m.MarshalJSON()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -227,22 +230,26 @@ func NewFuncCodec(conn deadlineCloser, encodeMsg encodeMsgFunc, encodeBatch enco
|
||||||
// NewCodec creates a codec on the given connection. If conn implements ConnRemoteAddr, log
|
// 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.
|
// messages will use it to include the remote address of the connection.
|
||||||
func NewCodec(conn Conn) ServerCodec {
|
func NewCodec(conn Conn) ServerCodec {
|
||||||
enc := json.NewEncoder(conn)
|
|
||||||
dec := json.NewDecoder(conn)
|
dec := json.NewDecoder(conn)
|
||||||
dec.UseNumber()
|
dec.UseNumber()
|
||||||
|
var buf []byte
|
||||||
encodeMsg := func(msg *jsonrpcMessage, isError bool) error {
|
encodeMsg := func(msg *jsonrpcMessage, isError bool) error {
|
||||||
return writeMessage(conn, msg)
|
buf = appendMessage(buf[:0], msg)
|
||||||
|
buf = append(buf, '\n')
|
||||||
|
_, err := conn.Write(buf)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
encodeBatch := func(msgs []*jsonrpcMessage, isError bool) error {
|
encodeBatch := func(msgs []*jsonrpcMessage, isError bool) error {
|
||||||
return enc.Encode(msgs)
|
buf = appendBatch(buf[:0], msgs)
|
||||||
|
buf = append(buf, '\n')
|
||||||
|
_, err := conn.Write(buf)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return NewFuncCodec(conn, encodeMsg, encodeBatch, dec.Decode)
|
return NewFuncCodec(conn, encodeMsg, encodeBatch, dec.Decode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeMessage writes a single jsonrpcMessage directly to the writer.
|
// appendMessage appends the JSON-RPC encoding of msg to buf.
|
||||||
func writeMessage(w io.Writer, msg *jsonrpcMessage) error {
|
func appendMessage(buf []byte, msg *jsonrpcMessage) []byte {
|
||||||
var buf []byte
|
|
||||||
buf = append(buf, `{"jsonrpc":"2.0"`...)
|
buf = append(buf, `{"jsonrpc":"2.0"`...)
|
||||||
if msg.ID != nil {
|
if msg.ID != nil {
|
||||||
buf = append(buf, `,"id":`...)
|
buf = append(buf, `,"id":`...)
|
||||||
|
|
@ -264,9 +271,21 @@ func writeMessage(w io.Writer, msg *jsonrpcMessage) error {
|
||||||
buf = append(buf, `,"result":`...)
|
buf = append(buf, `,"result":`...)
|
||||||
buf = append(buf, msg.Result...)
|
buf = append(buf, msg.Result...)
|
||||||
}
|
}
|
||||||
buf = append(buf, '}', '\n')
|
buf = append(buf, '}')
|
||||||
_, err := w.Write(buf)
|
return buf
|
||||||
return err
|
}
|
||||||
|
|
||||||
|
// appendBatch appends the JSON-RPC encoding of a message batch to buf.
|
||||||
|
func appendBatch(buf []byte, msgs []*jsonrpcMessage) []byte {
|
||||||
|
buf = append(buf, '[')
|
||||||
|
for i, msg := range msgs {
|
||||||
|
if i > 0 {
|
||||||
|
buf = append(buf, ',')
|
||||||
|
}
|
||||||
|
buf = appendMessage(buf, msg)
|
||||||
|
}
|
||||||
|
buf = append(buf, ']')
|
||||||
|
return buf
|
||||||
}
|
}
|
||||||
|
|
||||||
const hexDigits = "0123456789abcdef"
|
const hexDigits = "0123456789abcdef"
|
||||||
|
|
|
||||||
|
|
@ -220,7 +220,7 @@ func readAndValidateMessage(in *json.Decoder) (*subConfirmation, *subscriptionRe
|
||||||
case msg.isResponse():
|
case msg.isResponse():
|
||||||
var c subConfirmation
|
var c subConfirmation
|
||||||
if msg.Error != nil {
|
if msg.Error != nil {
|
||||||
return nil, nil, msg.jsonErr()
|
return nil, nil, msg.decodeError()
|
||||||
} else if err := json.Unmarshal(msg.Result, &c.subid); err != nil {
|
} else if err := json.Unmarshal(msg.Result, &c.subid); err != nil {
|
||||||
return nil, nil, fmt.Errorf("invalid response: %v", err)
|
return nil, nil, fmt.Errorf("invalid response: %v", err)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -237,11 +237,17 @@ type mockConn struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *mockConn) writeJSON(ctx context.Context, msg *jsonrpcMessage, isError bool) error {
|
func (c *mockConn) writeJSON(ctx context.Context, msg *jsonrpcMessage, isError bool) error {
|
||||||
return writeMessage(c.w, msg)
|
buf := appendMessage(nil, msg)
|
||||||
|
buf = append(buf, '\n')
|
||||||
|
_, err := c.w.Write(buf)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *mockConn) writeJSONBatch(ctx context.Context, msgs []*jsonrpcMessage, isError bool) error {
|
func (c *mockConn) writeJSONBatch(ctx context.Context, msgs []*jsonrpcMessage, isError bool) error {
|
||||||
return json.NewEncoder(c.w).Encode(msgs)
|
buf := appendBatch(nil, msgs)
|
||||||
|
buf = append(buf, '\n')
|
||||||
|
_, err := c.w.Write(buf)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// closed returns a channel which is closed when the connection is closed.
|
// closed returns a channel which is closed when the connection is closed.
|
||||||
|
|
|
||||||
|
|
@ -293,11 +293,14 @@ type websocketCodec struct {
|
||||||
|
|
||||||
func newWebsocketCodec(conn *websocket.Conn, host string, req http.Header, readLimit int64) ServerCodec {
|
func newWebsocketCodec(conn *websocket.Conn, host string, req http.Header, readLimit int64) ServerCodec {
|
||||||
conn.SetReadLimit(readLimit)
|
conn.SetReadLimit(readLimit)
|
||||||
|
var buf []byte
|
||||||
encodeMsg := func(msg *jsonrpcMessage, isError bool) error {
|
encodeMsg := func(msg *jsonrpcMessage, isError bool) error {
|
||||||
return conn.WriteJSON(msg)
|
buf = appendMessage(buf[:0], msg)
|
||||||
|
return conn.WriteMessage(websocket.TextMessage, buf)
|
||||||
}
|
}
|
||||||
encodeBatch := func(msgs []*jsonrpcMessage, isError bool) error {
|
encodeBatch := func(msgs []*jsonrpcMessage, isError bool) error {
|
||||||
return conn.WriteJSON(msgs)
|
buf = appendBatch(buf[:0], msgs)
|
||||||
|
return conn.WriteMessage(websocket.TextMessage, buf)
|
||||||
}
|
}
|
||||||
wc := &websocketCodec{
|
wc := &websocketCodec{
|
||||||
jsonCodec: NewFuncCodec(conn, encodeMsg, encodeBatch, conn.ReadJSON).(*jsonCodec),
|
jsonCodec: NewFuncCodec(conn, encodeMsg, encodeBatch, conn.ReadJSON).(*jsonCodec),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue