mirror of
https://github.com/certimate-go/certimate.git
synced 2026-10-30 18:29:52 +08:00
refactor(provider): re-implement deployment provider of volcengine and byteplus tos with custom sdk, to lightweight package size
This commit is contained in:
@@ -77,7 +77,6 @@ require (
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.3.112
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.3.111
|
||||
github.com/ucloud/ucloud-sdk-go v0.22.77
|
||||
github.com/volcengine/ve-tos-golang-sdk/v2 v2.9.5
|
||||
github.com/volcengine/volc-sdk-golang v1.0.250
|
||||
github.com/volcengine/volcengine-go-sdk v1.2.35
|
||||
github.com/wneessen/go-mail v0.7.3
|
||||
|
||||
@@ -849,7 +849,6 @@ github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
@@ -933,8 +932,6 @@ github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqri
|
||||
github.com/ucloud/ucloud-sdk-go v0.22.77 h1:5f54RZhVm4lgQ5RbKnzbk2Ck79XBn/cYRJbZ3zsNogQ=
|
||||
github.com/ucloud/ucloud-sdk-go v0.22.77/go.mod h1:dyLmFHmUfgb4RZKYQP9IArlvQ2pxzFthfhwxRzOEPIw=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/volcengine/ve-tos-golang-sdk/v2 v2.9.5 h1:6f6C0aTlnPiCH+KRKknf8vxbqqn0aA0J9WhO5mArFfg=
|
||||
github.com/volcengine/ve-tos-golang-sdk/v2 v2.9.5/go.mod h1:IrjK84IJJTuOZOTMv/P18Ydjy/x+ow7fF7q11jAxXLM=
|
||||
github.com/volcengine/volc-sdk-golang v1.0.23/go.mod h1:AfG/PZRUkHJ9inETvbjNifTDgut25Wbkm2QoYBTbvyU=
|
||||
github.com/volcengine/volc-sdk-golang v1.0.250 h1:2/RLIahTgZ57OkNgDV0FpAkvk/4QFMGIuVFTnU26GDs=
|
||||
github.com/volcengine/volc-sdk-golang v1.0.250/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM=
|
||||
@@ -1235,7 +1232,6 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
||||
@@ -4,11 +4,11 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/volcengine/ve-tos-golang-sdk/v2/tos"
|
||||
"net/url"
|
||||
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
cmgrimpl "github.com/certimate-go/certimate/pkg/core/certmgr/providers/byteplus-certcenter"
|
||||
tossdk "github.com/certimate-go/certimate/pkg/sdk3rd/volcengine/tos"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -34,7 +34,7 @@ type DeployerConfig struct {
|
||||
type Deployer struct {
|
||||
config *DeployerConfig
|
||||
logger *slog.Logger
|
||||
sdkClient *tos.ClientV2
|
||||
sdkClient *tossdk.Client
|
||||
sdkCertmgr core.Certmgr
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func NewDeployer(config *DeployerConfig) (*Deployer, error) {
|
||||
return nil, fmt.Errorf("the configuration of the deployer provider is nil")
|
||||
}
|
||||
|
||||
client, err := createSDKClient(config.AccessKeyId, config.SecretAccessKey, config.Region)
|
||||
client, err := createSDKClient(config.AccessKeyId, config.SecretAccessKey, config.Region, config.Bucket)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create client: %w", err)
|
||||
}
|
||||
@@ -96,16 +96,14 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep
|
||||
|
||||
// 设置自定义域名
|
||||
// REF: https://docs.byteplus.com/en/docs/tos/Putbucketcustomdomain
|
||||
// REF: https://docs.byteplus.com/en/docs/tos/Manage-custom-domain-names-go-sdk
|
||||
putBucketCustomDomainReq := &tos.PutBucketCustomDomainInput{
|
||||
Bucket: d.config.Bucket,
|
||||
Rule: tos.CustomDomainRule{
|
||||
putBucketCustomDomainReq := &tossdk.PutBucketCustomDomainRequest{
|
||||
CustomDomainRule: &tossdk.PutBucketCustomDomainRequestCustomDomainRule{
|
||||
Domain: d.config.Domain,
|
||||
CertID: upres.CertId,
|
||||
CertId: upres.CertId,
|
||||
},
|
||||
}
|
||||
putBucketCustomDomainResp, err := d.sdkClient.PutBucketCustomDomain(ctx, putBucketCustomDomainReq)
|
||||
d.logger.Debug("sdk request 'tos.PutBucketCustomDomain'", slog.Any("request", putBucketCustomDomainReq), slog.Any("response", putBucketCustomDomainResp))
|
||||
putBucketCustomDomainResp, err := d.sdkClient.PutBucketCustomDomainWithContext(ctx, putBucketCustomDomainReq)
|
||||
d.logger.Debug("sdk request 'tos.PutBucketCustomDomain'", slog.String("params.bucket", d.config.Bucket), slog.Any("request", putBucketCustomDomainReq), slog.Any("response", putBucketCustomDomainResp))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute sdk request 'tos.PutBucketCustomDomain': %w", err)
|
||||
}
|
||||
@@ -113,13 +111,13 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep
|
||||
return &DeployResult{}, nil
|
||||
}
|
||||
|
||||
func createSDKClient(accessKeyId, secretAccessKey, region string) (*tos.ClientV2, error) {
|
||||
endpoint := fmt.Sprintf("tos-%s.bytepluses.com", region)
|
||||
func createSDKClient(accessKeyId, secretAccessKey, region, bucket string) (*tossdk.Client, error) {
|
||||
endpoint := fmt.Sprintf("https://%s.tos-%s.bytepluses.com", url.PathEscape(bucket), url.PathEscape(region))
|
||||
|
||||
client, err := tos.NewClientV2(
|
||||
endpoint,
|
||||
tos.WithRegion(region),
|
||||
tos.WithCredentials(tos.NewStaticCredentials(accessKeyId, secretAccessKey)),
|
||||
client, err := tossdk.NewClient(endpoint,
|
||||
tossdk.WithAkSk(accessKeyId, secretAccessKey),
|
||||
tossdk.WithRegion(region),
|
||||
tossdk.WithBucket(bucket),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -5,10 +5,9 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/volcengine/ve-tos-golang-sdk/v2/tos"
|
||||
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
cmgrimpl "github.com/certimate-go/certimate/pkg/core/certmgr/providers/volcengine-certcenter"
|
||||
tossdk "github.com/certimate-go/certimate/pkg/sdk3rd/volcengine/tos"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -34,7 +33,7 @@ type DeployerConfig struct {
|
||||
type Deployer struct {
|
||||
config *DeployerConfig
|
||||
logger *slog.Logger
|
||||
sdkClient *tos.ClientV2
|
||||
sdkClient *tossdk.Client
|
||||
sdkCertmgr core.Certmgr
|
||||
}
|
||||
|
||||
@@ -45,7 +44,7 @@ func NewDeployer(config *DeployerConfig) (*Deployer, error) {
|
||||
return nil, fmt.Errorf("the configuration of the deployer provider is nil")
|
||||
}
|
||||
|
||||
client, err := createSDKClient(config.AccessKeyId, config.SecretAccessKey, config.Region)
|
||||
client, err := createSDKClient(config.AccessKeyId, config.SecretAccessKey, config.Region, config.Bucket)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create client: %w", err)
|
||||
}
|
||||
@@ -96,15 +95,14 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep
|
||||
|
||||
// 设置自定义域名
|
||||
// REF: https://www.volcengine.com/docs/6349/764779
|
||||
putBucketCustomDomainReq := &tos.PutBucketCustomDomainInput{
|
||||
Bucket: d.config.Bucket,
|
||||
Rule: tos.CustomDomainRule{
|
||||
putBucketCustomDomainReq := &tossdk.PutBucketCustomDomainRequest{
|
||||
CustomDomainRule: &tossdk.PutBucketCustomDomainRequestCustomDomainRule{
|
||||
Domain: d.config.Domain,
|
||||
CertID: upres.CertId,
|
||||
CertId: upres.CertId,
|
||||
},
|
||||
}
|
||||
putBucketCustomDomainResp, err := d.sdkClient.PutBucketCustomDomain(ctx, putBucketCustomDomainReq)
|
||||
d.logger.Debug("sdk request 'tos.PutBucketCustomDomain'", slog.Any("request", putBucketCustomDomainReq), slog.Any("response", putBucketCustomDomainResp))
|
||||
putBucketCustomDomainResp, err := d.sdkClient.PutBucketCustomDomainWithContext(ctx, putBucketCustomDomainReq)
|
||||
d.logger.Debug("sdk request 'tos.PutBucketCustomDomain'", slog.String("params.bucket", d.config.Bucket), slog.Any("request", putBucketCustomDomainReq), slog.Any("response", putBucketCustomDomainResp))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute sdk request 'tos.PutBucketCustomDomain': %w", err)
|
||||
}
|
||||
@@ -112,13 +110,11 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep
|
||||
return &DeployResult{}, nil
|
||||
}
|
||||
|
||||
func createSDKClient(accessKeyId, secretAccessKey, region string) (*tos.ClientV2, error) {
|
||||
endpoint := fmt.Sprintf("tos-%s.volces.com", region)
|
||||
|
||||
client, err := tos.NewClientV2(
|
||||
endpoint,
|
||||
tos.WithRegion(region),
|
||||
tos.WithCredentials(tos.NewStaticCredentials(accessKeyId, secretAccessKey)),
|
||||
func createSDKClient(accessKeyId, secretAccessKey, region, bucket string) (*tossdk.Client, error) {
|
||||
client, err := tossdk.NewClient("",
|
||||
tossdk.WithAkSk(accessKeyId, secretAccessKey),
|
||||
tossdk.WithRegion(region),
|
||||
tossdk.WithBucket(bucket),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -73,14 +74,13 @@ func NewClient(baseUrl string, optFns ...OptionsFunc) (*Client, error) {
|
||||
payloadHash := sha256.Sum256([]byte(payloadStr))
|
||||
payloadHashHex := hex.EncodeToString(payloadHash[:])
|
||||
|
||||
h := hmac.New(sha256.New, []byte(opts.SecretAccessKey))
|
||||
var h hash.Hash
|
||||
h = hmac.New(sha256.New, []byte(opts.SecretAccessKey))
|
||||
h.Write([]byte(eopDate))
|
||||
kTime := h.Sum(nil)
|
||||
|
||||
h = hmac.New(sha256.New, kTime)
|
||||
h.Write([]byte(opts.AccessKeyId))
|
||||
kAk := h.Sum(nil)
|
||||
|
||||
h = hmac.New(sha256.New, kAk)
|
||||
h.Write([]byte(now.Format("20060102")))
|
||||
kDate := h.Sum(nil)
|
||||
|
||||
@@ -154,6 +154,7 @@ func (c *Client) newRequest(method string, path string, params any) (*resty.Requ
|
||||
payloadStr := ""
|
||||
contentType := ""
|
||||
if params != nil {
|
||||
// 目前仅支持 XML 请求体,仅适用于非 S3 兼容接口
|
||||
payloadb, err := xml.Marshal(params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -36,7 +36,6 @@ func NewClient(optFns ...OptionsFunc) (*Client, error) {
|
||||
SetBaseURL("http://api.routewize.com").
|
||||
SetHeader("Accept", "application/json").
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetHeader("Host", "api.routewize.com").
|
||||
SetHeader("User-Agent", app.AppUserAgent).
|
||||
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
|
||||
// 签名机制:
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package tos
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type PutBucketCustomDomainRequest struct {
|
||||
CustomDomainRule *PutBucketCustomDomainRequestCustomDomainRule `json:",omitempty"`
|
||||
}
|
||||
|
||||
type PutBucketCustomDomainRequestCustomDomainRule struct {
|
||||
CertId string `json:",omitempty"`
|
||||
Domain string `json:",omitempty"`
|
||||
Protocol string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type PutBucketCustomDomainResponse struct {
|
||||
sdkResponseBase
|
||||
}
|
||||
|
||||
func (c *Client) PutBucketCustomDomain(req *PutBucketCustomDomainRequest) (*PutBucketCustomDomainResponse, error) {
|
||||
return c.PutBucketCustomDomainWithContext(context.Background(), req)
|
||||
}
|
||||
|
||||
func (c *Client) PutBucketCustomDomainWithContext(ctx context.Context, req *PutBucketCustomDomainRequest) (*PutBucketCustomDomainResponse, error) {
|
||||
httpreq, err := c.newRequest(http.MethodPut, "/?customdomain", req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
result := &PutBucketCustomDomainResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
package tos
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/app"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
rc *resty.Client
|
||||
}
|
||||
|
||||
func NewClient(endpoint string, optFns ...OptionsFunc) (*Client, error) {
|
||||
opts := &Options{}
|
||||
for _, fn := range optFns {
|
||||
fn(opts)
|
||||
}
|
||||
|
||||
if opts.Region == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset region")
|
||||
}
|
||||
if opts.AccessKeyId == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset accessKeyId")
|
||||
}
|
||||
if opts.SecretAccessKey == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset secretAccessKey")
|
||||
}
|
||||
|
||||
if endpoint == "" {
|
||||
if opts.Region == "" {
|
||||
endpoint = fmt.Sprintf("https://tos-%s.volces.com", url.PathEscape(opts.Region))
|
||||
} else {
|
||||
endpoint = fmt.Sprintf("https://%s.tos-%s.volces.com", url.PathEscape(opts.Bucket), url.PathEscape(opts.Region))
|
||||
}
|
||||
} else {
|
||||
if baseUrl, err := url.Parse(endpoint); err != nil {
|
||||
return nil, fmt.Errorf("sdkerr: invalid endpoint: %w", err)
|
||||
} else if baseUrl.Scheme == "" {
|
||||
endpoint = "https://" + endpoint
|
||||
}
|
||||
}
|
||||
|
||||
restyClient := resty.New().
|
||||
SetBaseURL(endpoint).
|
||||
SetHeader("User-Agent", app.AppUserAgent).
|
||||
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
|
||||
// API 签名机制:
|
||||
// https://www.volcengine.com/docs/6349/74839
|
||||
// https://docs.byteplus.com/en/docs/tos/reference-signature-mechanism_1
|
||||
|
||||
method := strings.ToUpper(req.Method)
|
||||
|
||||
nowUtc := time.Now().UTC()
|
||||
headerDateStr := nowUtc.Format(http.TimeFormat)
|
||||
requestDateStr := nowUtc.Format("20060102T150405Z")
|
||||
credentialDateStr := nowUtc.Format("20060102")
|
||||
|
||||
canonicalUrl := req.URL.Path
|
||||
if canonicalUrl == "" {
|
||||
canonicalUrl = "/"
|
||||
}
|
||||
|
||||
canonicalQueryStr := ""
|
||||
if len(req.URL.Query()) > 0 {
|
||||
query := req.URL.Query()
|
||||
|
||||
keys := make([]string, 0, len(query))
|
||||
for key := range query {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for i, key := range keys {
|
||||
if i > 0 {
|
||||
canonicalQueryStr += "&"
|
||||
}
|
||||
|
||||
value := query.Get(key)
|
||||
canonicalQueryStr += fmt.Sprintf("%s=%s", url.QueryEscape(key), url.QueryEscape(value))
|
||||
}
|
||||
}
|
||||
|
||||
canonicalHeaders := ""
|
||||
signedHeaders := ""
|
||||
if len(req.Header) > 0 || req.Host != "" {
|
||||
if req.Header.Get("Host") == "" {
|
||||
req.Header.Set("Host", req.Host)
|
||||
}
|
||||
if req.Header.Get("X-TOS-Date") == "" {
|
||||
req.Header.Set("X-TOS-Date", requestDateStr)
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(req.Header))
|
||||
for key := range req.Header {
|
||||
key = strings.ToLower(key)
|
||||
if strings.HasPrefix(key, "x-tos-") || key == "host" {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
if key == "content-type" && req.Header.Get("X-TOS-Content-SHA256") != "" {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for i, key := range keys {
|
||||
if i > 0 {
|
||||
canonicalHeaders += "\n"
|
||||
signedHeaders += ";"
|
||||
}
|
||||
|
||||
value := strings.TrimSpace(req.Header.Get(key))
|
||||
canonicalHeaders += fmt.Sprintf("%s:%s", key, value)
|
||||
signedHeaders += key
|
||||
}
|
||||
|
||||
canonicalHeaders += "\n"
|
||||
}
|
||||
|
||||
payloadSha256Str := req.Header.Get("X-TOS-Content-SHA256")
|
||||
if payloadSha256Str == "" {
|
||||
payloadSha256 := sha256.Sum256([]byte{})
|
||||
payloadSha256Str = strings.ToLower(hex.EncodeToString(payloadSha256[:]))
|
||||
}
|
||||
|
||||
canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", method, canonicalUrl, canonicalQueryStr, canonicalHeaders, signedHeaders, payloadSha256Str)
|
||||
canonicalRequestHash := sha256.Sum256([]byte(canonicalRequest))
|
||||
canonicalRequestHashHex := strings.ToLower(hex.EncodeToString(canonicalRequestHash[:]))
|
||||
|
||||
const signAlgorithmHeader = "TOS4-HMAC-SHA256"
|
||||
credentialScope := fmt.Sprintf("%s/%s/tos/request", credentialDateStr, opts.Region)
|
||||
stringToSign := fmt.Sprintf("%s\n%s\n%s\n%s", signAlgorithmHeader, requestDateStr, credentialScope, canonicalRequestHashHex)
|
||||
|
||||
var h hash.Hash
|
||||
h = hmac.New(sha256.New, []byte(opts.SecretAccessKey))
|
||||
h.Write([]byte(credentialDateStr))
|
||||
kDate := h.Sum(nil)
|
||||
h = hmac.New(sha256.New, kDate)
|
||||
h.Write([]byte(opts.Region))
|
||||
kRegion := h.Sum(nil)
|
||||
h = hmac.New(sha256.New, kRegion)
|
||||
h.Write([]byte("tos"))
|
||||
kService := h.Sum(nil)
|
||||
h = hmac.New(sha256.New, kService)
|
||||
h.Write([]byte("request"))
|
||||
kSigning := h.Sum(nil)
|
||||
|
||||
h = hmac.New(sha256.New, kSigning)
|
||||
h.Write([]byte(stringToSign))
|
||||
signature := strings.ToLower(hex.EncodeToString(h.Sum(nil)))
|
||||
|
||||
req.Header.Set("Authorization", fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", signAlgorithmHeader, opts.AccessKeyId, credentialScope, signedHeaders, signature))
|
||||
req.Header.Set("Date", headerDateStr)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return &Client{rc: restyClient}, nil
|
||||
}
|
||||
|
||||
func (c *Client) SetTimeout(timeout time.Duration) *Client {
|
||||
c.rc.SetTimeout(timeout)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) newRequest(method string, path string, params any) (*resty.Request, error) {
|
||||
if method == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset method")
|
||||
}
|
||||
if path == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset path")
|
||||
}
|
||||
|
||||
requestUrl, err := url.Parse(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sdkerr: invalid path: %w", err)
|
||||
} else if requestUrl.IsAbs() {
|
||||
return nil, fmt.Errorf("sdkerr: path should be relative")
|
||||
}
|
||||
|
||||
payloadStr := ""
|
||||
contentType := ""
|
||||
if params != nil {
|
||||
// 目前仅支持 JSON 请求体,仅适用于非 S3 兼容接口
|
||||
payloadb, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payloadStr = string(payloadb)
|
||||
contentType = "application/json"
|
||||
}
|
||||
payloadMd5 := md5.Sum([]byte(payloadStr))
|
||||
payloadMd5Encoded := base64.StdEncoding.EncodeToString(payloadMd5[:])
|
||||
|
||||
req := c.rc.R()
|
||||
req.Method = method
|
||||
req.URL = requestUrl.Path
|
||||
req.QueryParam = requestUrl.Query()
|
||||
req.SetBody(payloadStr)
|
||||
req.SetHeader("Content-MD5", payloadMd5Encoded)
|
||||
req.SetHeader("Content-Type", contentType)
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (c *Client) doRequest(req *resty.Request) (*resty.Response, error) {
|
||||
if req == nil {
|
||||
return nil, fmt.Errorf("sdkerr: nil request")
|
||||
}
|
||||
|
||||
// WARN:
|
||||
// PLEASE DO NOT USE `req.SetBody` HERE! USE `newRequest` INSTEAD.
|
||||
// PLEASE DO NOT USE `req.SetResult` or `req.SetError` HERE! USE `doRequestWithResult` INSTEAD.
|
||||
|
||||
resp, err := req.Send()
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("sdkerr: failed to send request: %w", err)
|
||||
} else if resp.IsError() {
|
||||
return resp, fmt.Errorf("sdkerr: unexpected status code: %d (resp: %s)", resp.StatusCode(), resp.String())
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) doRequestWithResult(req *resty.Request, res sdkResponse) (*resty.Response, error) {
|
||||
if req == nil {
|
||||
return nil, fmt.Errorf("sdkerr: nil request")
|
||||
}
|
||||
|
||||
resp, err := c.doRequest(req)
|
||||
if err != nil {
|
||||
if resp != nil {
|
||||
json.Unmarshal(resp.Body(), &res)
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if len(resp.Body()) != 0 {
|
||||
if err := json.Unmarshal(resp.Body(), &res); err != nil {
|
||||
return resp, fmt.Errorf("sdkerr: failed to unmarshal response: %w (resp: %s)", err, resp.String())
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package tos
|
||||
|
||||
type Options struct {
|
||||
AccessKeyId string
|
||||
SecretAccessKey string
|
||||
Region string
|
||||
Bucket string
|
||||
}
|
||||
|
||||
type OptionsFunc func(*Options)
|
||||
|
||||
func WithAkSk(ak, sk string) OptionsFunc {
|
||||
return func(o *Options) {
|
||||
o.AccessKeyId = ak
|
||||
o.SecretAccessKey = sk
|
||||
}
|
||||
}
|
||||
|
||||
func WithRegion(region string) OptionsFunc {
|
||||
return func(o *Options) {
|
||||
o.Region = region
|
||||
}
|
||||
}
|
||||
|
||||
func WithBucket(bucket string) OptionsFunc {
|
||||
return func(o *Options) {
|
||||
o.Bucket = bucket
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package tos
|
||||
|
||||
type sdkResponse interface{}
|
||||
|
||||
type sdkResponseBase struct{}
|
||||
|
||||
var _ sdkResponse = (*sdkResponseBase)(nil)
|
||||
@@ -40,7 +40,6 @@ func NewClient(optFns ...OptionsFunc) (*Client, error) {
|
||||
SetBaseURL("https://open.chinanetcenter.com").
|
||||
SetHeader("Accept", "application/json").
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetHeader("Host", "open.chinanetcenter.com").
|
||||
SetHeader("User-Agent", app.AppUserAgent).
|
||||
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
|
||||
// API 签名机制:
|
||||
@@ -67,7 +66,7 @@ func NewClient(optFns ...OptionsFunc) (*Client, error) {
|
||||
|
||||
canonicalHeaders := "" +
|
||||
"content-type:" + strings.TrimSpace(strings.ToLower(req.Header.Get("Content-Type"))) + "\n" +
|
||||
"host:" + strings.TrimSpace(strings.ToLower(req.Header.Get("Host"))) + "\n"
|
||||
"host:" + strings.TrimSpace(strings.ToLower(req.Host)) + "\n"
|
||||
signedHeaders := "content-type;host"
|
||||
|
||||
payloadStr := ""
|
||||
@@ -106,11 +105,11 @@ func NewClient(optFns ...OptionsFunc) (*Client, error) {
|
||||
h.Write([]byte(stringToSign))
|
||||
signature := strings.ToLower(hex.EncodeToString(h.Sum(nil)))
|
||||
|
||||
req.Header.Set("X-CNC-AccessKey", options.AccessKey)
|
||||
req.Header.Set("X-CNC-Timestamp", timestampStr)
|
||||
req.Header.Set("X-CNC-Auth-Method", "AKSK")
|
||||
req.Header.Set("Authorization", fmt.Sprintf("%s Credential=%s, SignedHeaders=%s, Signature=%s", signAlgorithmHeader, options.AccessKey, signedHeaders, signature))
|
||||
req.Header.Set("Date", nowUtc.Format(http.TimeFormat))
|
||||
req.Header.Set("X-CNC-Auth-Method", "AKSK")
|
||||
req.Header.Set("X-CNC-AccessKey", options.AccessKey)
|
||||
req.Header.Set("X-CNC-Timestamp", timestampStr)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user