From a41fdd75f9b7506b4cf9c0135c808469e448c380 Mon Sep 17 00:00:00 2001 From: 0xcary <0xcary.web3@gmail.com> Date: Thu, 25 Apr 2024 22:13:15 +0800 Subject: [PATCH 1/8] fix pointer dereference partially --- rpc/getAccountInfo.go | 2 +- rpc/getAccountInfoWithRpcContext.go | 6 +++++- rpc/getMultipleAccounts.go | 2 +- rpc/getSignatureStatuses.go | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/rpc/getAccountInfo.go b/rpc/getAccountInfo.go index fdd0384bf..9d922fcda 100644 --- a/rpc/getAccountInfo.go +++ b/rpc/getAccountInfo.go @@ -99,7 +99,7 @@ func (cl *Client) GetAccountInfoWithOpts( if err != nil { return nil, err } - if out.Value == nil { + if out == nil || out.Value == nil { return nil, ErrNotFound } return out, nil diff --git a/rpc/getAccountInfoWithRpcContext.go b/rpc/getAccountInfoWithRpcContext.go index 6a06ffe8d..ab0628252 100644 --- a/rpc/getAccountInfoWithRpcContext.go +++ b/rpc/getAccountInfoWithRpcContext.go @@ -32,5 +32,9 @@ func (cl *Client) GetAccountInfoWithRpcContext( if err != nil { return nil, nil, err } - return out.Value, &out.RPCContext, nil + if out == nil { + return nil, nil, nil + } else { + return out.Value, &out.RPCContext, nil + } } diff --git a/rpc/getMultipleAccounts.go b/rpc/getMultipleAccounts.go index af316cf98..cd88d1451 100644 --- a/rpc/getMultipleAccounts.go +++ b/rpc/getMultipleAccounts.go @@ -74,7 +74,7 @@ func (cl *Client) GetMultipleAccountsWithOpts( if err != nil { return nil, err } - if out.Value == nil { + if out == nil || out.Value == nil { return nil, ErrNotFound } return diff --git a/rpc/getSignatureStatuses.go b/rpc/getSignatureStatuses.go index 48cc8c18f..44e2cad01 100644 --- a/rpc/getSignatureStatuses.go +++ b/rpc/getSignatureStatuses.go @@ -43,7 +43,7 @@ func (cl *Client) GetSignatureStatuses( if err != nil { return nil, err } - if out.Value == nil { + if out == nil || out.Value == nil { // Unknown transaction return nil, ErrNotFound } From ba94bf038da86582af59ea9161bd803240f2e289 Mon Sep 17 00:00:00 2001 From: 0xcary <0xcary.web3@gmail.com> Date: Wed, 23 Jul 2025 16:23:03 +0800 Subject: [PATCH 2/8] http error --- rpc/jsonrpc/jsonrpc.go | 147 ++++++++++++++++++++++++++++------------- 1 file changed, 102 insertions(+), 45 deletions(-) diff --git a/rpc/jsonrpc/jsonrpc.go b/rpc/jsonrpc/jsonrpc.go index 6a48ec95f..40b8a8bd3 100644 --- a/rpc/jsonrpc/jsonrpc.go +++ b/rpc/jsonrpc/jsonrpc.go @@ -235,9 +235,22 @@ func (e *RPCError) Error() string { // and the body could not be parsed to a valid RPCResponse object that holds a RPCError. // // Otherwise a RPCResponse object is returned with a RPCError field that is not nil. +//type HTTPError struct { +// Code int +// err error +//} + type HTTPError struct { - Code int - err error + StatusCode int + Status string + Body []byte +} + +func (err HTTPError) Error() string { + if len(err.Body) == 0 { + return err.Status + } + return fmt.Sprintf("%v: %s", err.Status, err.Body) } // HTTPClient is an abstraction for a HTTP client @@ -246,17 +259,18 @@ type HTTPClient interface { CloseIdleConnections() } -func NewHTTPError(code int, err error) *HTTPError { +func NewHTTPError(code int, message string, body []byte) *HTTPError { return &HTTPError{ - Code: code, - err: err, + StatusCode: code, + Status: message, + Body: body, } } // Error function is provided to be used as error object. -func (e *HTTPError) Error() string { - return e.err.Error() -} +//func (e *HTTPError) Error() string { +// return e.err.Error() +//} type rpcClient struct { endpoint string @@ -358,6 +372,12 @@ func NewClientWithOpts(endpoint string, opts *RPCClientOpts) RPCClient { return rpcClient } +func (client *rpcClient) SetCustomHeaders(headers map[string]string) { + for k, v := range headers { + client.customHeaders[k] = v + } +} + func (client *rpcClient) Call(ctx context.Context, method string, params ...interface{}) (*RPCResponse, error) { request := &RPCRequest{ Method: method, @@ -499,26 +519,28 @@ func (client *rpcClient) doCall( err := decoder.Decode(&rpcResponse) // parsing error if err != nil { + return err // if we have some http error, return it - if httpResponse.StatusCode >= 400 { - return &HTTPError{ - Code: httpResponse.StatusCode, - err: fmt.Errorf("rpc call %v() on %v status code: %v. could not decode body to rpc response: %w", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode, err), - } - } - return fmt.Errorf("rpc call %v() on %v status code: %v. could not decode body to rpc response: %w", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode, err) + //if httpResponse.StatusCode >= 400 { + // return &HTTPError{ + // Code: httpResponse.StatusCode, + // err: fmt.Errorf("rpc call %v() on %v status code: %v. could not decode body to rpc response: %w", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode, err), + // } + //} + //return fmt.Errorf("rpc call %v() on %v status code: %v. could not decode body to rpc response: %w", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode, err) } // response body empty if rpcResponse == nil { + return fmt.Errorf("rpc response is empty") // if we have some http error, return it - if httpResponse.StatusCode >= 400 { - return &HTTPError{ - Code: httpResponse.StatusCode, - err: fmt.Errorf("rpc call %v() on %v status code: %v. rpc response missing", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode), - } - } - return fmt.Errorf("rpc call %v() on %v status code: %v. rpc response missing", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode) + //if httpResponse.StatusCode >= 400 { + // return &HTTPError{ + // Code: httpResponse.StatusCode, + // err: fmt.Errorf("rpc call %v() on %v status code: %v. rpc response missing", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode), + // } + //} + //return fmt.Errorf("rpc call %v() on %v status code: %v. rpc response missing", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode) } return nil }, @@ -558,16 +580,34 @@ func (client *rpcClient) doCallWithCallbackOnHTTPResponse( } httpRequest, err := client.newRequest(ctx, RPCRequest) if err != nil { - if httpRequest != nil { - return fmt.Errorf("rpc call %v() on %v: %w", RPCRequest.Method, httpRequest.URL.String(), err) - } - return fmt.Errorf("rpc call %v(): %w", RPCRequest.Method, err) + return err + //if httpRequest != nil { + // return fmt.Errorf("rpc call %v() on %v: %w", RPCRequest.Method, httpRequest.URL.String(), err) + //} + //return fmt.Errorf("rpc call %v(): %w", RPCRequest.Method, err) } httpResponse, err := client.httpClient.Do(httpRequest) if err != nil { - return fmt.Errorf("rpc call %v() on %v: %w", RPCRequest.Method, httpRequest.URL.String(), err) + return err } - defer httpResponse.Body.Close() + if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 300 { + var buf bytes.Buffer + var body []byte + if _, err := buf.ReadFrom(httpResponse.Body); err == nil { + body = buf.Bytes() + } + httpResponse.Body.Close() + return HTTPError{ + Status: httpResponse.Status, + StatusCode: httpResponse.StatusCode, + Body: body, + } + } + + //if err != nil { + // return fmt.Errorf("rpc call %v() on %v: %w", RPCRequest.Method, httpRequest.URL.String(), err) + //} + //defer httpResponse.Body.Close() return callback(httpRequest, httpResponse) } @@ -581,10 +621,23 @@ func (client *rpcClient) doBatchCall(ctx context.Context, rpcRequest []*RPCReque return nil, fmt.Errorf("rpc batch call: %w", err) } httpResponse, err := client.httpClient.Do(httpRequest) - if err != nil { - return nil, fmt.Errorf("rpc batch call on %v: %w", httpRequest.URL.String(), err) + if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 300 { + var buf bytes.Buffer + var body []byte + if _, err := buf.ReadFrom(httpResponse.Body); err == nil { + body = buf.Bytes() + } + httpResponse.Body.Close() + return nil, HTTPError{ + Status: httpResponse.Status, + StatusCode: httpResponse.StatusCode, + Body: body, + } } - defer httpResponse.Body.Close() + //if err != nil { + // return nil, fmt.Errorf("rpc batch call on %v: %w", httpRequest.URL.String(), err) + //} + //defer httpResponse.Body.Close() var rpcResponse RPCResponses decoder := json.NewDecoder(httpResponse.Body) @@ -593,26 +646,30 @@ func (client *rpcClient) doBatchCall(ctx context.Context, rpcRequest []*RPCReque err = decoder.Decode(&rpcResponse) // parsing error if err != nil { + return nil, err // if we have some http error, return it - if httpResponse.StatusCode >= 400 { - return nil, &HTTPError{ - Code: httpResponse.StatusCode, - err: fmt.Errorf("rpc batch call on %v status code: %v. could not decode body to rpc response: %w", httpRequest.URL.String(), httpResponse.StatusCode, err), - } - } - return nil, fmt.Errorf("rpc batch call on %v status code: %v. could not decode body to rpc response: %w", httpRequest.URL.String(), httpResponse.StatusCode, err) + //if httpResponse.StatusCode >= 400 { + // return nil, &HTTPError{ + // StatusCode: httpResponse.StatusCode, + // Status: httpResponse.Status, + // Body: + // //err: fmt.Errorf("rpc batch call on %v status code: %v. could not decode body to rpc response: %w", httpRequest.URL.String(), httpResponse.StatusCode, err), + // } + //} + //return nil, fmt.Errorf("rpc batch call on %v status code: %v. could not decode body to rpc response: %w", httpRequest.URL.String(), httpResponse.StatusCode, err) } // response body empty if rpcResponse == nil || len(rpcResponse) == 0 { + return nil, fmt.Errorf("JSON-RPC response has no result") // if we have some http error, return it - if httpResponse.StatusCode >= 400 { - return nil, &HTTPError{ - Code: httpResponse.StatusCode, - err: fmt.Errorf("rpc batch call on %v status code: %v. rpc response missing", httpRequest.URL.String(), httpResponse.StatusCode), - } - } - return nil, fmt.Errorf("rpc batch call on %v status code: %v. rpc response missing", httpRequest.URL.String(), httpResponse.StatusCode) + //if httpResponse.StatusCode >= 400 { + // return nil, &HTTPError{ + // Code: httpResponse.StatusCode, + // err: fmt.Errorf("rpc batch call on %v status code: %v. rpc response missing", httpRequest.URL.String(), httpResponse.StatusCode), + // } + //} + //return nil, fmt.Errorf("rpc batch call on %v status code: %v. rpc response missing", httpRequest.URL.String(), httpResponse.StatusCode) } return rpcResponse, nil From 588d29718748a75ee81b6afd81cbc6211b146bde Mon Sep 17 00:00:00 2001 From: 0xcary <0xcary.web3@gmail.com> Date: Wed, 23 Jul 2025 23:43:59 +0800 Subject: [PATCH 3/8] new client with dynamic header --- rpc/client.go | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/rpc/client.go b/rpc/client.go index 722e0e58c..8f72565a4 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -23,6 +23,7 @@ import ( "io" "net" "net/http" + "sync/atomic" "time" "github.com/gagliardetto/solana-go/rpc/jsonrpc" @@ -37,6 +38,7 @@ var ( type Client struct { rpcURL string rpcClient JSONRPCClient + headerFn atomic.Value } type JSONRPCClient interface { @@ -149,3 +151,58 @@ func NewBoolean(b bool) *bool { func NewTransactionVersion(v uint64) *uint64 { return &v } + +type HeaderRoundTripper struct { + Base http.RoundTripper + Headers func() map[string]string +} + +func (h *HeaderRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + for k, v := range h.Headers() { + req.Header.Set(k, v) + } + return h.Base.RoundTrip(req) +} + +func newHTTPWithHeaderFn(headerFn func() map[string]string) *http.Client { + baseTransport := newHTTPTransport() + + transport := &HeaderRoundTripper{ + Base: gzhttp.Transport(baseTransport), + Headers: headerFn, + } + + return &http.Client{ + Timeout: defaultTimeout, + Transport: transport, + } +} + +func NewWithDynamicHeaders(rpcEndpoint string) *Client { + client := &Client{} + + // default header function is nil + // it will be set later by the user. + client.headerFn.Store(func() map[string]string { + return map[string]string{} + }) + + httpClient := newHTTPWithHeaderFn(func() map[string]string { + if v := client.headerFn.Load(); v != nil { + if f, ok := v.(func() map[string]string); ok { + return f() + } + } + return nil + }) + + rpcClient := jsonrpc.NewClientWithOpts(rpcEndpoint, &jsonrpc.RPCClientOpts{ + HTTPClient: httpClient, + }) + client.rpcClient = rpcClient + return client +} + +func (cl *Client) SetHeaderFunc(f func() map[string]string) { + cl.headerFn.Store(f) +} From b4b1e582bb9528859d4784c0dea6737008154313 Mon Sep 17 00:00:00 2001 From: 0xcary <0xcary.web3@gmail.com> Date: Thu, 24 Jul 2025 15:48:25 +0800 Subject: [PATCH 4/8] add header --- rpc/client.go | 86 ++++++++++++++---------------------------- rpc/client_test.go | 2 + rpc/jsonrpc/jsonrpc.go | 38 +++++++++++++++++-- 3 files changed, 66 insertions(+), 60 deletions(-) diff --git a/rpc/client.go b/rpc/client.go index 8f72565a4..4496327ae 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -23,7 +23,6 @@ import ( "io" "net" "net/http" - "sync/atomic" "time" "github.com/gagliardetto/solana-go/rpc/jsonrpc" @@ -38,7 +37,6 @@ var ( type Client struct { rpcURL string rpcClient JSONRPCClient - headerFn atomic.Value } type JSONRPCClient interface { @@ -69,6 +67,35 @@ func NewWithHeaders(rpcEndpoint string, headers map[string]string) *Client { return NewWithCustomRPCClient(rpcClient) } +// SetHeader dynamically adds or modifies a header for the current client. +func (cl *Client) SetHeader(key, value string) { + + if rc, ok := cl.rpcClient.(interface { + SetHeader(key, value string) + }); ok { + rc.SetHeader(key, value) + } +} + +// RemoveHeader dynamically removes a header for the current client. +func (cl *Client) RemoveHeader(key string) { + if rc, ok := cl.rpcClient.(interface { + RemoveHeader(key string) + }); ok { + rc.RemoveHeader(key) + } +} + +// GetHeaders retrieves all headers currently set for the client. +func (cl *Client) GetHeaders() map[string]string { + if rc, ok := cl.rpcClient.(interface { + GetHeaders() map[string]string + }); ok { + return rc.GetHeaders() + } + return nil +} + // Close closes the client. func (cl *Client) Close() error { if cl.rpcClient == nil { @@ -151,58 +178,3 @@ func NewBoolean(b bool) *bool { func NewTransactionVersion(v uint64) *uint64 { return &v } - -type HeaderRoundTripper struct { - Base http.RoundTripper - Headers func() map[string]string -} - -func (h *HeaderRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { - for k, v := range h.Headers() { - req.Header.Set(k, v) - } - return h.Base.RoundTrip(req) -} - -func newHTTPWithHeaderFn(headerFn func() map[string]string) *http.Client { - baseTransport := newHTTPTransport() - - transport := &HeaderRoundTripper{ - Base: gzhttp.Transport(baseTransport), - Headers: headerFn, - } - - return &http.Client{ - Timeout: defaultTimeout, - Transport: transport, - } -} - -func NewWithDynamicHeaders(rpcEndpoint string) *Client { - client := &Client{} - - // default header function is nil - // it will be set later by the user. - client.headerFn.Store(func() map[string]string { - return map[string]string{} - }) - - httpClient := newHTTPWithHeaderFn(func() map[string]string { - if v := client.headerFn.Load(); v != nil { - if f, ok := v.(func() map[string]string); ok { - return f() - } - } - return nil - }) - - rpcClient := jsonrpc.NewClientWithOpts(rpcEndpoint, &jsonrpc.RPCClientOpts{ - HTTPClient: httpClient, - }) - client.rpcClient = rpcClient - return client -} - -func (cl *Client) SetHeaderFunc(f func() map[string]string) { - cl.headerFn.Store(f) -} diff --git a/rpc/client_test.go b/rpc/client_test.go index ff65c26eb..a8ad62977 100644 --- a/rpc/client_test.go +++ b/rpc/client_test.go @@ -24,6 +24,8 @@ import ( stdjson "encoding/json" "fmt" "math/big" + "net/http" + "sync" "testing" "github.com/AlekSi/pointer" diff --git a/rpc/jsonrpc/jsonrpc.go b/rpc/jsonrpc/jsonrpc.go index 40b8a8bd3..6252690f3 100644 --- a/rpc/jsonrpc/jsonrpc.go +++ b/rpc/jsonrpc/jsonrpc.go @@ -9,6 +9,7 @@ import ( "fmt" "net/http" "reflect" + "sync" "sync/atomic" "github.com/davecgh/go-spew/spew" @@ -276,6 +277,7 @@ type rpcClient struct { endpoint string httpClient HTTPClient customHeaders map[string]string + headersMutex sync.RWMutex } // RPCClientOpts can be provided to NewClientWithOpts() to change configuration of RPCClient. @@ -349,10 +351,16 @@ func NewClient(endpoint string) RPCClient { // // opts: RPCClientOpts provide custom configuration func NewClientWithOpts(endpoint string, opts *RPCClientOpts) RPCClient { + if endpoint == "" { + panic("endpoint must not be empty") + } + + // create the rpcClient and set default values rpcClient := &rpcClient{ endpoint: endpoint, httpClient: &http.Client{}, customHeaders: make(map[string]string), + headersMutex: sync.RWMutex{}, } if opts == nil { @@ -372,10 +380,32 @@ func NewClientWithOpts(endpoint string, opts *RPCClientOpts) RPCClient { return rpcClient } -func (client *rpcClient) SetCustomHeaders(headers map[string]string) { - for k, v := range headers { - client.customHeaders[k] = v +// SetHeader can be used to dynamically add or modify a header for the current client. +func (r *rpcClient) SetHeader(key, value string) { + r.headersMutex.Lock() + defer r.headersMutex.Unlock() + if r.customHeaders == nil { + r.customHeaders = make(map[string]string) + } + r.customHeaders[key] = value +} + +// RemoveHeader can be used to dynamically remove a header for the current client. +func (r *rpcClient) RemoveHeader(key string) { + r.headersMutex.Lock() + defer r.headersMutex.Unlock() + delete(r.customHeaders, key) +} + +// GetHeaders returns a copy of the current headers set for the client. +func (r *rpcClient) GetHeaders() map[string]string { + r.headersMutex.RLock() + defer r.headersMutex.RUnlock() + copied := make(map[string]string, len(r.customHeaders)) + for k, v := range r.customHeaders { + copied[k] = v } + return copied } func (client *rpcClient) Call(ctx context.Context, method string, params ...interface{}) (*RPCResponse, error) { @@ -497,6 +527,8 @@ func (client *rpcClient) newRequest(ctx context.Context, req interface{}) (*http request.Header.Set("Accept", "application/json") // set default headers first, so that even content type and accept can be overwritten + client.headersMutex.RLock() + defer client.headersMutex.RUnlock() for k, v := range client.customHeaders { request.Header.Set(k, v) } From 355a9bef0c7b0ddc2110716b0fcecfc7bfaf428a Mon Sep 17 00:00:00 2001 From: 0xcary <0xcary.web3@gmail.com> Date: Thu, 24 Jul 2025 15:56:58 +0800 Subject: [PATCH 5/8] testcase --- rpc/client_test.go | 76 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/rpc/client_test.go b/rpc/client_test.go index a8ad62977..4500bc076 100644 --- a/rpc/client_test.go +++ b/rpc/client_test.go @@ -25,7 +25,8 @@ import ( "fmt" "math/big" "net/http" - "sync" + "net/http/httptest" + "sync/atomic" "testing" "github.com/AlekSi/pointer" @@ -3125,3 +3126,76 @@ func TestClient_GetRecentPrioritizationFees(t *testing.T) { assert.Equal(t, expected, got, "both deserialized values must be equal") } + +// mockServer will check if the request header contains the specified key/value +func mockServer(t *testing.T, wantKey, wantValue string, called *atomic.Bool) *httptest.Server { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if got := r.Header.Get(wantKey); got != wantValue { + t.Errorf("header %q: got %q, want %q", wantKey, got, wantValue) + } + called.Store(true) + _, _ = w.Write([]byte(`{"jsonrpc":"2.0","result":{},"id":1}`)) + }) + return httptest.NewServer(handler) +} + +func TestClient_SetHeader_AddAndModify(t *testing.T) { + var called atomic.Bool + server := mockServer(t, "Authorization", "Bearer testtoken", &called) + defer server.Close() + + cli := New(server.URL) + + cli.SetHeader("Authorization", "Bearer testtoken") + // send a request to trigger the server + _ = cli.RPCCallForInto(context.Background(), &struct{}{}, "getVersion", nil) + if !called.Load() { + t.Fatalf("server was not called") + } + + // modify header + called.Store(false) + cli.SetHeader("Authorization", "Bearer newtoken") + s2 := mockServer(t, "Authorization", "Bearer newtoken", &called) + defer s2.Close() + cli = New(s2.URL) + cli.SetHeader("Authorization", "Bearer newtoken") + _ = cli.RPCCallForInto(context.Background(), &struct{}{}, "getVersion", nil) + if !called.Load() { + t.Fatalf("server was not called after modifying header") + } +} + +func TestClient_SetHeader_RemoveHeader(t *testing.T) { + var called atomic.Bool + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if got := r.Header.Get("X-Remove-Me"); got != "" { + t.Errorf("header X-Remove-Me should be empty after removal, got %q", got) + } + called.Store(true) + _, _ = w.Write([]byte(`{"jsonrpc":"2.0","result":{},"id":1}`)) + })) + defer server.Close() + + cli := New(server.URL) + cli.SetHeader("X-Remove-Me", "to-be-removed") + cli.RemoveHeader("X-Remove-Me") + _ = cli.RPCCallForInto(context.Background(), &struct{}{}, "getVersion", nil) + if !called.Load() { + t.Fatalf("server was not called for RemoveHeader test") + } +} + +func TestClient_GetHeaders(t *testing.T) { + cli := New("http://localhost") + cli.SetHeader("A", "1") + cli.SetHeader("B", "2") + cli.RemoveHeader("A") + headers := cli.GetHeaders() + if _, ok := headers["A"]; ok { + t.Errorf("header A should be removed") + } + if v, ok := headers["B"]; !ok || v != "2" { + t.Errorf("header B missing or incorrect") + } +} From 9d7ac1936b629fb33f2339bbaf1cac1ffaa6e865 Mon Sep 17 00:00:00 2001 From: 0xcary <0xcary.web3@gmail.com> Date: Mon, 28 Jul 2025 17:52:59 +0800 Subject: [PATCH 6/8] new context with headers --- rpc/jsonrpc/context_headers.go | 56 ++++++++++++++++++++++++++++++++++ rpc/jsonrpc/jsonrpc.go | 35 ++------------------- 2 files changed, 58 insertions(+), 33 deletions(-) create mode 100644 rpc/jsonrpc/context_headers.go diff --git a/rpc/jsonrpc/context_headers.go b/rpc/jsonrpc/context_headers.go new file mode 100644 index 000000000..810eeb473 --- /dev/null +++ b/rpc/jsonrpc/context_headers.go @@ -0,0 +1,56 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package jsonrpc + +import ( + "context" + "net/http" +) + +type mdHeaderKey struct{} + +// NewContextWithHeaders wraps the given context, adding HTTP headers. These headers will +// be applied by Client when making a request using the returned context. +func NewContextWithHeaders(ctx context.Context, h http.Header) context.Context { + if len(h) == 0 { + // This check ensures the header map set in context will never be nil. + return ctx + } + + var ctxh http.Header + prev, ok := ctx.Value(mdHeaderKey{}).(http.Header) + if ok { + ctxh = setHeaders(prev.Clone(), h) + } else { + ctxh = h.Clone() + } + return context.WithValue(ctx, mdHeaderKey{}, ctxh) +} + +// headersFromContext is used to extract http.Header from context. +func headersFromContext(ctx context.Context) http.Header { + source, _ := ctx.Value(mdHeaderKey{}).(http.Header) + return source +} + +// setHeaders sets all headers from src in dst. +func setHeaders(dst http.Header, src http.Header) http.Header { + for key, values := range src { + dst[http.CanonicalHeaderKey(key)] = values + } + return dst +} diff --git a/rpc/jsonrpc/jsonrpc.go b/rpc/jsonrpc/jsonrpc.go index 6252690f3..104242a3e 100644 --- a/rpc/jsonrpc/jsonrpc.go +++ b/rpc/jsonrpc/jsonrpc.go @@ -9,7 +9,6 @@ import ( "fmt" "net/http" "reflect" - "sync" "sync/atomic" "github.com/davecgh/go-spew/spew" @@ -277,7 +276,6 @@ type rpcClient struct { endpoint string httpClient HTTPClient customHeaders map[string]string - headersMutex sync.RWMutex } // RPCClientOpts can be provided to NewClientWithOpts() to change configuration of RPCClient. @@ -360,7 +358,6 @@ func NewClientWithOpts(endpoint string, opts *RPCClientOpts) RPCClient { endpoint: endpoint, httpClient: &http.Client{}, customHeaders: make(map[string]string), - headersMutex: sync.RWMutex{}, } if opts == nil { @@ -380,34 +377,6 @@ func NewClientWithOpts(endpoint string, opts *RPCClientOpts) RPCClient { return rpcClient } -// SetHeader can be used to dynamically add or modify a header for the current client. -func (r *rpcClient) SetHeader(key, value string) { - r.headersMutex.Lock() - defer r.headersMutex.Unlock() - if r.customHeaders == nil { - r.customHeaders = make(map[string]string) - } - r.customHeaders[key] = value -} - -// RemoveHeader can be used to dynamically remove a header for the current client. -func (r *rpcClient) RemoveHeader(key string) { - r.headersMutex.Lock() - defer r.headersMutex.Unlock() - delete(r.customHeaders, key) -} - -// GetHeaders returns a copy of the current headers set for the client. -func (r *rpcClient) GetHeaders() map[string]string { - r.headersMutex.RLock() - defer r.headersMutex.RUnlock() - copied := make(map[string]string, len(r.customHeaders)) - for k, v := range r.customHeaders { - copied[k] = v - } - return copied -} - func (client *rpcClient) Call(ctx context.Context, method string, params ...interface{}) (*RPCResponse, error) { request := &RPCRequest{ Method: method, @@ -527,11 +496,11 @@ func (client *rpcClient) newRequest(ctx context.Context, req interface{}) (*http request.Header.Set("Accept", "application/json") // set default headers first, so that even content type and accept can be overwritten - client.headersMutex.RLock() - defer client.headersMutex.RUnlock() for k, v := range client.customHeaders { request.Header.Set(k, v) } + // set headers from context + setHeaders(request.Header, headersFromContext(ctx)) return request, nil } From 7325c006c9a83bbc0efb2c3daf7ca711e4f423b0 Mon Sep 17 00:00:00 2001 From: 0xcary <0xcary.web3@gmail.com> Date: Mon, 28 Jul 2025 17:56:39 +0800 Subject: [PATCH 7/8] remove functions with header --- rpc/client.go | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/rpc/client.go b/rpc/client.go index 4496327ae..722e0e58c 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -67,35 +67,6 @@ func NewWithHeaders(rpcEndpoint string, headers map[string]string) *Client { return NewWithCustomRPCClient(rpcClient) } -// SetHeader dynamically adds or modifies a header for the current client. -func (cl *Client) SetHeader(key, value string) { - - if rc, ok := cl.rpcClient.(interface { - SetHeader(key, value string) - }); ok { - rc.SetHeader(key, value) - } -} - -// RemoveHeader dynamically removes a header for the current client. -func (cl *Client) RemoveHeader(key string) { - if rc, ok := cl.rpcClient.(interface { - RemoveHeader(key string) - }); ok { - rc.RemoveHeader(key) - } -} - -// GetHeaders retrieves all headers currently set for the client. -func (cl *Client) GetHeaders() map[string]string { - if rc, ok := cl.rpcClient.(interface { - GetHeaders() map[string]string - }); ok { - return rc.GetHeaders() - } - return nil -} - // Close closes the client. func (cl *Client) Close() error { if cl.rpcClient == nil { From ec0b01da912d70abd1211682a4e7ff943a472652 Mon Sep 17 00:00:00 2001 From: cary Date: Wed, 29 Oct 2025 15:46:07 +0800 Subject: [PATCH 8/8] fix handle error --- rpc/jsonrpc/jsonrpc.go | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/rpc/jsonrpc/jsonrpc.go b/rpc/jsonrpc/jsonrpc.go index 104242a3e..6fe7be67d 100644 --- a/rpc/jsonrpc/jsonrpc.go +++ b/rpc/jsonrpc/jsonrpc.go @@ -7,6 +7,7 @@ import ( stdjson "encoding/json" "errors" "fmt" + "io" "net/http" "reflect" "sync/atomic" @@ -582,22 +583,21 @@ func (client *rpcClient) doCallWithCallbackOnHTTPResponse( httpRequest, err := client.newRequest(ctx, RPCRequest) if err != nil { return err - //if httpRequest != nil { - // return fmt.Errorf("rpc call %v() on %v: %w", RPCRequest.Method, httpRequest.URL.String(), err) - //} - //return fmt.Errorf("rpc call %v(): %w", RPCRequest.Method, err) } httpResponse, err := client.httpClient.Do(httpRequest) if err != nil { return err } + defer httpResponse.Body.Close() + + // allow callback to process first (regardless of status code) + if err := callback(httpRequest, httpResponse); err != nil { + return err + } + + // if HTTP status code is not 2xx, return HTTPError additionally if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 300 { - var buf bytes.Buffer - var body []byte - if _, err := buf.ReadFrom(httpResponse.Body); err == nil { - body = buf.Bytes() - } - httpResponse.Body.Close() + body, _ := io.ReadAll(io.LimitReader(httpResponse.Body, 4<<10)) // 最多4KB return HTTPError{ Status: httpResponse.Status, StatusCode: httpResponse.StatusCode, @@ -605,12 +605,7 @@ func (client *rpcClient) doCallWithCallbackOnHTTPResponse( } } - //if err != nil { - // return fmt.Errorf("rpc call %v() on %v: %w", RPCRequest.Method, httpRequest.URL.String(), err) - //} - //defer httpResponse.Body.Close() - - return callback(httpRequest, httpResponse) + return nil } func (client *rpcClient) doBatchCall(ctx context.Context, rpcRequest []*RPCRequest) ([]*RPCResponse, error) {