Skip to content

Commit e7dfd17

Browse files
authored
feat: support reverse proxy (#304)
1 parent 165faa5 commit e7dfd17

File tree

4 files changed

+187
-27
lines changed

4 files changed

+187
-27
lines changed

examples/cvm/v20170312/nginx.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
8+
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
9+
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/regions"
10+
cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312"
11+
)
12+
13+
func main() {
14+
/*
15+
本示例演示如何通过 **nginx 反向代理** 请求腾讯云 API。
16+
场景:某些网络环境不能直接访问腾讯云域名(*.tencentcloudapi.com),只能通过代理服务器。
17+
18+
========================
19+
1. nginx 配置示例:
20+
========================
21+
server {
22+
listen 80;
23+
# 指定 DNS 服务器(可以根据自己网络环境进行替换)
24+
resolver 114.114.114.114;
25+
26+
# 可以自定义请求路径
27+
location /tc_api {
28+
# http_host 后必须以 / 结尾
29+
proxy_pass https://$http_host/$is_args$args;
30+
}
31+
}
32+
*/
33+
credential := common.NewCredential(
34+
os.Getenv("TENCENTCLOUD_SECRET_ID"),
35+
os.Getenv("TENCENTCLOUD_SECRET_KEY"),
36+
)
37+
38+
cpf := profile.NewClientProfile()
39+
// 2. 将 Scheme 设置为 http
40+
cpf.HttpProfile.Scheme = "http"
41+
42+
// 3. 替换 1.2.3.4 为真实的 nginx 地址, /tc_api 可以自定义
43+
nginx := "1.2.3.4/tc_api"
44+
cpf.HttpProfile.Endpoint = nginx
45+
46+
client, err := cvm.NewClient(credential, regions.Guangzhou, cpf)
47+
if err != nil {
48+
panic(err)
49+
}
50+
51+
request := cvm.NewDescribeInstancesRequest()
52+
request.Limit = common.Int64Ptr(10)
53+
// 4. 设置 header 为 {服务名}.tencentcloudapi.com
54+
request.SetHeader(map[string]string{
55+
"Host": "cvm.tencentcloudapi.com",
56+
})
57+
58+
response, err := client.DescribeInstances(request)
59+
if err != nil {
60+
panic(err)
61+
}
62+
fmt.Println(response.ToJsonString())
63+
}

tencentcloud/common/client.go

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,14 @@ func (c *Client) completeRequest(request tchttp.Request) {
102102
if domain == "" {
103103
domain = request.GetServiceDomain(request.GetService())
104104
}
105-
request.SetDomain(domain)
105+
pathIdx := strings.IndexByte(domain, '/')
106+
if pathIdx >= 0 {
107+
request.SetDomain(domain[:pathIdx])
108+
request.SetPath(domain[pathIdx:])
109+
} else {
110+
request.SetDomain(domain)
111+
request.SetPath("/")
112+
}
106113
}
107114

108115
if request.GetHttpMethod() == "" {
@@ -197,16 +204,6 @@ func (c *Client) sendWithoutSignature(request tchttp.Request, response tchttp.Re
197204
}
198205
}
199206

