/*
* 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;
}
}
}