Refactor HTTP clients
This commit is contained in:
parent
58f43f60bd
commit
f0f399ab57
|
@ -27,7 +27,7 @@ import (
|
|||
|
||||
func getClient() *rpc.Client {
|
||||
httpHeaders := viper.GetStringSlice("global-http-header")
|
||||
api := rpc.NewClient(sanitizeAPIURL(viper.GetString("global-rpc-url")))
|
||||
api := rpc.New(sanitizeAPIURL(viper.GetString("global-rpc-url")))
|
||||
|
||||
for i := 0; i < 25; i++ {
|
||||
if val := os.Getenv(fmt.Sprintf("SLNC_GLOBAL_HTTP_HEADER_%d", i)); val != "" {
|
||||
|
|
4
go.mod
4
go.mod
|
@ -21,7 +21,7 @@ require (
|
|||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||
github.com/magiconair/properties v1.8.1
|
||||
github.com/mr-tron/base58 v1.2.0
|
||||
github.com/onsi/gomega v1.10.1 // indirect
|
||||
github.com/onsi/gomega v1.10.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f
|
||||
github.com/spf13/cobra v1.1.1
|
||||
|
@ -30,9 +30,9 @@ require (
|
|||
github.com/stretchr/testify v1.6.1
|
||||
github.com/teris-io/shortid v0.0.0-20201117134242-e59966efd125 // indirect
|
||||
github.com/tidwall/gjson v1.6.7
|
||||
github.com/ybbus/jsonrpc v2.1.2+incompatible
|
||||
go.opencensus.io v0.22.5 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/ratelimit v0.2.0
|
||||
go.uber.org/zap v1.16.0
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
|
||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect
|
||||
|
|
9
go.sum
9
go.sum
|
@ -37,6 +37,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
|
|||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI=
|
||||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
|
@ -217,9 +219,11 @@ github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
|||
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
|
||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1 h1:mFwc4LvZ0xpSvDZ3E+k8Yte0hLOMxXUlP+yXtJqkYfQ=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
|
||||
|
@ -298,8 +302,6 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1
|
|||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/ybbus/jsonrpc v2.1.2+incompatible h1:V4mkE9qhbDQ92/MLMIhlhMSbz8jNXdagC3xBR5NDwaQ=
|
||||
github.com/ybbus/jsonrpc v2.1.2+incompatible/go.mod h1:XJrh1eMSzdIYFbM08flv0wp5G35eRniyeGut1z+LSiE=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
|
@ -321,6 +323,8 @@ go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+
|
|||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA=
|
||||
go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.14.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
|
@ -559,6 +563,7 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
|
|||
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
|
@ -40,7 +40,7 @@ func TestDecoder_EventQueue_Diff(t *testing.T) {
|
|||
newDataJSONFile := strings.ReplaceAll(newDataFile, ".bin.zst", ".json")
|
||||
|
||||
if os.Getenv("TESTDATA_UPDATE") == "true" {
|
||||
client := rpc.NewClient("http://api.mainnet-beta.solana.com:80/rpc")
|
||||
client := rpc.New("http://api.mainnet-beta.solana.com:80/rpc")
|
||||
ctx := context.Background()
|
||||
account := solana.MustPublicKeyFromBase58("13iGJcA4w5hcJZDjJbJQor1zUiDLE4jv2rMW9HkD5Eo1")
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ func TestFetchMarket(t *testing.T) {
|
|||
|
||||
//
|
||||
|
||||
client := rpc.NewClient(rpcURL)
|
||||
client := rpc.New(rpcURL)
|
||||
ctx := context.Background()
|
||||
|
||||
openOrderAdd, err := solana.PublicKeyFromBase58("jFoHUkNDC767PyK11cZM4zyNcpjLqFnSjaqEYp5GVBr")
|
||||
|
@ -61,7 +61,7 @@ func TestStreamOpenOrders(t *testing.T) {
|
|||
t.Skip("Setup 'RPC_URL' to run test i.e. 'ws://api.mainnet-beta.solana.com:80/rpc'")
|
||||
return
|
||||
}
|
||||
client, err := ws.Dial(context.Background(), rpcURL)
|
||||
client, err := ws.Connect(context.Background(), rpcURL)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = StreamOpenOrders(client)
|
||||
|
|
|
@ -68,7 +68,7 @@ func TestAccount(t *testing.T) {
|
|||
func TestMint(t *testing.T) {
|
||||
|
||||
addr := solana.MustPublicKeyFromBase58("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")
|
||||
cli := rpc.NewClient("http://api.mainnet-beta.solana.com")
|
||||
cli := rpc.New("http://api.mainnet-beta.solana.com")
|
||||
|
||||
var m Mint
|
||||
err := cli.GetAccountDataIn(context.Background(), addr, &m)
|
||||
|
@ -85,7 +85,7 @@ func TestMint(t *testing.T) {
|
|||
func TestRawMint(t *testing.T) {
|
||||
|
||||
addr := solana.MustPublicKeyFromBase58("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")
|
||||
cli := rpc.NewClient("http://api.mainnet-beta.solana.com")
|
||||
cli := rpc.New("http://api.mainnet-beta.solana.com")
|
||||
|
||||
resp, err := cli.GetAccountInfo(context.Background(), addr)
|
||||
// handle `err`
|
||||
|
|
141
rpc/client.go
141
rpc/client.go
|
@ -15,10 +15,15 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/ybbus/jsonrpc"
|
||||
"github.com/gagliardetto/solana-go/rpc/jsonrpc"
|
||||
"github.com/klauspost/compress/gzhttp"
|
||||
"go.uber.org/ratelimit"
|
||||
)
|
||||
|
||||
var ErrNotFound = errors.New("not found")
|
||||
|
@ -26,35 +31,141 @@ var ErrNotConfirmed = errors.New("not confirmed")
|
|||
|
||||
type Client struct {
|
||||
rpcURL string
|
||||
rpcClient CallForClientInterface
|
||||
rpcClient JSONRPCClient
|
||||
headers http.Header
|
||||
}
|
||||
|
||||
type CallForClientInterface interface {
|
||||
CallFor(out interface{}, method string, params ...interface{}) error
|
||||
type JSONRPCClient interface {
|
||||
CallForInto(ctx context.Context, out interface{}, method string, params []interface{}) error
|
||||
}
|
||||
|
||||
func NewClient(rpcURL string) *Client {
|
||||
return NewClientWithOpts(rpcURL, nil)
|
||||
// New creates a new Solana JSON RPC client.
|
||||
func New(rpcEndpoint string) *Client {
|
||||
opts := &jsonrpc.RPCClientOpts{
|
||||
HTTPClient: newHTTP(),
|
||||
}
|
||||
rpcClient := jsonrpc.NewClientWithOpts(rpcEndpoint, opts)
|
||||
return NewWithCustomRPCClient(rpcClient)
|
||||
}
|
||||
|
||||
func NewClientWithOpts(rpcURL string, opts *jsonrpc.RPCClientOpts) *Client {
|
||||
rpcClient := jsonrpc.NewClientWithOpts(rpcURL, opts)
|
||||
return &Client{
|
||||
rpcURL: rpcURL,
|
||||
rpcClient: rpcClient,
|
||||
}
|
||||
}
|
||||
|
||||
func NewWithCustomRPCClient(rpcClient CallForClientInterface) *Client {
|
||||
// NewWithCustomRPCClient creates a new Solana RPC client
|
||||
// with the provided RPC client.
|
||||
func NewWithCustomRPCClient(rpcClient JSONRPCClient) *Client {
|
||||
return &Client{
|
||||
rpcClient: rpcClient,
|
||||
}
|
||||
}
|
||||
|
||||
var _ JSONRPCClient = &clientWithRateLimiting{}
|
||||
|
||||
type clientWithRateLimiting struct {
|
||||
rpcClient jsonrpc.RPCClient
|
||||
rateLimiter ratelimit.Limiter
|
||||
}
|
||||
|
||||
func (wr *clientWithRateLimiting) CallForInto(ctx context.Context, out interface{}, method string, params []interface{}) error {
|
||||
wr.rateLimiter.Take()
|
||||
return wr.rpcClient.CallForInto(ctx, &out, method, params)
|
||||
}
|
||||
|
||||
// NewWithRateLimit creates a new rate-limitted Solana RPC client.
|
||||
func NewWithRateLimit(
|
||||
rpcEndpoint string,
|
||||
rps int, // requests per second
|
||||
) JSONRPCClient {
|
||||
opts := &jsonrpc.RPCClientOpts{
|
||||
HTTPClient: newHTTP(),
|
||||
}
|
||||
|
||||
rpcClient := jsonrpc.NewClientWithOpts(rpcEndpoint, opts)
|
||||
|
||||
return &clientWithRateLimiting{
|
||||
rpcClient: rpcClient,
|
||||
rateLimiter: ratelimit.New(rps),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) SetHeader(k, v string) {
|
||||
if c.headers == nil {
|
||||
c.headers = http.Header{}
|
||||
}
|
||||
c.headers.Set(k, v)
|
||||
}
|
||||
|
||||
var (
|
||||
defaultMaxIdleConnsPerHost = 9
|
||||
defaultTimeout = 5 * time.Minute
|
||||
defaultKeepAlive = 180 * time.Second
|
||||
)
|
||||
|
||||
func newHTTPTransport() *http.Transport {
|
||||
return &http.Transport{
|
||||
IdleConnTimeout: defaultTimeout,
|
||||
MaxConnsPerHost: defaultMaxIdleConnsPerHost,
|
||||
MaxIdleConnsPerHost: defaultMaxIdleConnsPerHost,
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: defaultTimeout,
|
||||
KeepAlive: defaultKeepAlive,
|
||||
DualStack: true,
|
||||
}).DialContext,
|
||||
ForceAttemptHTTP2: true,
|
||||
// MaxIdleConns: 100,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
// ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
// newHTTP returns a new Client from the provided config.
|
||||
// Client is safe for concurrent use by multiple goroutines.
|
||||
func newHTTP() *http.Client {
|
||||
tr := newHTTPTransport()
|
||||
|
||||
return &http.Client{
|
||||
Timeout: defaultTimeout,
|
||||
Transport: gzhttp.Transport(tr),
|
||||
}
|
||||
}
|
||||
|
||||
// type wrapped struct {
|
||||
// beforeHooks []JSONRPCClient
|
||||
// cl JSONRPCClient
|
||||
// afterHooks []JSONRPCClient
|
||||
// }
|
||||
|
||||
// func (wr *wrapped) CallForInto(ctx context.Context, out interface{}, method string, params []interface{}) error {
|
||||
// for _, ware := range wr.beforeHooks {
|
||||
// err := ware.CallForInto(ctx, &out, method, params)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
|
||||
// err := wr.cl.CallForInto(ctx, &out, method, params)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// for _, ware := range wr.afterHooks {
|
||||
// err := ware.CallForInto(ctx, &out, method, params)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// func (cl *Client) WithBeforeHook(beforeHook JSONRPCClient) *Client {
|
||||
// /* code */
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// func (cl *Client) WithAfterHook(afterHook JSONRPCClient) *Client {
|
||||
// /* code */
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// // Expose this to make it easily expandable; or maybe don't ???
|
||||
// func (cl *Client) CallForInto(ctx context.Context, out interface{}, method string, params []interface{}) error {
|
||||
// return cl.rpcClient.CallForInto(ctx, &out, method, params)
|
||||
// }
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -79,7 +79,7 @@ func (cl *Client) GetConfirmedBlockWithOpts(
|
|||
}
|
||||
}
|
||||
|
||||
err = cl.rpcClient.CallFor(&out, "getConfirmedBlock", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getConfirmedBlock", params)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -106,7 +106,7 @@ func (cl *Client) GetConfirmedBlocks(
|
|||
params = append(params, M{"commitment": string(commitment)})
|
||||
}
|
||||
|
||||
err = cl.rpcClient.CallFor(&out, "getConfirmedBlocks", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getConfirmedBlocks", params)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -126,7 +126,7 @@ func (cl *Client) GetConfirmedBlocksWithLimit(
|
|||
params = append(params, M{"commitment": string(commitment)})
|
||||
}
|
||||
|
||||
err = cl.rpcClient.CallFor(&out, "getConfirmedBlocksWithLimit", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getConfirmedBlocksWithLimit", params)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -143,7 +143,7 @@ func (cl *Client) GetConfirmedSignaturesForAddress2(
|
|||
|
||||
params := []interface{}{address, opts}
|
||||
|
||||
err = cl.rpcClient.CallFor(&out, "getConfirmedSignaturesForAddress2", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getConfirmedSignaturesForAddress2", params)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -154,7 +154,7 @@ func (cl *Client) GetConfirmedTransaction(
|
|||
) (out *TransactionWithMeta, err error) {
|
||||
params := []interface{}{signature, "json"}
|
||||
|
||||
err = cl.rpcClient.CallFor(&out, "getConfirmedTransaction", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getConfirmedTransaction", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -105,7 +105,7 @@ func (cl *Client) GetAccountInfoWithOpts(
|
|||
params = append(params, obj)
|
||||
}
|
||||
|
||||
err = cl.rpcClient.CallFor(&out, "getAccountInfo", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getAccountInfo", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -34,6 +34,6 @@ func (cl *Client) GetBalance(
|
|||
params = append(params, M{"commitment": string(commitment)})
|
||||
}
|
||||
|
||||
err = cl.rpcClient.CallFor(&out, "getBalance", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getBalance", params)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -90,7 +90,7 @@ func (cl *Client) GetBlockWithOpts(
|
|||
|
||||
params := []interface{}{slot, obj}
|
||||
|
||||
err = cl.rpcClient.CallFor(&out, "getBlock", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getBlock", params)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -12,7 +12,7 @@ func (cl *Client) GetBlockCommitment(
|
|||
block uint64, // block, identified by Slot
|
||||
) (out *GetBlockCommitmentResult, err error) {
|
||||
params := []interface{}{block}
|
||||
err = cl.rpcClient.CallFor(&out, "getBlockCommitment", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getBlockCommitment", params)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,6 @@ func (cl *Client) GetBlockHeight(
|
|||
if commitment != "" {
|
||||
params = append(params, M{"commitment": commitment})
|
||||
}
|
||||
err = cl.rpcClient.CallFor(&out, "getBlockHeight", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getBlockHeight", params)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ func (cl *Client) GetBlockProductionWithOpts(
|
|||
params = append(params, obj)
|
||||
}
|
||||
}
|
||||
err = cl.rpcClient.CallFor(&out, "getBlockProduction", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getBlockProduction", params)
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -20,6 +20,6 @@ func (cl *Client) GetBlockTime(
|
|||
block uint64, // block, identified by Slot
|
||||
) (out *UnixTimeSeconds, err error) {
|
||||
params := []interface{}{block}
|
||||
err = cl.rpcClient.CallFor(&out, "getBlockTime", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getBlockTime", params)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ func (cl *Client) GetBlocks(
|
|||
M{"commitment": commitment},
|
||||
)
|
||||
}
|
||||
err = cl.rpcClient.CallFor(&out, "getBlocks", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getBlocks", params)
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -23,6 +23,6 @@ func (cl *Client) GetBlocksWithLimit(
|
|||
M{"commitment": commitment},
|
||||
)
|
||||
}
|
||||
err = cl.rpcClient.CallFor(&out, "getBlocksWithLimit", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getBlocksWithLimit", params)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
// GetClusterNodes returns information about all the nodes participating in the cluster.
|
||||
func (cl *Client) GetClusterNodes(ctx context.Context) (out []*GetClusterNodesResult, err error) {
|
||||
err = cl.rpcClient.CallFor(&out, "getClusterNodes")
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getClusterNodes", nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ func (cl *Client) GetEpochInfo(
|
|||
if commitment != "" {
|
||||
params = append(params, M{"commitment": commitment})
|
||||
}
|
||||
err = cl.rpcClient.CallFor(&out, "getEpochInfo", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getEpochInfo", params)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
|
||||
// GetEpochSchedule returns epoch schedule information from this cluster's genesis config.
|
||||
func (cl *Client) GetEpochSchedule(ctx context.Context) (out *GetEpochScheduleResult, err error) {
|
||||
err = cl.rpcClient.CallFor(&out, "getEpochSchedule")
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getEpochSchedule", nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ func (cl *Client) GetFeeCalculatorForBlockhash(
|
|||
if commitment != "" {
|
||||
params = append(params, M{"commitment": commitment})
|
||||
}
|
||||
err = cl.rpcClient.CallFor(&out, "getFeeCalculatorForBlockhash", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getFeeCalculatorForBlockhash", params)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
|
||||
// GetFeeRateGovernor returns the fee rate governor information from the root bank.
|
||||
func (cl *Client) GetFeeRateGovernor(ctx context.Context) (out *GetFeeRateGovernorResult, err error) {
|
||||
err = cl.rpcClient.CallFor(&out, "getFeeRateGovernor")
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getFeeRateGovernor", nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ func (cl *Client) GetFees(
|
|||
if commitment != "" {
|
||||
params = append(params, M{"commitment": commitment})
|
||||
}
|
||||
err = cl.rpcClient.CallFor(&out, "getFees", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getFees", params)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,6 @@ import (
|
|||
|
||||
// GetFirstAvailableBlock returns the slot of the lowest confirmed block that has not been purged from the ledger.
|
||||
func (cl *Client) GetFirstAvailableBlock(ctx context.Context) (out uint64, err error) {
|
||||
err = cl.rpcClient.CallFor(&out, "getFirstAvailableBlock")
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getFirstAvailableBlock", nil)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -8,6 +8,6 @@ import (
|
|||
|
||||
// GetGenesisHash returns the genesis hash.
|
||||
func (cl *Client) GetGenesisHash(ctx context.Context) (out solana.Hash, err error) {
|
||||
err = cl.rpcClient.CallFor(&out, "getGenesisHash")
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getGenesisHash", nil)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
// - If the node is unhealthy, a JSON RPC error response is returned.
|
||||
// The specifics of the error response are UNSTABLE and may change in the future.
|
||||
func (cl *Client) GetHealth(ctx context.Context) (out string, err error) {
|
||||
err = cl.rpcClient.CallFor(&out, "getHealth")
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getHealth", nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
|
||||
// GetIdentity returns the identity pubkey for the current node.
|
||||
func (cl *Client) GetIdentity(ctx context.Context) (out *GetIdentityResult, err error) {
|
||||
err = cl.rpcClient.CallFor(&out, "getIdentity")
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getIdentity", nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ func (cl *Client) GetInflationGovernor(
|
|||
M{"commitment": commitment},
|
||||
)
|
||||
}
|
||||
err = cl.rpcClient.CallFor(&out, "getInflationGovernor", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getInflationGovernor", params)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
|
||||
// GetInflationRate returns the specific inflation values for the current epoch.
|
||||
func (cl *Client) GetInflationRate(ctx context.Context) (out *GetInflationRateResult, err error) {
|
||||
err = cl.rpcClient.CallFor(&out, "getInflationRate")
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getInflationRate", nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ func (cl *Client) GetInflationReward(
|
|||
}
|
||||
}
|
||||
// TODO: check
|
||||
err = cl.rpcClient.CallFor(&out, "getInflationReward", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getInflationReward", params)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ func (cl *Client) GetLargestAccounts(
|
|||
if len(obj) > 0 {
|
||||
params = append(params, obj)
|
||||
}
|
||||
err = cl.rpcClient.CallFor(&out, "getLargestAccounts", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getLargestAccounts", params)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ func (cl *Client) GetLeaderScheduleWithOpts(
|
|||
params = append(params, obj)
|
||||
}
|
||||
}
|
||||
err = cl.rpcClient.CallFor(&out, "getLeaderSchedule", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getLeaderSchedule", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -6,6 +6,6 @@ import (
|
|||
|
||||
// GetMaxRetransmitSlot returns the max slot seen from retransmit stage.
|
||||
func (cl *Client) GetMaxRetransmitSlot(ctx context.Context) (out uint64, err error) {
|
||||
err = cl.rpcClient.CallFor(&out, "getMaxRetransmitSlot")
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getMaxRetransmitSlot", nil)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -8,6 +8,6 @@ type GetMaxShredInsertSlotResult struct{}
|
|||
|
||||
// GetMaxShredInsertSlot returns the max slot seen from after shred insert.
|
||||
func (cl *Client) GetMaxShredInsertSlot(ctx context.Context) (out uint64, err error) {
|
||||
err = cl.rpcClient.CallFor(&out, "getMaxShredInsertSlot")
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getMaxShredInsertSlot", nil)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -27,6 +27,6 @@ func (cl *Client) GetMinimumBalanceForRentExemption(
|
|||
if commitment != "" {
|
||||
params = append(params, M{"commitment": commitment})
|
||||
}
|
||||
err = cl.rpcClient.CallFor(&lamport, "getMinimumBalanceForRentExemption", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &lamport, "getMinimumBalanceForRentExemption", params)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ func (cl *Client) GetMultipleAccountsWithOpts(
|
|||
}
|
||||
}
|
||||
|
||||
err = cl.rpcClient.CallFor(&out, "getMultipleAccounts", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getMultipleAccounts", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -60,6 +60,6 @@ func (cl *Client) GetProgramAccountsWithOpts(
|
|||
|
||||
params := []interface{}{publicKey, obj}
|
||||
|
||||
err = cl.rpcClient.CallFor(&out, "getProgramAccounts", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getProgramAccounts", params)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -28,6 +28,6 @@ func (cl *Client) GetRecentBlockhash(
|
|||
params = append(params, M{"commitment": commitment})
|
||||
}
|
||||
|
||||
err = cl.rpcClient.CallFor(&out, "getRecentBlockhash", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getRecentBlockhash", params)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ func (cl *Client) GetRecentPerformanceSamples(ctx context.Context, limit *int) (
|
|||
if limit != nil {
|
||||
params = append(params, limit)
|
||||
}
|
||||
err = cl.rpcClient.CallFor(&out, "getRecentPerformanceSamples", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getRecentPerformanceSamples", params)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ func (cl *Client) GetSignatureStatuses(
|
|||
if searchTransactionHistory {
|
||||
params = append(params, M{"searchTransactionHistory": searchTransactionHistory})
|
||||
}
|
||||
err = cl.rpcClient.CallFor(&out, "getSignatureStatuses", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getSignatureStatuses", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -70,6 +70,6 @@ func (cl *Client) GetSignaturesForAddressWithOpts(
|
|||
}
|
||||
}
|
||||
|
||||
err = cl.rpcClient.CallFor(&out, "getSignaturesForAddress", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getSignaturesForAddress", params)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -27,6 +27,6 @@ func (cl *Client) GetSlot(
|
|||
params = append(params, M{"commitment": commitment})
|
||||
}
|
||||
|
||||
err = cl.rpcClient.CallFor(&out, "getSlot", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getSlot", params)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -15,6 +15,6 @@ func (cl *Client) GetSlotLeader(
|
|||
if commitment != "" {
|
||||
params = append(params, M{"commitment": commitment})
|
||||
}
|
||||
err = cl.rpcClient.CallFor(&out, "getSlotLeader", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getSlotLeader", params)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -13,6 +13,6 @@ func (cl *Client) GetSlotLeaders(
|
|||
limit uint64,
|
||||
) (out []solana.PublicKey, err error) {
|
||||
params := []interface{}{start, limit}
|
||||
err = cl.rpcClient.CallFor(&out, "getSlotLeaders", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getSlotLeaders", params)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -6,6 +6,6 @@ import (
|
|||
|
||||
// GetSnapshotSlot returns the highest slot that the node has a snapshot for.
|
||||
func (cl *Client) GetSnapshotSlot(ctx context.Context) (out uint64, err error) {
|
||||
err = cl.rpcClient.CallFor(&out, "getSnapshotSlot")
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getSnapshotSlot", nil)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ func (cl *Client) GetStakeActivation(
|
|||
params = append(params, obj)
|
||||
}
|
||||
}
|
||||
err = cl.rpcClient.CallFor(&out, "getStakeActivation", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getStakeActivation", params)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ func (cl *Client) GetSupply(
|
|||
M{"commitment": commitment},
|
||||
)
|
||||
}
|
||||
err = cl.rpcClient.CallFor(&out, "getSupply", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getSupply", params)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ func (cl *Client) GetTokenAccountBalance(
|
|||
M{"commitment": commitment},
|
||||
)
|
||||
}
|
||||
err = cl.rpcClient.CallFor(&out, "getTokenAccountBalance", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getTokenAccountBalance", params)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ func (cl *Client) GetTokenAccountsByDelegate(
|
|||
}
|
||||
}
|
||||
|
||||
err = cl.rpcClient.CallFor(&out, "getTokenAccountsByDelegate", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getTokenAccountsByDelegate", params)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -58,6 +58,6 @@ func (cl *Client) GetTokenAccountsByOwner(
|
|||
}
|
||||
}
|
||||
|
||||
err = cl.rpcClient.CallFor(&out, "getTokenAccountsByOwner", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getTokenAccountsByOwner", params)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ func (cl *Client) GetTokenLargestAccounts(
|
|||
M{"commitment": commitment},
|
||||
)
|
||||
}
|
||||
err = cl.rpcClient.CallFor(&out, "getTokenLargestAccounts", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getTokenLargestAccounts", params)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ func (cl *Client) GetTokenSupply(
|
|||
M{"commitment": commitment},
|
||||
)
|
||||
}
|
||||
err = cl.rpcClient.CallFor(&out, "getTokenSupply", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getTokenSupply", params)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ func (cl *Client) GetTransaction(
|
|||
params = append(params, obj)
|
||||
}
|
||||
}
|
||||
err = cl.rpcClient.CallFor(&out, "getTransaction", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getTransaction", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -15,6 +15,6 @@ func (cl *Client) GetTransactionCount(
|
|||
M{"commitment": commitment},
|
||||
)
|
||||
}
|
||||
err = cl.rpcClient.CallFor(&out, "getTransactionCount", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getTransactionCount", params)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
|
||||
// GetVersion returns the current solana versions running on the node.
|
||||
func (cl *Client) GetVersion(ctx context.Context) (out *GetVersionResult, err error) {
|
||||
err = cl.rpcClient.CallFor(&out, "getVersion")
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getVersion", nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ func (cl *Client) GetVoteAccounts(
|
|||
params = append(params, obj)
|
||||
}
|
||||
}
|
||||
err = cl.rpcClient.CallFor(&out, "getVoteAccounts", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "getVoteAccounts", params)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2016 Alexander Gehres
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,472 @@
|
|||
[![Go Report Card](https://goreportcard.com/badge/github.com/ybbus/jsonrpc)](https://goreportcard.com/report/github.com/ybbus/jsonrpc)
|
||||
[![GoDoc](https://godoc.org/github.com/ybbus/jsonrpc?status.svg)](https://godoc.org/github.com/ybbus/jsonrpc)
|
||||
[![GitHub license](https://img.shields.io/github/license/mashape/apistatus.svg)]()
|
||||
|
||||
# JSON-RPC 2.0 Client for golang
|
||||
A go implementation of an rpc client using json as data format over http.
|
||||
The implementation is based on the JSON-RPC 2.0 specification: http://www.jsonrpc.org/specification
|
||||
|
||||
Supports:
|
||||
- requests with arbitrary parameters
|
||||
- convenient response retrieval
|
||||
- batch requests
|
||||
- custom http client (e.g. proxy, tls config)
|
||||
- custom headers (e.g. basic auth)
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
go get -u github.com/ybbus/jsonrpc
|
||||
```
|
||||
|
||||
## Getting started
|
||||
Let's say we want to retrieve a person struct with a specific id using rpc-json over http.
|
||||
Then we want to save this person after we changed a property.
|
||||
(Error handling is omitted here)
|
||||
|
||||
```go
|
||||
type Person struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
|
||||
|
||||
var person *Person
|
||||
rpcClient.CallFor(&person, "getPersonById", 4711)
|
||||
|
||||
person.Age = 33
|
||||
rpcClient.Call("updatePerson", person)
|
||||
}
|
||||
```
|
||||
|
||||
## In detail
|
||||
|
||||
### Generating rpc-json requests
|
||||
|
||||
Let's start by executing a simple json-rpc http call:
|
||||
In production code: Always make sure to check err != nil first!
|
||||
|
||||
This calls generate and send a valid rpc-json object. (see: http://www.jsonrpc.org/specification#request_object)
|
||||
|
||||
```go
|
||||
func main() {
|
||||
rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
|
||||
rpcClient.Call("getDate")
|
||||
// generates body: {"method":"getDate","id":1,"jsonrpc":"2.0"}
|
||||
}
|
||||
```
|
||||
|
||||
Call a function with parameter:
|
||||
|
||||
```go
|
||||
func main() {
|
||||
rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
|
||||
rpcClient.Call("addNumbers", 1, 2)
|
||||
// generates body: {"method":"addNumbers","params":[1,2],"id":1,"jsonrpc":"2.0"}
|
||||
}
|
||||
```
|
||||
|
||||
Call a function with arbitrary parameters:
|
||||
|
||||
```go
|
||||
func main() {
|
||||
rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
|
||||
rpcClient.Call("createPerson", "Alex", 33, "Germany")
|
||||
// generates body: {"method":"createPerson","params":["Alex",33,"Germany"],"id":1,"jsonrpc":"2.0"}
|
||||
}
|
||||
```
|
||||
|
||||
Call a function providing custom data structures as parameters:
|
||||
|
||||
```go
|
||||
type Person struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
Country string `json:"country"`
|
||||
}
|
||||
func main() {
|
||||
rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
|
||||
rpcClient.Call("createPerson", &Person{"Alex", 33, "Germany"})
|
||||
// generates body: {"jsonrpc":"2.0","method":"createPerson","params":{"name":"Alex","age":33,"country":"Germany"},"id":1}
|
||||
}
|
||||
```
|
||||
|
||||
Complex example:
|
||||
|
||||
```go
|
||||
type Person struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
Country string `json:"country"`
|
||||
}
|
||||
func main() {
|
||||
rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
|
||||
rpcClient.Call("createPersonsWithRole", &Person{"Alex", 33, "Germany"}, &Person{"Barney", 38, "Germany"}, []string{"Admin", "User"})
|
||||
// generates body: {"jsonrpc":"2.0","method":"createPersonsWithRole","params":[{"name":"Alex","age":33,"country":"Germany"},{"name":"Barney","age":38,"country":"Germany"},["Admin","User"]],"id":1}
|
||||
}
|
||||
```
|
||||
|
||||
Some examples and resulting JSON-RPC objects:
|
||||
|
||||
```go
|
||||
rpcClient.Call("missingParam")
|
||||
{"method":"missingParam"}
|
||||
|
||||
rpcClient.Call("nullParam", nil)
|
||||
{"method":"nullParam","params":[null]}
|
||||
|
||||
rpcClient.Call("boolParam", true)
|
||||
{"method":"boolParam","params":[true]}
|
||||
|
||||
rpcClient.Call("boolParams", true, false, true)
|
||||
{"method":"boolParams","params":[true,false,true]}
|
||||
|
||||
rpcClient.Call("stringParam", "Alex")
|
||||
{"method":"stringParam","params":["Alex"]}
|
||||
|
||||
rpcClient.Call("stringParams", "JSON", "RPC")
|
||||
{"method":"stringParams","params":["JSON","RPC"]}
|
||||
|
||||
rpcClient.Call("numberParam", 123)
|
||||
{"method":"numberParam","params":[123]}
|
||||
|
||||
rpcClient.Call("numberParams", 123, 321)
|
||||
{"method":"numberParams","params":[123,321]}
|
||||
|
||||
rpcClient.Call("floatParam", 1.23)
|
||||
{"method":"floatParam","params":[1.23]}
|
||||
|
||||
rpcClient.Call("floatParams", 1.23, 3.21)
|
||||
{"method":"floatParams","params":[1.23,3.21]}
|
||||
|
||||
rpcClient.Call("manyParams", "Alex", 35, true, nil, 2.34)
|
||||
{"method":"manyParams","params":["Alex",35,true,null,2.34]}
|
||||
|
||||
rpcClient.Call("singlePointerToStruct", &person)
|
||||
{"method":"singlePointerToStruct","params":{"name":"Alex","age":35,"country":"Germany"}}
|
||||
|
||||
rpcClient.Call("multipleStructs", &person, &drink)
|
||||
{"method":"multipleStructs","params":[{"name":"Alex","age":35,"country":"Germany"},{"name":"Cuba Libre","ingredients":["rum","cola"]}]}
|
||||
|
||||
rpcClient.Call("singleStructInArray", []*Person{&person})
|
||||
{"method":"singleStructInArray","params":[{"name":"Alex","age":35,"country":"Germany"}]}
|
||||
|
||||
rpcClient.Call("namedParameters", map[string]interface{}{
|
||||
"name": "Alex",
|
||||
"age": 35,
|
||||
})
|
||||
{"method":"namedParameters","params":{"age":35,"name":"Alex"}}
|
||||
|
||||
rpcClient.Call("anonymousStruct", struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
}{"Alex", 33})
|
||||
{"method":"anonymousStructWithTags","params":{"name":"Alex","age":33}}
|
||||
|
||||
rpcClient.Call("structWithNullField", struct {
|
||||
Name string `json:"name"`
|
||||
Address *string `json:"address"`
|
||||
}{"Alex", nil})
|
||||
{"method":"structWithNullField","params":{"name":"Alex","address":null}}
|
||||
```
|
||||
|
||||
### Working with rpc-json responses
|
||||
|
||||
|
||||
Before working with the response object, make sure to check err != nil.
|
||||
Also keep in mind that the json-rpc result field can be nil even on success.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
|
||||
response, err := rpcClient.Call("addNumbers", 1, 2)
|
||||
if err != nil {
|
||||
// error handling goes here e.g. network / http error
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If an http error occurred, maybe you are interested in the error code (403 etc.)
|
||||
```go
|
||||
func main() {
|
||||
rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
|
||||
response, err := rpcClient.Call("addNumbers", 1, 2)
|
||||
|
||||
switch e := err.(type) {
|
||||
case nil: // if error is nil, do nothing
|
||||
case *HTTPError:
|
||||
// use e.Code here
|
||||
return
|
||||
default:
|
||||
// any other error
|
||||
return
|
||||
}
|
||||
|
||||
// no error, go on...
|
||||
}
|
||||
```
|
||||
|
||||
The next thing you have to check is if an rpc-json protocol error occurred. This is done by checking if the Error field in the rpc-response != nil:
|
||||
(see: http://www.jsonrpc.org/specification#error_object)
|
||||
|
||||
```go
|
||||
func main() {
|
||||
rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
|
||||
response, err := rpcClient.Call("addNumbers", 1, 2)
|
||||
if err != nil {
|
||||
//error handling goes here
|
||||
}
|
||||
|
||||
if response.Error != nil {
|
||||
// rpc error handling goes here
|
||||
// check response.Error.Code, response.Error.Message and optional response.Error.Data
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After making sure that no errors occurred you can now examine the RPCResponse object.
|
||||
When executing a json-rpc request, most of the time you will be interested in the "result"-property of the returned json-rpc response object.
|
||||
(see: http://www.jsonrpc.org/specification#response_object)
|
||||
The library provides some helper functions to retrieve the result in the data format you are interested in.
|
||||
Again: check for err != nil here to be sure the expected type was provided in the response and could be parsed.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
|
||||
response, _ := rpcClient.Call("addNumbers", 1, 2)
|
||||
|
||||
result, err := response.GetInt()
|
||||
if err != nil {
|
||||
// result cannot be unmarshalled as integer
|
||||
}
|
||||
|
||||
// helpers provided for all primitive types:
|
||||
response.GetInt()
|
||||
response.GetFloat()
|
||||
response.GetString()
|
||||
response.GetBool()
|
||||
}
|
||||
```
|
||||
|
||||
Retrieving arrays and objects is also very simple:
|
||||
|
||||
```go
|
||||
// json annotations are only required to transform the structure back to json
|
||||
type Person struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
|
||||
response, _ := rpcClient.Call("getPersonById", 123)
|
||||
|
||||
var person *Person
|
||||
err := response.GetObject(&person) // expects a rpc-object result value like: {"id": 123, "name": "alex", "age": 33}
|
||||
if err != nil || person == nil {
|
||||
// some error on json unmarshal level or json result field was null
|
||||
}
|
||||
|
||||
fmt.Println(person.Name)
|
||||
|
||||
// we can also set default values if they are missing from the result, or result == null:
|
||||
person2 := &Person{
|
||||
Id: 0,
|
||||
Name: "<empty>",
|
||||
Age: -1,
|
||||
}
|
||||
err := response.GetObject(&person2) // expects a rpc-object result value like: {"id": 123, "name": "alex", "age": 33}
|
||||
if err != nil || person2 == nil {
|
||||
// some error on json unmarshal level or json result field was null
|
||||
}
|
||||
|
||||
fmt.Println(person2.Name) // prints "<empty>" if "name" field was missing in result-json
|
||||
}
|
||||
```
|
||||
|
||||
Retrieving arrays:
|
||||
|
||||
```go
|
||||
func main() {
|
||||
rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
|
||||
response, _ := rpcClient.Call("getRandomNumbers", 10)
|
||||
|
||||
rndNumbers := []int{}
|
||||
err := response.GetObject(&rndNumbers) // expects a rpc-object result value like: [10, 188, 14, 3]
|
||||
if err != nil {
|
||||
// do error handling
|
||||
}
|
||||
|
||||
for _, num := range rndNumbers {
|
||||
fmt.Printf("%v\n", num)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using convenient function CallFor()
|
||||
A very handy way to quickly invoke methods and retrieve results is by using CallFor()
|
||||
|
||||
You can directly provide an object where the result should be stored. Be sure to provide it be reference.
|
||||
An error is returned if:
|
||||
- there was an network / http error
|
||||
- RPCError object is not nil (err can be casted to this object)
|
||||
- rpc result could not be parsed into provided object
|
||||
|
||||
One of te above examples could look like this:
|
||||
|
||||
```go
|
||||
// json annotations are only required to transform the structure back to json
|
||||
type Person struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
|
||||
|
||||
var person *Person
|
||||
err := rpcClient.CallFor(&person, "getPersonById", 123)
|
||||
|
||||
if err != nil || person == nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
fmt.Println(person.Name)
|
||||
}
|
||||
```
|
||||
|
||||
Most of the time it is ok to check if a struct field is 0, empty string "" etc. to check if it was provided by the json rpc response.
|
||||
But if you want to be sure that a JSON-RPC response field was missing or not, you should use pointers to the fields.
|
||||
This is just a single example since all this Unmarshaling is standard go json functionality, exactly as if you would call json.Unmarshal(rpcResponse.ResultAsByteArray, &objectToStoreResult)
|
||||
|
||||
```
|
||||
type Person struct {
|
||||
Id *int `json:"id"`
|
||||
Name *string `json:"name"`
|
||||
Age *int `json:"age"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
|
||||
|
||||
var person *Person
|
||||
err := rpcClient.CallFor(&person, "getPersonById", 123)
|
||||
|
||||
if err != nil || person == nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
if person.Name == nil {
|
||||
// json rpc response did not provide a field "name" in the result object
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using RPC Batch Requests
|
||||
|
||||
You can send multiple RPC-Requests in one single HTTP request using RPC Batch Requests.
|
||||
|
||||
```
|
||||
func main() {
|
||||
rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
|
||||
|
||||
response, _ := rpcClient.CallBatch(RPCRequests{
|
||||
NewRequest("myMethod1", 1, 2, 3),
|
||||
NewRequest("anotherMethod", "Alex", 35, true),
|
||||
NewRequest("myMethod2", &Person{
|
||||
Name: "Emmy",
|
||||
Age: 4,
|
||||
}),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Keep the following in mind:
|
||||
- the request / response id's are important to map the requests to the responses. CallBatch() automatically sets the ids to requests[i].ID == i
|
||||
- the response can be provided in an unordered and maybe incomplete form
|
||||
- when you want to set the id yourself use, CallRaw()
|
||||
|
||||
There are some helper methods for batch request results:
|
||||
```
|
||||
func main() {
|
||||
// [...]
|
||||
|
||||
result.HasErrors() // returns true if one of the rpc response objects has Error field != nil
|
||||
resultMap := result.AsMap() // returns a map for easier retrieval of requests
|
||||
|
||||
if response123, ok := resultMap[123]; ok {
|
||||
// response object with id 123 exists, use it here
|
||||
// response123.ID == 123
|
||||
response123.GetObjectAs(&person)
|
||||
// ...
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### Raw functions
|
||||
There are also Raw function calls. Consider the non Raw functions first, unless you know what you are doing.
|
||||
You can create invalid json rpc requests and have to take care of id's etc. yourself.
|
||||
Also check documentation of Params() for raw requests.
|
||||
|
||||
### Custom Headers, Basic authentication
|
||||
|
||||
If the rpc-service is running behind a basic authentication you can easily set the Authorization header:
|
||||
|
||||
```go
|
||||
func main() {
|
||||
rpcClient := jsonrpc.NewClientWithOpts("http://my-rpc-service:8080/rpc", &jsonrpc.RPCClientOpts{
|
||||
CustomHeaders: map[string]string{
|
||||
"Authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte("myUser"+":"+"mySecret")),
|
||||
},
|
||||
})
|
||||
response, _ := rpcClient.Call("addNumbers", 1, 2) // send with Authorization-Header
|
||||
}
|
||||
```
|
||||
|
||||
### Using oauth
|
||||
|
||||
Using oauth is also easy, e.g. with clientID and clientSecret authentication
|
||||
|
||||
```go
|
||||
func main() {
|
||||
credentials := clientcredentials.Config{
|
||||
ClientID: "myID",
|
||||
ClientSecret: "mySecret",
|
||||
TokenURL: "http://mytokenurl",
|
||||
}
|
||||
|
||||
rpcClient := jsonrpc.NewClientWithOpts("http://my-rpc-service:8080/rpc", &jsonrpc.RPCClientOpts{
|
||||
HTTPClient: credentials.Client(context.Background()),
|
||||
})
|
||||
|
||||
// requests now retrieve and use an oauth token
|
||||
}
|
||||
```
|
||||
|
||||
### Set a custom httpClient
|
||||
|
||||
If you have some special needs on the http.Client of the standard go library, just provide your own one.
|
||||
For example to use a proxy when executing json-rpc calls:
|
||||
|
||||
```go
|
||||
func main() {
|
||||
proxyURL, _ := url.Parse("http://proxy:8080")
|
||||
transport := &http.Transport{Proxy: http.ProxyURL(proxyURL)}
|
||||
|
||||
httpClient := &http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
rpcClient := jsonrpc.NewClientWithOpts("http://my-rpc-service:8080/rpc", &jsonrpc.RPCClientOpts{
|
||||
HTTPClient: httpClient,
|
||||
})
|
||||
|
||||
// requests now use proxy
|
||||
}
|
||||
```
|
|
@ -0,0 +1,651 @@
|
|||
// Package jsonrpc provides a JSON-RPC 2.0 client that sends JSON-RPC requests and receives JSON-RPC responses using HTTP.
|
||||
package jsonrpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
jsonrpcVersion = "2.0"
|
||||
)
|
||||
|
||||
// RPCClient sends JSON-RPC requests over HTTP to the provided JSON-RPC backend.
|
||||
//
|
||||
// RPCClient is created using the factory function NewClient().
|
||||
type RPCClient interface {
|
||||
// Call is used to send a JSON-RPC request to the server endpoint.
|
||||
//
|
||||
// The spec states, that params can only be an array or an object, no primitive values.
|
||||
// So there are a few simple rules to notice:
|
||||
//
|
||||
// 1. no params: params field is omitted. e.g. Call("getinfo")
|
||||
//
|
||||
// 2. single params primitive value: value is wrapped in array. e.g. Call("getByID", 1423)
|
||||
//
|
||||
// 3. single params value array or object: value is unchanged. e.g. Call("storePerson", &Person{Name: "Alex"})
|
||||
//
|
||||
// 4. multiple params values: always wrapped in array. e.g. Call("setDetails", "Alex, 35, "Germany", true)
|
||||
//
|
||||
// Examples:
|
||||
// Call("getinfo") -> {"method": "getinfo"}
|
||||
// Call("getPersonId", 123) -> {"method": "getPersonId", "params": [123]}
|
||||
// Call("setName", "Alex") -> {"method": "setName", "params": ["Alex"]}
|
||||
// Call("setMale", true) -> {"method": "setMale", "params": [true]}
|
||||
// Call("setNumbers", []int{1, 2, 3}) -> {"method": "setNumbers", "params": [1, 2, 3]}
|
||||
// Call("setNumbers", 1, 2, 3) -> {"method": "setNumbers", "params": [1, 2, 3]}
|
||||
// Call("savePerson", &Person{Name: "Alex", Age: 35}) -> {"method": "savePerson", "params": {"name": "Alex", "age": 35}}
|
||||
// Call("setPersonDetails", "Alex", 35, "Germany") -> {"method": "setPersonDetails", "params": ["Alex", 35, "Germany"}}
|
||||
//
|
||||
// for more information, see the examples or the unit tests
|
||||
Call(ctx context.Context, method string, params ...interface{}) (*RPCResponse, error)
|
||||
|
||||
// CallRaw is like Call() but without magic in the requests.Params field.
|
||||
// The RPCRequest object is sent exactly as you provide it.
|
||||
// See docs: NewRequest, RPCRequest, Params()
|
||||
//
|
||||
// It is recommended to first consider Call() and CallFor()
|
||||
CallRaw(ctx context.Context, request *RPCRequest) (*RPCResponse, error)
|
||||
|
||||
// CallFor is a very handy function to send a JSON-RPC request to the server endpoint
|
||||
// and directly specify an object to store the response.
|
||||
//
|
||||
// out: will store the unmarshaled object, if request was successful.
|
||||
// should always be provided by references. can be nil even on success.
|
||||
// the behaviour is the same as expected from json.Unmarshal()
|
||||
//
|
||||
// method and params: see Call() function
|
||||
//
|
||||
// if the request was not successful (network, http error) or the rpc response returns an error,
|
||||
// an error is returned. if it was an JSON-RPC error it can be casted
|
||||
// to *RPCError.
|
||||
//
|
||||
CallFor(ctx context.Context, out interface{}, method string, params ...interface{}) error
|
||||
|
||||
CallForInto(ctx context.Context, out interface{}, method string, params []interface{}) error
|
||||
|
||||
// CallBatch invokes a list of RPCRequests in a single batch request.
|
||||
//
|
||||
// Most convenient is to use the following form:
|
||||
// CallBatch(RPCRequests{
|
||||
// Batch("myMethod1", 1, 2, 3),
|
||||
// Batch("myMethod2), "Test"),
|
||||
// })
|
||||
//
|
||||
// You can create the []*RPCRequest array yourself, but it is not recommended and you should notice the following:
|
||||
// - field Params is sent as provided, so Params: 2 forms an invalid json (correct would be Params: []int{2})
|
||||
// - you can use the helper function Params(1, 2, 3) to use the same format as in Call()
|
||||
// - field JSONRPC is overwritten and set to value: "2.0"
|
||||
// - field ID is overwritten and set incrementally and maps to the array position (e.g. requests[5].ID == 5)
|
||||
//
|
||||
//
|
||||
// Returns RPCResponses that is of type []*RPCResponse
|
||||
// - note that a list of RPCResponses can be received unordered so it can happen that: responses[i] != responses[i].ID
|
||||
// - RPCPersponses is enriched with helper functions e.g.: responses.HasError() returns true if one of the responses holds an RPCError
|
||||
CallBatch(ctx context.Context, requests RPCRequests) (RPCResponses, error)
|
||||
|
||||
// CallBatchRaw invokes a list of RPCRequests in a single batch request.
|
||||
// It sends the RPCRequests parameter is it passed (no magic, no id autoincrement).
|
||||
//
|
||||
// Consider to use CallBatch() instead except you have some good reason not to.
|
||||
//
|
||||
// CallBatchRaw(RPCRequests{
|
||||
// &RPCRequest{
|
||||
// ID: 123, // this won't be replaced in CallBatchRaw
|
||||
// JSONRPC: "wrong", // this won't be replaced in CallBatchRaw
|
||||
// Method: "myMethod1",
|
||||
// Params: []int{1}, // there is no magic, be sure to only use array or object
|
||||
// },
|
||||
// &RPCRequest{
|
||||
// ID: 612,
|
||||
// JSONRPC: "2.0",
|
||||
// Method: "myMethod2",
|
||||
// Params: Params("Alex", 35, true), // you can use helper function Params() (see doc)
|
||||
// },
|
||||
// })
|
||||
//
|
||||
// Returns RPCResponses that is of type []*RPCResponse
|
||||
// - note that a list of RPCResponses can be received unordered
|
||||
// - the id's must be mapped against the id's you provided
|
||||
// - RPCPersponses is enriched with helper functions e.g.: responses.HasError() returns true if one of the responses holds an RPCError
|
||||
CallBatchRaw(ctx context.Context, requests RPCRequests) (RPCResponses, error)
|
||||
}
|
||||
|
||||
// RPCRequest represents a JSON-RPC request object.
|
||||
//
|
||||
// Method: string containing the method to be invoked
|
||||
//
|
||||
// Params: can be nil. if not must be an json array or object
|
||||
//
|
||||
// ID: may always set to 1 for single requests. Should be unique for every request in one batch request.
|
||||
//
|
||||
// JSONRPC: must always be set to "2.0" for JSON-RPC version 2.0
|
||||
//
|
||||
// See: http://www.jsonrpc.org/specification#request_object
|
||||
//
|
||||
// Most of the time you shouldn't create the RPCRequest object yourself.
|
||||
// The following functions do that for you:
|
||||
// Call(), CallFor(), NewRequest()
|
||||
//
|
||||
// If you want to create it yourself (e.g. in batch or CallRaw()), consider using Params().
|
||||
// Params() is a helper function that uses the same parameter syntax as Call().
|
||||
//
|
||||
// e.g. to manually create an RPCRequest object:
|
||||
// request := &RPCRequest{
|
||||
// Method: "myMethod",
|
||||
// Params: Params("Alex", 35, true),
|
||||
// }
|
||||
//
|
||||
// If you know what you are doing you can omit the Params() call to avoid some reflection but potentially create incorrect rpc requests:
|
||||
//request := &RPCRequest{
|
||||
// Method: "myMethod",
|
||||
// Params: 2, <-- invalid since a single primitive value must be wrapped in an array --> no magic without Params()
|
||||
// }
|
||||
//
|
||||
// correct:
|
||||
// request := &RPCRequest{
|
||||
// Method: "myMethod",
|
||||
// Params: []int{2}, <-- invalid since a single primitive value must be wrapped in an array
|
||||
// }
|
||||
type RPCRequest struct {
|
||||
Method string `json:"method"`
|
||||
Params interface{} `json:"params,omitempty"`
|
||||
ID int `json:"id"`
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
}
|
||||
|
||||
// NewRequest returns a new RPCRequest that can be created using the same convenient parameter syntax as Call()
|
||||
//
|
||||
// e.g. NewRequest("myMethod", "Alex", 35, true)
|
||||
func NewRequest(method string, params ...interface{}) *RPCRequest {
|
||||
request := &RPCRequest{
|
||||
Method: method,
|
||||
Params: Params(params...),
|
||||
JSONRPC: jsonrpcVersion,
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
// RPCResponse represents a JSON-RPC response object.
|
||||
//
|
||||
// Result: holds the result of the rpc call if no error occurred, nil otherwise. can be nil even on success.
|
||||
//
|
||||
// Error: holds an RPCError object if an error occurred. must be nil on success.
|
||||
//
|
||||
// ID: may always be 0 for single requests. is unique for each request in a batch call (see CallBatch())
|
||||
//
|
||||
// JSONRPC: must always be set to "2.0" for JSON-RPC version 2.0
|
||||
//
|
||||
// See: http://www.jsonrpc.org/specification#response_object
|
||||
type RPCResponse struct {
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
Result interface{} `json:"result,omitempty"`
|
||||
Error *RPCError `json:"error,omitempty"`
|
||||
ID int `json:"id"`
|
||||
}
|
||||
|
||||
// RPCError represents a JSON-RPC error object if an RPC error occurred.
|
||||
//
|
||||
// Code: holds the error code
|
||||
//
|
||||
// Message: holds a short error message
|
||||
//
|
||||
// Data: holds additional error data, may be nil
|
||||
//
|
||||
// See: http://www.jsonrpc.org/specification#error_object
|
||||
type RPCError struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// Error function is provided to be used as error object.
|
||||
func (e *RPCError) Error() string {
|
||||
return strconv.Itoa(e.Code) + ":" + e.Message
|
||||
}
|
||||
|
||||
// HTTPError represents a error that occurred on HTTP level.
|
||||
//
|
||||
// An error of type HTTPError is returned when a HTTP error occurred (status code)
|
||||
// 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
|
||||
}
|
||||
|
||||
// Error function is provided to be used as error object.
|
||||
func (e *HTTPError) Error() string {
|
||||
return e.err.Error()
|
||||
}
|
||||
|
||||
type rpcClient struct {
|
||||
endpoint string
|
||||
httpClient *http.Client
|
||||
customHeaders map[string]string
|
||||
}
|
||||
|
||||
// RPCClientOpts can be provided to NewClientWithOpts() to change configuration of RPCClient.
|
||||
//
|
||||
// HTTPClient: provide a custom http.Client (e.g. to set a proxy, or tls options)
|
||||
//
|
||||
// CustomHeaders: provide custom headers, e.g. to set BasicAuth
|
||||
type RPCClientOpts struct {
|
||||
HTTPClient *http.Client
|
||||
CustomHeaders map[string]string
|
||||
}
|
||||
|
||||
// RPCResponses is of type []*RPCResponse.
|
||||
// This type is used to provide helper functions on the result list
|
||||
type RPCResponses []*RPCResponse
|
||||
|
||||
// AsMap returns the responses as map with response id as key.
|
||||
func (res RPCResponses) AsMap() map[int]*RPCResponse {
|
||||
resMap := make(map[int]*RPCResponse, 0)
|
||||
for _, r := range res {
|
||||
resMap[r.ID] = r
|
||||
}
|
||||
|
||||
return resMap
|
||||
}
|
||||
|
||||
// GetByID returns the response object of the given id, nil if it does not exist.
|
||||
func (res RPCResponses) GetByID(id int) *RPCResponse {
|
||||
for _, r := range res {
|
||||
if r.ID == id {
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasError returns true if one of the response objects has Error field != nil
|
||||
func (res RPCResponses) HasError() bool {
|
||||
for _, res := range res {
|
||||
if res.Error != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RPCRequests is of type []*RPCRequest.
|
||||
// This type is used to provide helper functions on the request list
|
||||
type RPCRequests []*RPCRequest
|
||||
|
||||
// NewClient returns a new RPCClient instance with default configuration.
|
||||
//
|
||||
// endpoint: JSON-RPC service URL to which JSON-RPC requests are sent.
|
||||
func NewClient(endpoint string) RPCClient {
|
||||
return NewClientWithOpts(endpoint, nil)
|
||||
}
|
||||
|
||||
// NewClientWithOpts returns a new RPCClient instance with custom configuration.
|
||||
//
|
||||
// endpoint: JSON-RPC service URL to which JSON-RPC requests are sent.
|
||||
//
|
||||
// opts: RPCClientOpts provide custom configuration
|
||||
func NewClientWithOpts(endpoint string, opts *RPCClientOpts) RPCClient {
|
||||
rpcClient := &rpcClient{
|
||||
endpoint: endpoint,
|
||||
httpClient: &http.Client{},
|
||||
customHeaders: make(map[string]string),
|
||||
}
|
||||
|
||||
if opts == nil {
|
||||
return rpcClient
|
||||
}
|
||||
|
||||
if opts.HTTPClient != nil {
|
||||
rpcClient.httpClient = opts.HTTPClient
|
||||
}
|
||||
|
||||
if opts.CustomHeaders != nil {
|
||||
for k, v := range opts.CustomHeaders {
|
||||
rpcClient.customHeaders[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return rpcClient
|
||||
}
|
||||
|
||||
func (client *rpcClient) Call(ctx context.Context, method string, params ...interface{}) (*RPCResponse, error) {
|
||||
|
||||
request := &RPCRequest{
|
||||
Method: method,
|
||||
Params: Params(params...),
|
||||
JSONRPC: jsonrpcVersion,
|
||||
}
|
||||
|
||||
return client.doCall(ctx, request)
|
||||
}
|
||||
|
||||
func (client *rpcClient) CallForInto(
|
||||
ctx context.Context,
|
||||
out interface{},
|
||||
method string,
|
||||
params []interface{},
|
||||
) error {
|
||||
|
||||
request := &RPCRequest{
|
||||
Method: method,
|
||||
JSONRPC: jsonrpcVersion,
|
||||
}
|
||||
|
||||
if params != nil {
|
||||
request.Params = params
|
||||
}
|
||||
|
||||
rpcResponse, err := client.doCall(ctx, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rpcResponse.Error != nil {
|
||||
return rpcResponse.Error
|
||||
}
|
||||
|
||||
return rpcResponse.GetObject(out)
|
||||
}
|
||||
|
||||
func (client *rpcClient) CallRaw(ctx context.Context, request *RPCRequest) (*RPCResponse, error) {
|
||||
|
||||
return client.doCall(ctx, request)
|
||||
}
|
||||
|
||||
func (client *rpcClient) CallFor(ctx context.Context, out interface{}, method string, params ...interface{}) error {
|
||||
rpcResponse, err := client.Call(ctx, method, params...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rpcResponse.Error != nil {
|
||||
return rpcResponse.Error
|
||||
}
|
||||
|
||||
return rpcResponse.GetObject(out)
|
||||
}
|
||||
|
||||
func (client *rpcClient) CallBatch(ctx context.Context, requests RPCRequests) (RPCResponses, error) {
|
||||
if len(requests) == 0 {
|
||||
return nil, errors.New("empty request list")
|
||||
}
|
||||
|
||||
for i, req := range requests {
|
||||
req.ID = i
|
||||
req.JSONRPC = jsonrpcVersion
|
||||
}
|
||||
|
||||
return client.doBatchCall(ctx, requests)
|
||||
}
|
||||
|
||||
func (client *rpcClient) CallBatchRaw(ctx context.Context, requests RPCRequests) (RPCResponses, error) {
|
||||
if len(requests) == 0 {
|
||||
return nil, errors.New("empty request list")
|
||||
}
|
||||
|
||||
return client.doBatchCall(ctx, requests)
|
||||
}
|
||||
|
||||
func (client *rpcClient) newRequest(ctx context.Context, req interface{}) (*http.Request, error) {
|
||||
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, "POST", client.endpoint, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
request.Header.Set("Accept", "application/json")
|
||||
|
||||
// set default headers first, so that even content type and accept can be overwritten
|
||||
for k, v := range client.customHeaders {
|
||||
request.Header.Set(k, v)
|
||||
}
|
||||
|
||||
return request, nil
|
||||
}
|
||||
|
||||
func (client *rpcClient) doCall(ctx context.Context, RPCRequest *RPCRequest) (*RPCResponse, error) {
|
||||
|
||||
httpRequest, err := client.newRequest(ctx, RPCRequest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("rpc call %v() on %v: %v", RPCRequest.Method, httpRequest.URL.String(), err.Error())
|
||||
}
|
||||
httpResponse, err := client.httpClient.Do(httpRequest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("rpc call %v() on %v: %v", RPCRequest.Method, httpRequest.URL.String(), err.Error())
|
||||
}
|
||||
defer httpResponse.Body.Close()
|
||||
|
||||
var rpcResponse *RPCResponse
|
||||
decoder := json.NewDecoder(httpResponse.Body)
|
||||
decoder.DisallowUnknownFields()
|
||||
decoder.UseNumber()
|
||||
err = decoder.Decode(&rpcResponse)
|
||||
|
||||
// parsing error
|
||||
if err != nil {
|
||||
// if we have some http error, return it
|
||||
if httpResponse.StatusCode >= 400 {
|
||||
return nil, &HTTPError{
|
||||
Code: httpResponse.StatusCode,
|
||||
err: fmt.Errorf("rpc call %v() on %v status code: %v. could not decode body to rpc response: %v", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode, err.Error()),
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("rpc call %v() on %v status code: %v. could not decode body to rpc response: %v", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode, err.Error())
|
||||
}
|
||||
|
||||
// response body empty
|
||||
if rpcResponse == nil {
|
||||
// if we have some http error, return it
|
||||
if httpResponse.StatusCode >= 400 {
|
||||
return nil, &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 nil, fmt.Errorf("rpc call %v() on %v status code: %v. rpc response missing", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode)
|
||||
}
|
||||
|
||||
return rpcResponse, nil
|
||||
}
|
||||
|
||||
func (client *rpcClient) doBatchCall(ctx context.Context, rpcRequest []*RPCRequest) ([]*RPCResponse, error) {
|
||||
httpRequest, err := client.newRequest(ctx, rpcRequest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("rpc batch call on %v: %v", httpRequest.URL.String(), err.Error())
|
||||
}
|
||||
httpResponse, err := client.httpClient.Do(httpRequest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("rpc batch call on %v: %v", httpRequest.URL.String(), err.Error())
|
||||
}
|
||||
defer httpResponse.Body.Close()
|
||||
|
||||
var rpcResponse RPCResponses
|
||||
decoder := json.NewDecoder(httpResponse.Body)
|
||||
decoder.DisallowUnknownFields()
|
||||
decoder.UseNumber()
|
||||
err = decoder.Decode(&rpcResponse)
|
||||
|
||||
// parsing error
|
||||
if err != nil {
|
||||
// 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: %v", httpRequest.URL.String(), httpResponse.StatusCode, err.Error()),
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("rpc batch call on %v status code: %v. could not decode body to rpc response: %v", httpRequest.URL.String(), httpResponse.StatusCode, err.Error())
|
||||
}
|
||||
|
||||
// response body empty
|
||||
if rpcResponse == nil || len(rpcResponse) == 0 {
|
||||
// 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)
|
||||
}
|
||||
|
||||
return rpcResponse, nil
|
||||
}
|
||||
|
||||
// Params is a helper function that uses the same parameter syntax as Call().
|
||||
// But you should consider to always use NewRequest() instead.
|
||||
//
|
||||
// e.g. to manually create an RPCRequest object:
|
||||
// request := &RPCRequest{
|
||||
// Method: "myMethod",
|
||||
// Params: Params("Alex", 35, true),
|
||||
// }
|
||||
//
|
||||
// same with new request:
|
||||
// request := NewRequest("myMethod", "Alex", 35, true)
|
||||
//
|
||||
// If you know what you are doing you can omit the Params() call but potentially create incorrect rpc requests:
|
||||
//request := &RPCRequest{
|
||||
// Method: "myMethod",
|
||||
// Params: 2, <-- invalid since a single primitive value must be wrapped in an array --> no magic without Params()
|
||||
// }
|
||||
//
|
||||
// correct:
|
||||
// request := &RPCRequest{
|
||||
// Method: "myMethod",
|
||||
// Params: []int{2}, <-- invalid since a single primitive value must be wrapped in an array
|
||||
// }
|
||||
func Params(params ...interface{}) interface{} {
|
||||
var finalParams interface{}
|
||||
|
||||
// if params was nil skip this and p stays nil
|
||||
if params != nil {
|
||||
switch len(params) {
|
||||
case 0: // no parameters were provided, do nothing so finalParam is nil and will be omitted
|
||||
case 1: // one param was provided, use it directly as is, or wrap primitive types in array
|
||||
if params[0] != nil {
|
||||
var typeOf reflect.Type
|
||||
|
||||
// traverse until nil or not a pointer type
|
||||
for typeOf = reflect.TypeOf(params[0]); typeOf != nil && typeOf.Kind() == reflect.Ptr; typeOf = typeOf.Elem() {
|
||||
}
|
||||
|
||||
if typeOf != nil {
|
||||
// now check if we can directly marshal the type or if it must be wrapped in an array
|
||||
switch typeOf.Kind() {
|
||||
// for these types we just do nothing, since value of p is already unwrapped from the array params
|
||||
case reflect.Struct:
|
||||
finalParams = params[0]
|
||||
case reflect.Array:
|
||||
finalParams = params[0]
|
||||
case reflect.Slice:
|
||||
finalParams = params[0]
|
||||
case reflect.Interface:
|
||||
finalParams = params[0]
|
||||
case reflect.Map:
|
||||
finalParams = params[0]
|
||||
default: // everything else must stay in an array (int, string, etc)
|
||||
finalParams = params
|
||||
}
|
||||
}
|
||||
} else {
|
||||
finalParams = params
|
||||
}
|
||||
default: // if more than one parameter was provided it should be treated as an array
|
||||
finalParams = params
|
||||
}
|
||||
}
|
||||
|
||||
return finalParams
|
||||
}
|
||||
|
||||
// GetInt converts the rpc response to an int64 and returns it.
|
||||
//
|
||||
// If result was not an integer an error is returned.
|
||||
func (RPCResponse *RPCResponse) GetInt() (int64, error) {
|
||||
val, ok := RPCResponse.Result.(json.Number)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("could not parse int64 from %s", RPCResponse.Result)
|
||||
}
|
||||
|
||||
i, err := val.Int64()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// GetFloat converts the rpc response to float64 and returns it.
|
||||
//
|
||||
// If result was not an float64 an error is returned.
|
||||
func (RPCResponse *RPCResponse) GetFloat() (float64, error) {
|
||||
val, ok := RPCResponse.Result.(json.Number)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("could not parse float64 from %s", RPCResponse.Result)
|
||||
}
|
||||
|
||||
f, err := val.Float64()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// GetBool converts the rpc response to a bool and returns it.
|
||||
//
|
||||
// If result was not a bool an error is returned.
|
||||
func (RPCResponse *RPCResponse) GetBool() (bool, error) {
|
||||
val, ok := RPCResponse.Result.(bool)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("could not parse bool from %s", RPCResponse.Result)
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// GetString converts the rpc response to a string and returns it.
|
||||
//
|
||||
// If result was not a string an error is returned.
|
||||
func (RPCResponse *RPCResponse) GetString() (string, error) {
|
||||
val, ok := RPCResponse.Result.(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("could not parse string from %s", RPCResponse.Result)
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// GetObject converts the rpc response to an arbitrary type.
|
||||
//
|
||||
// The function works as you would expect it from json.Unmarshal()
|
||||
func (RPCResponse *RPCResponse) GetObject(toType interface{}) error {
|
||||
js, err := json.Marshal(RPCResponse.Result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(js, toType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -8,6 +8,6 @@ import (
|
|||
// has information about in its ledger. This value may increase
|
||||
// over time if the node is configured to purge older ledger data.
|
||||
func (cl *Client) MinimumLedgerSlot(ctx context.Context) (out uint64, err error) {
|
||||
err = cl.rpcClient.CallFor(&out, "minimumLedgerSlot")
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "minimumLedgerSlot", nil)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -36,6 +36,6 @@ func (cl *Client) RequestAirdrop(
|
|||
M{"commitment": commitment},
|
||||
)
|
||||
}
|
||||
err = cl.rpcClient.CallFor(&signature, "requestAirdrop", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &signature, "requestAirdrop", params)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -3,8 +3,27 @@ package rpc
|
|||
// See more: https://docs.solana.com/cluster/rpc-endpoints
|
||||
|
||||
const (
|
||||
EndpointDevNet = "https://api.devnet.solana.com"
|
||||
EndpointTestNet = "https://api.testnet.solana.com"
|
||||
EndpointMainNetBeta = "https://api.mainnet-beta.solana.com"
|
||||
EndpointMainNetBetaSerum = "https://solana-api.projectserum.com"
|
||||
protocolHTTPS = "https://"
|
||||
protocolWSS = "wss://"
|
||||
)
|
||||
|
||||
const (
|
||||
hostDevNet = "api.devnet.solana.com"
|
||||
hostTestNet = "api.testnet.solana.com"
|
||||
hostMainNetBeta = "api.mainnet-beta.solana.com"
|
||||
hostMainNetBetaSerum = "solana-api.projectserum.com"
|
||||
)
|
||||
|
||||
const (
|
||||
EndpointRPCDevNet = protocolHTTPS + "api.devnet.solana.com"
|
||||
EndpointRPCTestNet = protocolHTTPS + "api.testnet.solana.com"
|
||||
EndpointRPCMainNetBeta = protocolHTTPS + "api.mainnet-beta.solana.com"
|
||||
EndpointRPCMainNetBetaSerum = protocolHTTPS + "solana-api.projectserum.com"
|
||||
)
|
||||
|
||||
const (
|
||||
EndpointWSDevNet = protocolWSS + "api.devnet.solana.com"
|
||||
EndpointWSTestNet = protocolWSS + "api.testnet.solana.com"
|
||||
EndpointWSMainNetBeta = protocolWSS + "api.mainnet-beta.solana.com"
|
||||
EndpointWSMainNetBetaSerum = protocolWSS + "solana-api.projectserum.com"
|
||||
)
|
||||
|
|
|
@ -92,6 +92,6 @@ func (cl *Client) SendTransactionWithOpts(
|
|||
obj,
|
||||
}
|
||||
|
||||
err = cl.rpcClient.CallFor(&signature, "sendTransaction", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &signature, "sendTransaction", params)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -120,6 +120,6 @@ func (cl *Client) SimulateTransactionWithOpts(
|
|||
obj,
|
||||
}
|
||||
|
||||
err = cl.rpcClient.CallFor(&out, "simulateTransaction", params)
|
||||
err = cl.rpcClient.CallForInto(ctx, &out, "simulateTransaction", params)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -40,9 +40,10 @@ type Client struct {
|
|||
reconnectOnErr bool
|
||||
}
|
||||
|
||||
func Dial(ctx context.Context, rpcURL string) (c *Client, err error) {
|
||||
// Connect creates a new websocket client connecting to the provided endpoint.
|
||||
func Connect(ctx context.Context, rpcEndpoint string) (c *Client, err error) {
|
||||
c = &Client{
|
||||
rpcURL: rpcURL,
|
||||
rpcURL: rpcEndpoint,
|
||||
subscriptionByRequestID: map[uint64]*Subscription{},
|
||||
subscriptionByWSSubID: map[uint64]*Subscription{},
|
||||
}
|
||||
|
@ -53,7 +54,7 @@ func Dial(ctx context.Context, rpcURL string) (c *Client, err error) {
|
|||
EnableCompression: true,
|
||||
}
|
||||
|
||||
c.conn, _, err = dialer.DialContext(ctx, rpcURL, nil)
|
||||
c.conn, _, err = dialer.DialContext(ctx, rpcEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new ws client: dial: %w", err)
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ func Test_AccountSubscribe(t *testing.T) {
|
|||
|
||||
zlog, _ = zap.NewDevelopment()
|
||||
|
||||
c, err := Dial(context.Background(), "ws://api.mainnet-beta.solana.com:80")
|
||||
c, err := Connect(context.Background(), "ws://api.mainnet-beta.solana.com:80")
|
||||
defer c.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -56,7 +56,7 @@ func Test_ProgramSubscribe(t *testing.T) {
|
|||
zlog, _ = zap.NewDevelopment()
|
||||
|
||||
fmt.Println("Dialing")
|
||||
c, err := Dial(context.Background(), "wss://solana-api.projectserum.com")
|
||||
c, err := Connect(context.Background(), "wss://solana-api.projectserum.com")
|
||||
fmt.Println("Hello?")
|
||||
defer c.Close()
|
||||
require.NoError(t, err)
|
||||
|
@ -80,7 +80,7 @@ func Test_SlotSubscribe(t *testing.T) {
|
|||
|
||||
zlog, _ = zap.NewDevelopment()
|
||||
|
||||
c, err := Dial(context.Background(), "ws://api.mainnet-beta.solana.com:80")
|
||||
c, err := Connect(context.Background(), "ws://api.mainnet-beta.solana.com:80")
|
||||
defer c.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
|
|
Loading…
Reference in New Issue