200-
for k, v := range request.GetHeader() {
201-
switch k {
202-
case "X-TC-Action", "X-TC-Version", "X-TC-Timestamp", "X-TC-RequestClient",
203-
"X-TC-Language", "Content-Type", "X-TC-Region", "X-TC-Token":
204-
c.logger.Printf("Skip header \"%s\": can not specify built-in header", k)
205-
default:
206-
headers[k] = v
207-
}
208-
}
209-
210207
if !isOctetStream && request.GetContentType() == octetStream {
211208
isOctetStream = true
212209
b, _ := json.Marshal(request)
@@ -269,7 +266,11 @@ func (c *Client) sendWithoutSignature(request tchttp.Request, response tchttp.Re
269266
}
270267
httpRequest = httpRequest.WithContext(request.GetContext())
271268
for k, v := range headers {
272-
httpRequest.Header[k] = []string{v}
269+
if strings.EqualFold(k, "Host") {
270+
httpRequest.Host = v
271+
} else {
272+
httpRequest.Header.Set(k, v)
273+
}
273274
}
274275
httpResponse, err := c.sendWithRateLimitRetry(httpRequest, isRetryable(request))
275276
if err != nil {
@@ -300,7 +301,11 @@ func (c *Client) sendWithSignatureV1(request tchttp.Request, response tchttp.Res
300301
}
301302

302303
for k, v := range request.GetHeader() {
303-
httpRequest.Header.Set(k, v)
304+
if strings.EqualFold(k, "Host") {
305+
httpRequest.Host = v
306+
} else {
307+
httpRequest.Header.Set(k, v)
308+
}
304309
}
305310

306311
httpResponse, err := c.sendWithRateLimitRetry(httpRequest, isRetryable(request))
@@ -358,18 +363,6 @@ func (c *Client) sendWithSignatureV3(request tchttp.Request, response tchttp.Res
358363
}
359364
}
360365

361-
// Merge any additional headers from the request, but skip built-in headers
362-
// to prevent them from being overridden.
363-
for k, v := range request.GetHeader() {
364-
switch k {
365-
case "X-TC-Action", "X-TC-Version", "X-TC-Timestamp", "X-TC-RequestClient",
366-
"X-TC-Language", "X-TC-Region", "X-TC-Token":
367-
c.logger.Printf("Skip header \"%s\": can not specify built-in header", k)
368-
default:
369-
headers[k] = v
370-
}
371-
}
372-
373366
// Handle the case where the request content type is explicitly set to octet-stream,
374367
// but it's not already handled as an OctetStream CommonRequest.
375368
if !isOctetStream && request.GetContentType() == octetStream {
@@ -385,6 +378,12 @@ func (c *Client) sendWithSignatureV3(request tchttp.Request, response tchttp.Res
385378
headers["Content-Type"] = octetStream
386379
octetStreamBody = request.GetBody()
387380
}
381+
382+
// Merge any additional headers from the request
383+
for k, v := range request.GetHeader() {
384+
headers[k] = v
385+
}
386+
388387
// --- Begin Signature Version 3 (TC3-HMAC-SHA256) Signing Process ---
389388

390389
// 1. Construct the Canonical Request
@@ -528,7 +527,11 @@ func (c *Client) sendWithSignatureV3(request tchttp.Request, response tchttp.Res
528527

529528
// Set all the headers on the request.
530529
for k, v := range headers {
531-
httpRequest.Header[k] = []string{v}
530+
if strings.EqualFold(k, "Host") {
531+
httpRequest.Host = v
532+
} else {
533+
httpRequest.Header.Set(k, v)
534+
}
532535
}
533536

534537
// Send the HTTP request with rate limit retry logic.

tencentcloud/common/sign.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,10 @@ func signRequest(request tchttp.Request, credential CredentialIface, method stri
6262

6363
func getStringToSign(request tchttp.Request) string {
6464
method := request.GetHttpMethod()
65-
domain := request.GetDomain()
65+
domain := request.GetHeader()["Host"]
66+
if domain == "" {
67+
domain = request.GetDomain()
68+
}
6669
path := request.GetPath()
6770

6871
var buf bytes.Buffer

testing/integration/header_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright 1999-2021 Tencent Ltd.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package integration
16+
17+
import (
18+
"context"
19+
"net/http/httptrace"
20+
"os"
21+
"strings"
22+
"testing"
23+
24+
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
25+
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
26+
cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312"
27+
)
28+
29+
func TestSetHeader(t *testing.T) {
30+
signMethods := []string{"TC3-HMAC-SHA256", "HmacSHA256", "HmacSHA1"}
31+
reqMethods := []string{"GET", "POST"}
32+
33+
endpoint := "vpc.tencentcloudapi.com"
34+
signHost := "cvm.tencentcloudapi.com"
35+
36+
for _, signMethod := range signMethods {
37+
for _, reqMethod := range reqMethods {
38+
credential := common.NewCredential(
39+
os.Getenv("TENCENTCLOUD_SECRET_ID"),
40+
os.Getenv("TENCENTCLOUD_SECRET_KEY"),
41+
)
42+
cpf := profile.NewClientProfile()
43+
cpf.HttpProfile.Endpoint = endpoint
44+
cpf.HttpProfile.ReqMethod = reqMethod
45+
cpf.SignMethod = signMethod
46+
client, err := cvm.NewClient(credential, "ap-guangzhou", cpf)
47+
if err != nil {
48+
t.Fatal(err)
49+
}
50+
51+
request := cvm.NewDescribeZonesRequest()
52+
request.SetHeader(map[string]string{
53+
"TestKey": "TestValue",
54+
"Host": signHost,
55+
})
56+
57+
var hostChecked, testKeyChecked bool
58+
trace := &httptrace.ClientTrace{
59+
DNSStart: func(info httptrace.DNSStartInfo) {
60+
if info.Host != endpoint {
61+
t.Fatalf("invalid req endpoint: signMethod=%s reqMethod=%s host=%s", signMethod, reqMethod, info.Host)
62+
}
63+
},
64+
WroteHeaderField: func(key string, value []string) {
65+
if strings.EqualFold(key, "Host") {
66+
hostChecked = true
67+
if value[0] != signHost {
68+
t.Fatalf("invalid Host: %s", value[0])
69+
}
70+
}
71+
72+
if strings.EqualFold(key, "TestKey") {
73+
testKeyChecked = true
74+
if value[0] != "TestValue" {
75+
t.Fatalf("invalid Header: %s", value[0])
76+
}
77+
}
78+
},
79+
}
80+
ctx := httptrace.WithClientTrace(context.Background(), trace)
81+
_, err = client.DescribeZonesWithContext(ctx, request)
82+
if err != nil {
83+
t.Fatalf("unexpected error: %s", err)
84+
}
85+
86+
if !hostChecked || !testKeyChecked {
87+
t.Fatalf("header not sent: %v %v", hostChecked, testKeyChecked)
88+
}
89+
}
90+
}
91+
}

0 commit comments

Comments
 (0)