/* * Copyright (c) 2018 THL A29 Limited, a Tencent company. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ using Newtonsoft.Json; using Pathoschild.Http.Client; using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Text; using System.Threading.Tasks; using TencentCloud.Common.Http; using TencentCloud.Common.Profile; namespace TencentCloud.Common { public class AbstractClient { public const int HTTP_RSP_OK = 200; public const string SDK_VERSION = "SDK_NET_3.0.53"; public AbstractClient(string endpoint, string version, Credential credential, string region, ClientProfile profile) { this.Credential = credential; this.Profile = profile; this.Endpoint = endpoint; this.Region = region; this.Path = "/"; this.SdkVersion = SDK_VERSION; this.ApiVersion = version; } /// /// Credentials. /// public Credential Credential { get; set; } /// /// Client profiles. /// public ClientProfile Profile { get; set; } /// /// Service endpoint, or domain name, such as productName.tencentcloudapi.com. /// public string Endpoint { get; set; } /// /// Service region, such as ap-guangzhou. /// public string Region { get; set; } /// /// URL path, for API 3.0, is /. /// public string Path { get; private set; } /// /// SDK version. /// public string SdkVersion { get; set; } /// /// API version. /// public string ApiVersion { get; set; } protected async Task InternalRequest(AbstractModel request, string actionName) { if ((this.Profile.HttpProfile.ReqMethod != HttpProfile.REQ_GET) && (this.Profile.HttpProfile.ReqMethod != HttpProfile.REQ_POST)) { throw new TencentCloudSDKException("Method only support (GET, POST)"); } IResponse response = null; if (ClientProfile.SIGN_SHA1.Equals(this.Profile.SignMethod) || ClientProfile.SIGN_SHA256.Equals(this.Profile.SignMethod)) { response = await RequestV1(request, actionName); } else { response = await RequestV3(request, actionName); } if ((int)response.Status != HTTP_RSP_OK) { throw new TencentCloudSDKException(response.Status + await response.Message.Content.ReadAsStringAsync()); } string strResp = null; try { strResp = await response.AsString(); } catch (ApiException ex) { string responseText = await ex.Response.AsString(); throw new TencentCloudSDKException($"The API responded with HTTP {ex.Response.Status}: {responseText}"); } JsonResponseModel errResp = null; try { errResp = JsonConvert.DeserializeObject>(strResp); } catch (JsonSerializationException e) { throw new TencentCloudSDKException(e.Message); } if (errResp.Response.Error != null) { throw new TencentCloudSDKException($"code:{errResp.Response.Error.Code} message:{errResp.Response.Error.Message} ", errResp.Response.RequestId); } return strResp; } protected string InternalRequestSync(AbstractModel request, string actionName) { if ((this.Profile.HttpProfile.ReqMethod != HttpProfile.REQ_GET) && (this.Profile.HttpProfile.ReqMethod != HttpProfile.REQ_POST)) { throw new TencentCloudSDKException("Method only support (GET, POST)"); } HttpWebResponse response = null; if (ClientProfile.SIGN_SHA1.Equals(this.Profile.SignMethod) || ClientProfile.SIGN_SHA256.Equals(this.Profile.SignMethod)) { response = RequestV1Sync(request, actionName); } else { response = RequestV3Sync(request, actionName); } HttpStatusCode statusCode = response.StatusCode; if (statusCode != HttpStatusCode.OK) { Encoding encoding = Encoding.UTF8; using (Stream stream = response.GetResponseStream()) { using (StreamReader sr = new StreamReader(response.GetResponseStream(), encoding)) { string content = sr.ReadToEnd().ToString(); throw new TencentCloudSDKException(statusCode.ToString() + content); } } } string strResp = null; try { Encoding encoding = Encoding.UTF8; using (Stream stream = response.GetResponseStream()) { using (StreamReader sr = new StreamReader(response.GetResponseStream(), encoding)) { strResp = sr.ReadToEnd().ToString(); } } } catch (Exception ex) { string responseText = ex.Message; throw new TencentCloudSDKException($"The API responded with HTTP {responseText}"); } JsonResponseModel errResp = null; try { errResp = JsonConvert.DeserializeObject>(strResp); } catch (JsonSerializationException e) { throw new TencentCloudSDKException(e.Message); } if (errResp.Response.Error != null) { throw new TencentCloudSDKException($"code:{errResp.Response.Error.Code} message:{errResp.Response.Error.Message} ", errResp.Response.RequestId); } return strResp; } private async Task RequestV3(AbstractModel request, string actionName) { string httpRequestMethod = this.Profile.HttpProfile.ReqMethod; string canonicalQueryString = this.BuildCanonicalQueryString(request); string requestPayload = this.BuildRequestPayload(request); string contentType = this.BuildContentType(); Dictionary headers = this.BuildHeaders(contentType, requestPayload, canonicalQueryString); headers.Add("X-TC-Action", actionName); string endpoint = headers["Host"]; HttpConnection conn = new HttpConnection( $"{this.Profile.HttpProfile.Protocol }{endpoint}", this.Profile.HttpProfile.Timeout, this.Profile.HttpProfile.WebProxy); try { if (this.Profile.HttpProfile.ReqMethod == HttpProfile.REQ_GET) { return await conn.GetRequest(this.Path, canonicalQueryString, headers); } else { return await conn.PostRequest(this.Path, requestPayload, headers); } } catch (Exception e) { throw new TencentCloudSDKException($"The request with exception: {e.Message}"); } } private Dictionary BuildHeaders(string contentType, string requestPayload, string canonicalQueryString) { string endpoint = this.Endpoint; if (!string.IsNullOrEmpty(this.Profile.HttpProfile.Endpoint)) { endpoint = this.Profile.HttpProfile.Endpoint; } string httpRequestMethod = this.Profile.HttpProfile.ReqMethod; string canonicalURI = "/"; string canonicalHeaders = "content-type:" + contentType + "; charset=utf-8\nhost:" + endpoint + "\n"; string signedHeaders = "content-type;host"; string hashedRequestPayload = SignHelper.SHA256Hex(requestPayload); string canonicalRequest = httpRequestMethod + "\n" + canonicalURI + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload; string algorithm = "TC3-HMAC-SHA256"; long timestamp = ToTimestamp() / 1000; string requestTimestamp = timestamp.ToString(); string date = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(timestamp).ToString("yyyy-MM-dd"); string service = endpoint.Split('.')[0]; string credentialScope = date + "/" + service + "/" + "tc3_request"; string hashedCanonicalRequest = SignHelper.SHA256Hex(canonicalRequest); string stringToSign = algorithm + "\n" + requestTimestamp + "\n" + credentialScope + "\n" + hashedCanonicalRequest; byte[] tc3SecretKey = Encoding.UTF8.GetBytes("TC3" + Credential.SecretKey); byte[] secretDate = SignHelper.HmacSHA256(tc3SecretKey, Encoding.UTF8.GetBytes(date)); byte[] secretService = SignHelper.HmacSHA256(secretDate, Encoding.UTF8.GetBytes(service)); byte[] secretSigning = SignHelper.HmacSHA256(secretService, Encoding.UTF8.GetBytes("tc3_request")); byte[] signatureBytes = SignHelper.HmacSHA256(secretSigning, Encoding.UTF8.GetBytes(stringToSign)); string signature = BitConverter.ToString(signatureBytes).Replace("-", "").ToLower(); string authorization = algorithm + " " + "Credential=" + Credential.SecretId + "/" + credentialScope + ", " + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature; Dictionary headers = new Dictionary(); headers.Add("Authorization", authorization); headers.Add("Host", endpoint); headers.Add("Content-Type", contentType); headers.Add("X-TC-Timestamp", requestTimestamp); headers.Add("X-TC-Version", this.ApiVersion); headers.Add("X-TC-Region", this.Region); headers.Add("X-TC-RequestClient", this.SdkVersion); if (!string.IsNullOrEmpty(this.Credential.Token)) { headers.Add("X-TC-Token", this.Credential.Token); } return headers; } private string BuildContentType() { string httpRequestMethod = this.Profile.HttpProfile.ReqMethod; if (HttpProfile.REQ_GET.Equals(httpRequestMethod)) { return "application/x-www-form-urlencoded"; } else { return "application/json"; } } private string BuildCanonicalQueryString(AbstractModel request) { string httpRequestMethod = this.Profile.HttpProfile.ReqMethod; if (!HttpProfile.REQ_GET.Equals(httpRequestMethod)) { return ""; } Dictionary param = new Dictionary(); request.ToMap(param, ""); StringBuilder urlBuilder = new StringBuilder(); foreach (KeyValuePair kvp in param) { urlBuilder.Append($"{WebUtility.UrlEncode(kvp.Key)}={WebUtility.UrlEncode(kvp.Value)}&"); } return urlBuilder.ToString().TrimEnd('&'); } private string BuildRequestPayload(AbstractModel request) { string httpRequestMethod = this.Profile.HttpProfile.ReqMethod; if (HttpProfile.REQ_GET.Equals(httpRequestMethod)) { return ""; } return JsonConvert.SerializeObject(request, Newtonsoft.Json.Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); } private HttpWebResponse RequestV3Sync(AbstractModel request, string actionName) { string httpRequestMethod = this.Profile.HttpProfile.ReqMethod; string canonicalQueryString = this.BuildCanonicalQueryString(request); string requestPayload = this.BuildRequestPayload(request); string contentType = this.BuildContentType(); Dictionary headers = this.BuildHeaders(contentType, requestPayload, canonicalQueryString); headers.Add("X-TC-Action", actionName); string endpoint = headers["Host"]; HttpConnection conn = new HttpConnection( $"{this.Profile.HttpProfile.Protocol }{endpoint}", this.Profile.HttpProfile.Timeout, this.Profile.HttpProfile.WebProxy); try { if (this.Profile.HttpProfile.ReqMethod == HttpProfile.REQ_GET) { return conn.GetRequestSync(this.Path, canonicalQueryString, headers); } else { return conn.PostRequestSync(this.Path, requestPayload, headers); } } catch (Exception e) { throw new TencentCloudSDKException($"The request with exception: {e.Message}"); } } private HttpConnection BuildConnection() { string endpoint = this.Endpoint; if (!string.IsNullOrEmpty(this.Profile.HttpProfile.Endpoint)) { endpoint = this.Profile.HttpProfile.Endpoint; } HttpConnection conn = new HttpConnection( $"{this.Profile.HttpProfile.Protocol }{endpoint}", this.Profile.HttpProfile.Timeout, this.Profile.HttpProfile.WebProxy); return conn; } private Dictionary BuildParam(AbstractModel request, string actionName) { Dictionary param = new Dictionary(); request.ToMap(param, ""); // inplace change this.FormatRequestData(actionName, param); return param; } private async Task RequestV1(AbstractModel request, string actionName) { IResponse response = null; Dictionary param = BuildParam(request, actionName); HttpConnection conn = this.BuildConnection(); try { if (this.Profile.HttpProfile.ReqMethod == HttpProfile.REQ_GET) { response = await conn.GetRequest(this.Path, param); } else if (this.Profile.HttpProfile.ReqMethod == HttpProfile.REQ_POST) { response = await conn.PostRequest(this.Path, param); } } catch(Exception ex) { throw new TencentCloudSDKException($"The request with exception: {ex.Message }"); } return response; } private HttpWebResponse RequestV1Sync(AbstractModel request, string actionName) { HttpWebResponse response = null; Dictionary param = BuildParam(request, actionName); HttpConnection conn = this.BuildConnection(); try { if (this.Profile.HttpProfile.ReqMethod == HttpProfile.REQ_GET) { response = conn.GetRequestSync(this.Path, param); } else if (this.Profile.HttpProfile.ReqMethod == HttpProfile.REQ_POST) { response = conn.PostRequestSync(this.Path, param); } } catch (Exception ex) { throw new TencentCloudSDKException($"The request with exception: {ex.Message }"); } return response; } private Dictionary FormatRequestData(string action, Dictionary param) { param.Add("Action", action); param.Add("RequestClient", this.SdkVersion); param.Add("Nonce", Math.Abs(new Random().Next()).ToString()); long unixTime = ToTimestamp(); param.Add("Timestamp", (unixTime / 1000).ToString()); param.Add("Version", this.ApiVersion); if (!string.IsNullOrEmpty(this.Credential.SecretId)) { param.Add("SecretId", this.Credential.SecretId); } if (!string.IsNullOrEmpty(this.Region)) { param.Add("Region", this.Region); } if (!string.IsNullOrEmpty(this.Profile.SignMethod)) { param.Add("SignatureMethod", this.Profile.SignMethod); } if (!string.IsNullOrEmpty(this.Credential.Token)) { param.Add("Token", this.Credential.Token); } string endpoint = this.Endpoint; if (!string.IsNullOrEmpty(this.Profile.HttpProfile.Endpoint)) { endpoint = this.Profile.HttpProfile.Endpoint; } string sigInParam = SignHelper.MakeSignPlainText(new SortedDictionary(param, StringComparer.Ordinal), this.Profile.HttpProfile.ReqMethod, endpoint, this.Path); string sigOutParam = SignHelper.Sign(this.Credential.SecretKey, sigInParam, this.Profile.SignMethod); param.Add("Signature", sigOutParam); return param; } public long ToTimestamp() { DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1, 0, 0, 0, 0)); DateTime nowTime = DateTime.Now; long unixTime = (long)Math.Round((nowTime - startTime).TotalMilliseconds, MidpointRounding.AwayFromZero); return unixTime; } } }