bdcontract-client/sdk-go/client/client.go

193 lines
3.6 KiB
Go
Raw Normal View History

2024-11-26 10:54:32 +00:00
package client
import (
"bytes"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"strings"
"time"
"github.com/tjfoc/gmsm/sm2"
"go.fusiongalaxy.cn/bdware/bdcontract-client/sm2util"
)
type HttpCOptions struct {
MaxRetries int
}
type Client struct {
baseUrl string
priv *sm2.PrivateKey
pub *sm2.PublicKey
pubHex string
httpc *http.Client
maxRetry int
}
func NewClient(
baseUrl string,
priv *sm2.PrivateKey,
pub *sm2.PublicKey,
opt HttpCOptions,
) (*Client, error) {
pubHex, err := sm2util.CheckSm2KeyPair(priv, pub)
if err != nil {
return nil, err
}
if opt.MaxRetries == 0 {
opt.MaxRetries = 3
}
return &Client{
baseUrl: baseUrl,
pub: pub,
pubHex: pubHex,
priv: priv,
httpc: &http.Client{Timeout: 10 * time.Second},
maxRetry: opt.MaxRetries,
}, nil
}
func (c *Client) RequestWithSignature(
path string,
method string,
body map[string]interface{},
priv *sm2.PrivateKey,
pub *sm2.PublicKey,
) (statusCode int, respBody io.ReadCloser, err error) {
var pubHex string
if priv != nil {
var err error
pubHex, err = sm2util.CheckSm2KeyPair(priv, pub)
if err != nil {
return 0, nil, err
}
} else {
priv = c.priv
pubHex = c.pubHex
}
rawUrl := c.baseUrl + path
u := fmt.Sprintf("%s%spubKey=%s",
rawUrl,
func() string {
if strings.Contains(path, "?") {
return "&"
}
return "?"
}(),
pubHex,
)
switch strings.ToUpper(method) {
case "POST":
body["sign"] = c.Sign(u[strings.Index(u, "?")+1:], priv)
bodyJson, err := json.Marshal(body)
if err != nil {
return 0, nil, err
}
resp, err := c.httpc.Post(rawUrl, "application/json", bytes.NewBuffer(bodyJson))
if err != nil {
return 0, nil, err
}
respBody = resp.Body
}
return statusCode, respBody, nil
}
func (c *Client) Sign(data string, priv *sm2.PrivateKey) string {
if priv == nil {
priv = c.priv
}
sig, err := priv.Sign(rand.Reader, []byte(data), nil)
if err != nil {
log.Fatal(err)
}
return hex.EncodeToString(sig)
}
func genUrlParamsFromObject(obj map[string]interface{}) string {
params := make([]string, 0)
for key, value := range obj {
params = append(params, fmt.Sprintf("%s=%v", key, value))
}
return strings.Join(params, "&")
}
func retry[T any](fn func() (int, T, error)) (int, T, error) {
var lastErr error
for attempt := 0; attempt < 3; attempt++ {
resp, statusCode, err := fn()
if err == nil {
return resp, statusCode, nil
}
lastErr = err
time.Sleep(100 * time.Millisecond * time.Duration(attempt+1))
}
var zero T
return 0, zero, lastErr
}
func (c *Client) Ping() (statusCode int, resp *PingResponse, err error) {
return retry(func() (statusCode int, resp *PingResponse, err error) {
statusCode, respBody, err := c.RequestWithSignature(
"/SCManager?action=ping",
"GET",
nil,
nil,
nil,
)
if err != nil {
return 0, nil, err
}
defer respBody.Close()
decoder := json.NewDecoder(respBody)
if err := decoder.Decode(&resp); err != nil {
return 0, nil, err
}
return statusCode, resp, nil
})
}
func (c *Client) StartContract(code string) (statusCode int, resp string, err error) {
params := url.Values{
"action": {"startContract"},
"script": {code},
}
path := fmt.Sprintf("/SCManager?%s", params.Encode())
return retry(func() (int, string, error) {
statusCode, respBody, err := c.RequestWithSignature(
path,
"GET",
nil,
nil,
nil,
)
if err != nil {
return 0, "", err
}
var resp string
defer respBody.Close()
buf := new(strings.Builder)
if _, err := io.Copy(buf, respBody); err != nil {
return 0, "", err
}
return statusCode, resp, nil
})
}