2

对接腾讯云未集成到SDK的接口开发实践小记

 1 month ago
source link: https://wiki.eryajf.net/pages/2fb0c2/#%E5%A6%82%E4%BD%95%E5%BF%AB%E9%80%9F%E6%89%BE%E5%88%B0%E6%8E%A5%E5%8F%A3
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

对接腾讯云未集成到SDK的接口开发实践小记

文章发布较早,内容可能过时,阅读注意甄别。

# 前言

日常工作中,与腾讯云打交道,为了提高效率,会调用它的接口来完成一些重复性工作。一般都是通过官方集成好的 SDK 来完成交互。

但有时候也存在一种情况,那就是这个接口还没有集成到 SDK 当中,又需要进行调用,此时可通过原生方式生成 curl 请求,以达到调用目的。

本文记录如何快速实现并完成这样一个流程,以此记录,便于后续再次查阅。

1710774996090.jpeg

# 认识原生请求方法

通常在每个产品的 API 接口文档页面,都会在调用方式里边有一个 签名方法 v3 的页面,在这个页面,介绍了如何自助生成签名,完成接口的调用。

这里以云服务器的签名方法 v3 (opens new window) 的文档介绍为例,来简单认识一下这一思路的用法。

我这里选择其中提供的 Golang 代码示例来做演示:

package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "os"
    "strings"
    "time"
)

func sha256hex(s string) string {
    b := sha256.Sum256([]byte(s))
    return hex.EncodeToString(b[:])
}

func hmacsha256(s, key string) string {
    hashed := hmac.New(sha256.New, []byte(key))
    hashed.Write([]byte(s))
    return string(hashed.Sum(nil))
}

func main() {
    // 需要设置环境变量 TENCENTCLOUD_SECRET_ID,值为示例的 AKIDz8krbsJ5yKBZQpn74WFkmLPx3*******
    secretId := os.Getenv("TENCENTCLOUD_SECRET_ID")
    // 需要设置环境变量 TENCENTCLOUD_SECRET_KEY,值为示例的 Gu5t9xGARNpq86cd98joQYCN3*******
    secretKey := os.Getenv("TENCENTCLOUD_SECRET_KEY")
    host := "cvm.tencentcloudapi.com"
    algorithm := "TC3-HMAC-SHA256"
    service := "cvm"
    version := "2017-03-12"
    action := "DescribeInstances"
    region := "ap-guangzhou"
    //var timestamp int64 = time.Now().Unix()
    var timestamp int64 = 1551113065

    // step 1: build canonical request string
    httpRequestMethod := "POST"
    canonicalURI := "/"
    canonicalQueryString := ""
    canonicalHeaders := fmt.Sprintf("content-type:%s\nhost:%s\nx-tc-action:%s\n",
        "application/json; charset=utf-8", host, strings.ToLower(action))
    signedHeaders := "content-type;host;x-tc-action"
    payload := `{"Limit": 1, "Filters": [{"Values": ["\u672a\u547d\u540d"], "Name": "instance-name"}]}`
    hashedRequestPayload := sha256hex(payload)
    canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s",
        httpRequestMethod,
        canonicalURI,
        canonicalQueryString,
        canonicalHeaders,
        signedHeaders,
        hashedRequestPayload)
    fmt.Println(canonicalRequest)

    // step 2: build string to sign
    date := time.Unix(timestamp, 0).UTC().Format("2006-01-02")
    credentialScope := fmt.Sprintf("%s/%s/tc3_request", date, service)
    hashedCanonicalRequest := sha256hex(canonicalRequest)
    string2sign := fmt.Sprintf("%s\n%d\n%s\n%s",
        algorithm,
        timestamp,
        credentialScope,
        hashedCanonicalRequest)
    fmt.Println(string2sign)

    // step 3: sign string
    secretDate := hmacsha256(date, "TC3"+secretKey)
    secretService := hmacsha256(service, secretDate)
    secretSigning := hmacsha256("tc3_request", secretService)
    signature := hex.EncodeToString([]byte(hmacsha256(string2sign, secretSigning)))
    fmt.Println(signature)

    // step 4: build authorization
    authorization := fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s",
        algorithm,
        secretId,
        credentialScope,
        signedHeaders,
        signature)
    fmt.Println(authorization)

    curl := fmt.Sprintf(`curl -X POST https://%s\
 -H "Authorization: %s"\
 -H "Content-Type: application/json; charset=utf-8"\
 -H "Host: %s" -H "X-TC-Action: %s"\
 -H "X-TC-Timestamp: %d"\
 -H "X-TC-Version: %s"\
 -H "X-TC-Region: %s"\
 -d '%s'`, host, authorization, host, action, timestamp, version, region, payload)
    fmt.Println(curl)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92

如上命令运行之后,最后会打印该 curl 请求如下:

$ curl -X POST https://cvm.tencentcloudapi.com\
 -H "Authorization: TC3-HMAC-SHA256 Credential=AKIDEG1YaM266rMcU6uZOzyhAhzRdZaRqMRG/2019-02-25/cvm/tc3_request, SignedHeaders=content-type;host;x-tc-action, Signature=ba8bf15056bcc7cd1c906c828ba52266b543816adf61a6df9af2e4da708fb790"\
 -H "Content-Type: application/json; charset=utf-8"\
 -H "Host: cvm.tencentcloudapi.com" -H "X-TC-Action: DescribeInstances"\
 -H "X-TC-Timestamp: 1551113065"\
 -H "X-TC-Version: 2017-03-12"\
 -H "X-TC-Region: ap-guangzhou"\
 -d '{"Limit": 1, "Filters": [{"Values": ["\u672a\u547d\u540d"], "Name": "instance-name"}]}'
1
2
3
4
5
6
7
8

整个代码里边每个步骤的操作也都有注释,就不一一解释了。这里简单概述一下,如上代码,除去生成签名的步骤之外,还有五个部分,来完成此命令的渲染:

  • host: 指定要请求的域名地址。
  • service: 指定服务类型
  • version::指定接口版本
  • action::指定请求的动作,即对应的接口
  • region::指定资源的区域
  • payload::指定请求的参数

# 集成官方提供的方法

如上方法对应的代码,一大块儿都是签名的处理,猛地一看这些代码,可能也很懵,我一开始还想着如何简化抽象一下签名的方法,就在简化的过程中,一检索,发现,事实上官方的 SDK 也提供了简化的方案。

比如 golang 的 SDK,看官方 readme,有专门的介绍:结合 common client 自定义请求 (opens new window)

其中也提供了代码示例:

package main

import (
	"fmt"
	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
	tchttp "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http"
	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/regions"
	"log"
	"os"
)

// 目前只支持 签名v3+POST
func main() {
	credential := common.NewCredential(
		os.Getenv("TENCENTCLOUD_SECRET_ID"),
		os.Getenv("TENCENTCLOUD_SECRET_KEY"))
	cpf := profile.NewClientProfile()
	cpf.HttpProfile.Endpoint = "cvm.tencentcloudapi.com"
	cpf.HttpProfile.ReqMethod = "POST"
	//创建common client
	client := common.NewCommonClient(credential, regions.Guangzhou, cpf).WithLogger(log.Default())

	// 创建common request,依次传入产品名、产品版本、接口名称
	request := tchttp.NewCommonRequest("cvm", "2017-03-12", "DescribeZones")

	// 自定义请求参数:
	// SetActionParameters 函数支持三种数据类型的入参:
	// 1. map[string]interface{}
	// body:=map[string]interface{}{
	// "InstanceId":"crs-xxx",
	// "SpanType":2,
	// }
	body := map[string]interface{}{}

	// // 2. string
	// bodyStr := `{}`

	// // 3. []byte
	// bodyBytes := []byte(bodyStr)

	// set custom headers
	request.SetHeader(map[string]string{
		"X-TC-TraceId": "ffe0c072-8a5d-4e17-8887-a8a60252abca",
	})

	// 设置action所需的请求数据
	err := request.SetActionParameters(body)
	if err != nil {
		return
	}

	//创建common response
	response := tchttp.NewCommonResponse()

	//发送请求
	err = client.Send(request, response)
	if err != nil {
		fmt.Printf("fail to invoke api: %v \n", err)
	}

	// 获取响应结果
	fmt.Println(string(response.GetBody()))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

借助于官方封装好的 client,就可以直接拼装接口,完成调用了。

这里贴一个自己封装的,将 MySQL 实例设置为只读的方法,以供参考:

// SetCdbReadOnly 设置cdb实例只读 0:只读 1:读写
func SetCdbReadOnly(credential *common.Credential, instanceid string, readonly int) (*tchttp.CommonResponse, error) {
	cpf := profile.NewClientProfile()
	cpf.HttpProfile.Endpoint = "cdb.tencentcloudapi.com"
	client := common.NewCommonClient(credential, regions.Shanghai, cpf).WithLogger(log.Default())

	request := tchttp.NewCommonRequest("cdb", "2017-03-20", "ModifyDBInstanceReadOnlyStatus")
	body := map[string]interface{}{
		"InstanceId": instanceid,
		"ReadOnly":   readonly,
	}
	err := request.SetActionParameters(body)
	if err != nil {
		return nil, err
	}
	response := tchttp.NewCommonResponse()
	err = client.Send(request, response)
	if err != nil {
		return nil, err
	}

	return response, nil
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

如此一来,要想自定义封装一些尚未集成到 SDK 的方法,就变得比较简单了。

# 如何快速找到接口

很多时候,一些相对偏门的接口,可能第一步困难的是如何找到这个接口相关的文档说明,这里也记录一下我个人的一些心得。

方法一:

通过自己的渠道,直接找到腾讯云官方的人员,咨询对应的接口,这是最高效也是最可靠的方式。

方法二:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK