193 lines
3.6 KiB
Go
193 lines
3.6 KiB
Go
|
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
|
||
|
})
|
||
|
}
|