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 }) }