diff --git a/aliyun-net-sdk-core/AcsClientExtensions.cs b/aliyun-net-sdk-core/AcsClientExtensions.cs new file mode 100644 index 0000000000..f031ff3c3f --- /dev/null +++ b/aliyun-net-sdk-core/AcsClientExtensions.cs @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Aliyun.Acs.Core.Auth; +using Aliyun.Acs.Core.Http; +using Aliyun.Acs.Core.Profile; +using Aliyun.Acs.Core.Regions; + +namespace Aliyun.Acs.Core +{ + public static class AcsClientExtensions + { + public static Task GetAcsResponseAsync(this IAcsClient client, AcsRequest request) where T : AcsResponse + { + return client.GetAcsResponseAsync(request, default(CancellationToken)); + } + + public static Task GetAcsResponseAsync(this IAcsClient client, AcsRequest request, bool autoRetry, int maxRetryCounts) where T : AcsResponse + { + return client.GetAcsResponseAsync(request, autoRetry, maxRetryCounts, default(CancellationToken)); + } + + public static Task GetAcsResponseAsync(this IAcsClient client, AcsRequest request, IClientProfile profile) where T : AcsResponse + { + return client.GetAcsResponseAsync(request, profile, default(CancellationToken)); + } + + public static Task GetAcsResponseAsync(this IAcsClient client, AcsRequest request, string regionId, Credential credential) where T : AcsResponse + { + return client.GetAcsResponseAsync(request, regionId, credential, default(CancellationToken)); + } + + public static Task GetCommonResponseAsync(this IAcsClient client, CommonRequest request) + { + return client.GetCommonResponseAsync(request, default(CancellationToken)); + } + + public static Task DoActionAsync(this IAcsClient client, AcsRequest request) where T : AcsResponse + { + return client.DoActionAsync(request, default(CancellationToken)); + } + + public static Task DoActionAsync(this IAcsClient client, AcsRequest request, bool autoRetry, int maxRetryCounts) where T : AcsResponse + { + return client.DoActionAsync(request, autoRetry, maxRetryCounts, default(CancellationToken)); + } + + public static Task DoActionAsync(this IAcsClient client, AcsRequest request, IClientProfile profile) where T : AcsResponse + { + return client.DoActionAsync(request, profile, default(CancellationToken)); + } + + public static Task DoActionAsync(this IAcsClient client, AcsRequest request, string regionId, Credential credential) where T : AcsResponse + { + return client.DoActionAsync(request, regionId, credential, default(CancellationToken)); + } + + public static Task DoActionAsync(this IAcsClient client, AcsRequest request, bool autoRetry, int maxRetryCounts, IClientProfile profile) where T : AcsResponse + { + return client.DoActionAsync(request, autoRetry, maxRetryCounts, profile, default(CancellationToken)); + } + + public static Task DoActionAsync(this IAcsClient client, AcsRequest request, + bool autoRetry, int maxRetryNumber, + string regionId, Credential credential, + Signer signer, FormatType? format, + List endpoints) where T : AcsResponse + { + return client.DoActionAsync(request, autoRetry, maxRetryNumber, regionId, credential, signer, format, endpoints, default(CancellationToken)); + } + } +} diff --git a/aliyun-net-sdk-core/Auth/Provider/AccessKeyCredentialProvider.cs b/aliyun-net-sdk-core/Auth/Provider/AccessKeyCredentialProvider.cs index 09fc87b6ca..132b196ac8 100644 --- a/aliyun-net-sdk-core/Auth/Provider/AccessKeyCredentialProvider.cs +++ b/aliyun-net-sdk-core/Auth/Provider/AccessKeyCredentialProvider.cs @@ -1,4 +1,4 @@ -/* +/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -17,6 +17,9 @@ * under the License. */ +using System.Threading; +using System.Threading.Tasks; + namespace Aliyun.Acs.Core.Auth { /// @@ -35,5 +38,10 @@ public AlibabaCloudCredentials GetCredentials() { return accessKeyCredential; } + + public Task GetCredentialsAsync(CancellationToken cancellationToken) + { + return Task.FromResult(accessKeyCredential); + } } } diff --git a/aliyun-net-sdk-core/Auth/Provider/AlibabaCloudCredentialsProvider.cs b/aliyun-net-sdk-core/Auth/Provider/AlibabaCloudCredentialsProvider.cs index c01ee3e5df..5b5e397198 100644 --- a/aliyun-net-sdk-core/Auth/Provider/AlibabaCloudCredentialsProvider.cs +++ b/aliyun-net-sdk-core/Auth/Provider/AlibabaCloudCredentialsProvider.cs @@ -1,4 +1,4 @@ -/* +/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -17,10 +17,15 @@ * under the License. */ +using System.Threading; +using System.Threading.Tasks; + namespace Aliyun.Acs.Core.Auth { public interface AlibabaCloudCredentialsProvider { AlibabaCloudCredentials GetCredentials(); + + Task GetCredentialsAsync(CancellationToken cancellationToken); } } diff --git a/aliyun-net-sdk-core/Auth/Provider/BearerTokenCredentialProvider.cs b/aliyun-net-sdk-core/Auth/Provider/BearerTokenCredentialProvider.cs index 5507a2bbfb..65555ae520 100644 --- a/aliyun-net-sdk-core/Auth/Provider/BearerTokenCredentialProvider.cs +++ b/aliyun-net-sdk-core/Auth/Provider/BearerTokenCredentialProvider.cs @@ -1,4 +1,4 @@ -/* +/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -17,6 +17,9 @@ * under the License. */ +using System.Threading; +using System.Threading.Tasks; + namespace Aliyun.Acs.Core.Auth { public class BearerTokenCredentialProvider : AlibabaCloudCredentialsProvider @@ -32,5 +35,10 @@ public AlibabaCloudCredentials GetCredentials() { return bearerTokenCredential; } + + public Task GetCredentialsAsync(CancellationToken cancellationToken) + { + return Task.FromResult(bearerTokenCredential); + } } } diff --git a/aliyun-net-sdk-core/Auth/Provider/DefaultCredentialProvider.cs b/aliyun-net-sdk-core/Auth/Provider/DefaultCredentialProvider.cs index 235c4a849a..7b6e77154d 100644 --- a/aliyun-net-sdk-core/Auth/Provider/DefaultCredentialProvider.cs +++ b/aliyun-net-sdk-core/Auth/Provider/DefaultCredentialProvider.cs @@ -18,7 +18,8 @@ */ using System.IO; - +using System.Threading; +using System.Threading.Tasks; using Aliyun.Acs.Core.Exceptions; using Aliyun.Acs.Core.Profile; using Aliyun.Acs.Core.Utils; @@ -288,5 +289,220 @@ public virtual string GetHomePath() { return EnvironmentUtil.GetHomePath(); } + + #region Async + + public async Task GetAlibabaCloudClientCredentialAsync(CancellationToken cancellationToken) + { + var credential = await GetEnvironmentAlibabaCloudCredentialAsync(cancellationToken).ConfigureAwait(false) ?? + await GetCredentialFileAlibabaCloudCredentialAsync(cancellationToken).ConfigureAwait(false) ?? + await GetInstanceRamRoleAlibabaCloudCredentialAsync(cancellationToken).ConfigureAwait(false); + + if (credential == null) + { + throw new ClientException("There is no credential chain can use."); + } + + return credential; + } + + public async Task GetEnvironmentAlibabaCloudCredentialAsync(CancellationToken cancellationToken) + { + if (null == accessKeyId || null == accessKeySecret) + { + return null; + } + + if (accessKeyId.Equals("") || accessKeySecret.Equals("")) + { + throw new ClientException( + "Environment credential variable 'ALIBABA_CLOUD_ACCESS_KEY_*' cannot be empty"); + } + + return defaultProfile.DefaultClientName.Equals("default") ? await GetAccessKeyCredentialAsync(cancellationToken).ConfigureAwait(false) : null; + } + + public async Task GetCredentialFileAlibabaCloudCredentialAsync(CancellationToken cancellationToken) + { + if (null == credentialFileLocation) + { + credentialFileLocation = GetHomePath(); + var slash = EnvironmentUtil.GetOSSlash(); + var fileLocation = EnvironmentUtil.GetComposedPath(credentialFileLocation, slash); + + if (File.Exists(fileLocation)) + { + credentialFileLocation = fileLocation; + } + else + { + return null; + } + } + + if (credentialFileLocation.Equals("")) + { + throw new ClientException( + "Credentials file environment variable 'ALIBABA_CLOUD_CREDENTIALS_FILE' cannot be empty"); + } + + var iniReader = new IniReader(credentialFileLocation); + var sectionNameList = iniReader.GetSections(); + + if (null != defaultProfile.DefaultClientName) + { + var userDefineSectionNode = defaultProfile.DefaultClientName; + + var iniKeyTypeValue = iniReader.GetValue("type", userDefineSectionNode); + + if (iniKeyTypeValue.Equals("access_key")) + { + accessKeyId = iniReader.GetValue("access_key_id", userDefineSectionNode); + accessKeySecret = iniReader.GetValue("access_key_secret", userDefineSectionNode); + regionId = iniReader.GetValue("region_id", userDefineSectionNode); + + return await GetAccessKeyCredentialAsync(cancellationToken).ConfigureAwait(false); + } + + if (iniKeyTypeValue.Equals("ecs_ram_role")) + { + roleName = iniReader.GetValue("role_name", userDefineSectionNode); + regionId = iniReader.GetValue("region_id", userDefineSectionNode); + + return await GetInstanceRamRoleAlibabaCloudCredentialAsync(cancellationToken).ConfigureAwait(false); + } + + if (iniKeyTypeValue.Equals("ram_role_arn")) + { + accessKeyId = iniReader.GetValue("access_key_id", userDefineSectionNode); + accessKeySecret = iniReader.GetValue("access_key_secret", userDefineSectionNode); + roleArn = iniReader.GetValue("role_arn", userDefineSectionNode); + + return await GetRamRoleArnAlibabaCloudCredentialAsync(cancellationToken).ConfigureAwait(false); + } + + if (iniKeyTypeValue.Equals("rsa_key_pair")) + { + publicKeyId = iniReader.GetValue("public_key_id", userDefineSectionNode); + privateKeyFile = iniReader.GetValue("private_key_file", userDefineSectionNode); + + return await GetRsaKeyPairAlibabaCloudCredentialAsync(cancellationToken).ConfigureAwait(false); + } + } + else + { + foreach (var sectionItem in sectionNameList) + { + if (!sectionItem.Equals("default")) + { + continue; + } + + accessKeyId = iniReader.GetValue("access_key_id", "default"); + accessKeySecret = iniReader.GetValue("access_key_secret", "default"); + regionId = iniReader.GetValue("region_id", "default"); + + return await GetAccessKeyCredentialAsync(cancellationToken).ConfigureAwait(false); + } + } + + return null; + } + + public virtual Task GetInstanceRamRoleAlibabaCloudCredentialAsync(CancellationToken cancellationToken) + { + if (null == regionId || regionId.Equals("")) + { + throw new ClientException("RegionID cannot be null or empty."); + } + + if (!defaultProfile.DefaultClientName.Equals("default")) + { + return null; + } + + InstanceProfileCredentialsProvider instanceProfileCredentialProvider; + if (null != alibabaCloudCredentialProvider) + { + instanceProfileCredentialProvider = + (InstanceProfileCredentialsProvider)alibabaCloudCredentialProvider; + } + else + { + instanceProfileCredentialProvider = new InstanceProfileCredentialsProvider(roleName); + } + + return instanceProfileCredentialProvider.GetCredentialsAsync(cancellationToken); + } + + public Task GetAccessKeyCredentialAsync(CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(accessKeyId) || string.IsNullOrEmpty(accessKeySecret) || + string.IsNullOrEmpty(regionId)) + { + throw new ClientException("Missing required variable option for 'default Client'"); + } + + var accessKeyCredentialProvider = + new AccessKeyCredentialProvider(accessKeyId, accessKeySecret); + + return accessKeyCredentialProvider.GetCredentialsAsync(cancellationToken); + } + + public virtual Task GetRamRoleArnAlibabaCloudCredentialAsync(CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(accessKeyId) || string.IsNullOrEmpty(accessKeySecret) || + string.IsNullOrEmpty(regionId)) + { + throw new ClientException("Missing required variable option for 'default Client'"); + } + + var credential = new BasicSessionCredentials(accessKeyId, accessKeySecret, + STSAssumeRoleSessionCredentialsProvider.GetNewRoleSessionName(), + 3600 + ); + var profile = DefaultProfile.GetProfile(regionId, accessKeyId, accessKeySecret); + + STSAssumeRoleSessionCredentialsProvider stsAsssumeRoleSessionCredentialProvider; + + if (null != alibabaCloudCredentialProvider) + { + stsAsssumeRoleSessionCredentialProvider = + (STSAssumeRoleSessionCredentialsProvider)alibabaCloudCredentialProvider; + } + else + { + stsAsssumeRoleSessionCredentialProvider = + new STSAssumeRoleSessionCredentialsProvider(credential, roleArn, profile); + } + + return stsAsssumeRoleSessionCredentialProvider.GetCredentialsAsync(cancellationToken); + } + + public virtual Task GetRsaKeyPairAlibabaCloudCredentialAsync(CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(publicKeyId) || string.IsNullOrEmpty(privateKeyFile) || + string.IsNullOrEmpty(regionId)) + { + throw new ClientException("Missing required variable option for 'default Client'"); + } + + var rsaKeyPairCredential = new KeyPairCredentials(publicKeyId, privateKeyFile); + var profile = DefaultProfile.GetProfile(regionId, publicKeyId, privateKeyFile); + + RsaKeyPairCredentialProvider rsaKeyPairCredentialProvider; + + if (null != alibabaCloudCredentialProvider) + { + rsaKeyPairCredentialProvider = (RsaKeyPairCredentialProvider)alibabaCloudCredentialProvider; + } + else + { + rsaKeyPairCredentialProvider = new RsaKeyPairCredentialProvider(rsaKeyPairCredential, profile); + } + + return rsaKeyPairCredentialProvider.GetCredentialsAsync(cancellationToken); + } + #endregion } } diff --git a/aliyun-net-sdk-core/Auth/Provider/ECSMetadataServiceCredentialsFetcher.cs b/aliyun-net-sdk-core/Auth/Provider/ECSMetadataServiceCredentialsFetcher.cs index 3de0ff3d0a..21c24b7de2 100644 --- a/aliyun-net-sdk-core/Auth/Provider/ECSMetadataServiceCredentialsFetcher.cs +++ b/aliyun-net-sdk-core/Auth/Provider/ECSMetadataServiceCredentialsFetcher.cs @@ -21,7 +21,8 @@ using System.Collections.Generic; using System.Net; using System.Text; - +using System.Threading; +using System.Threading.Tasks; using Aliyun.Acs.Core.Exceptions; using Aliyun.Acs.Core.Http; using Aliyun.Acs.Core.Reader; @@ -174,5 +175,102 @@ public virtual HttpResponse GetResponse(HttpRequest request) { return HttpResponse.GetResponse(request); } + + #region Async + + public async Task GetCredentialsAsync(CancellationToken cancellationToken) + { + return await FetchAsync(cancellationToken).ConfigureAwait(false); + } + + public async Task GetMetadataAsync(CancellationToken cancellationToken) + { + var request = new HttpRequest(credentialUrl); + request.Method = MethodType.GET; + request.SetConnectTimeoutInMilliSeconds(connectionTimeoutInMilliseconds); + + HttpResponse response; + try + { + response = await GetResponseAsync(request, cancellationToken).ConfigureAwait(false); + } + catch (WebException e) + { + throw new ClientException("Failed to connect ECS Metadata Service: " + e); + } + + if (response.Status != 200) + { + throw new ClientException(ECS_METADAT_FETCH_ERROR_MSG + " HttpCode=" + response.Status); + } + + return Encoding.UTF8.GetString(response.Content); + } + + public virtual async Task FetchAsync(CancellationToken cancellationToken) + { + Dictionary dic; + try + { + var jsonContent = await GetMetadataAsync(cancellationToken).ConfigureAwait(false); + + IReader reader = new JsonReader(); + dic = reader.Read(jsonContent, ""); + } + catch (Exception e) + { + throw new ClientException(ECS_METADAT_FETCH_ERROR_MSG + " Reason: " + e); + } + + if ( + DictionaryUtil.Get(dic, ".Code") == null || + DictionaryUtil.Get(dic, ".AccessKeyId") == null || + DictionaryUtil.Get(dic, ".AccessKeySecret") == null || + DictionaryUtil.Get(dic, ".SecurityToken") == null || + DictionaryUtil.Get(dic, ".Expiration") == null + ) + { + throw new ClientException("Invalid json got from ECS Metadata service."); + } + + if (!"Success".Equals(DictionaryUtil.Get(dic, ".Code"))) + { + throw new ClientException(ECS_METADAT_FETCH_ERROR_MSG); + } + + return new InstanceProfileCredentials( + DictionaryUtil.Get(dic, ".AccessKeyId"), + DictionaryUtil.Get(dic, ".AccessKeySecret"), + DictionaryUtil.Get(dic, ".SecurityToken"), + DictionaryUtil.Get(dic, ".Expiration"), + DEFAULT_ECS_SESSION_TOKEN_DURATION_SECONDS + ); + } + + public async Task FetchAsync(int retryTimes, CancellationToken cancellationToken) + { + for (var i = 0; i <= retryTimes; i++) + { + try + { + return await FetchAsync(cancellationToken).ConfigureAwait(false); + } + catch (ClientException e) + { + if (i == retryTimes) + { + throw new ClientException(e.ErrorCode, e.ErrorMessage); + } + } + } + + throw new ClientException("Failed to connect ECS Metadata Service: Max retry times exceeded."); + } + + public virtual Task GetResponseAsync(HttpRequest request, CancellationToken cancellationToken) + { + return HttpResponse.GetResponseAsync(request, cancellationToken); + } + #endregion } } diff --git a/aliyun-net-sdk-core/Auth/Provider/InstanceProfileCredentialsProvider.cs b/aliyun-net-sdk-core/Auth/Provider/InstanceProfileCredentialsProvider.cs index b5084ca889..63df830752 100644 --- a/aliyun-net-sdk-core/Auth/Provider/InstanceProfileCredentialsProvider.cs +++ b/aliyun-net-sdk-core/Auth/Provider/InstanceProfileCredentialsProvider.cs @@ -17,6 +17,8 @@ * under the License. */ +using System.Threading; +using System.Threading.Tasks; using Aliyun.Acs.Core.Exceptions; using Aliyun.Acs.Core.Utils; @@ -77,6 +79,44 @@ public virtual AlibabaCloudCredentials GetCredentials() return credentials; } + public async Task GetCredentialsAsync(CancellationToken cancellationToken) + { + try + { + if (credentials == null) + { + credentials = await fetcher.FetchAsync(maxRetryTimes, cancellationToken).ConfigureAwait(false); + } + + if (credentials.IsExpired()) + { + throw new ClientException("SDK.SessionTokenExpired", "Current session token has expired."); + } + + if (!credentials.WillSoonExpire() || !credentials.ShouldRefresh()) + { + return credentials; + } + + credentials = await fetcher.FetchAsync(cancellationToken).ConfigureAwait(false); + return credentials; + } + catch (ClientException ex) + { + if (ex.ErrorCode.Equals("SDK.SessionTokenExpired") && + ex.ErrorMessage.Equals("Current session token has expired.")) + { + CommonLog.LogException(ex, ex.ErrorCode, ex.ErrorMessage); + throw new ClientException(ex.ErrorCode, ex.ErrorMessage); + } + + // Use the current expiring session token and wait for next round + credentials.SetLastFailedRefreshTime(); + } + + return credentials; + } + public void withFetcher(ECSMetadataServiceCredentialsFetcher fetcher) { this.fetcher = fetcher; diff --git a/aliyun-net-sdk-core/Auth/Provider/RsaKeyPairCredentialProvider.cs b/aliyun-net-sdk-core/Auth/Provider/RsaKeyPairCredentialProvider.cs index 1cd4e3a575..58709df921 100644 --- a/aliyun-net-sdk-core/Auth/Provider/RsaKeyPairCredentialProvider.cs +++ b/aliyun-net-sdk-core/Auth/Provider/RsaKeyPairCredentialProvider.cs @@ -1,4 +1,4 @@ -/* +/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -17,6 +17,8 @@ * under the License. */ +using System.Threading; +using System.Threading.Tasks; using Aliyun.Acs.Core.Auth.Sts; using Aliyun.Acs.Core.Http; using Aliyun.Acs.Core.Profile; @@ -52,6 +54,16 @@ public virtual AlibabaCloudCredentials GetCredentials() return basicSessionCredentials; } + public async Task GetCredentialsAsync(CancellationToken cancellationToken) + { + if (basicSessionCredentials == null || basicSessionCredentials.WillSoonExpire()) + { + basicSessionCredentials = await GetNewSessionCredentialsAsync(cancellationToken).ConfigureAwait(false); + } + + return basicSessionCredentials; + } + public void WithDurationSeconds(long seconds) { sessionDurationSeconds = seconds; @@ -79,5 +91,23 @@ private BasicSessionCredentials GetNewSessionCredentials() null, sessionDurationSeconds ); } + + private async Task GetNewSessionCredentialsAsync(CancellationToken cancellationToken) + { + var request = new GetSessionAccessKeyRequest + { + PublicKeyId = rsaKeyPairCredential.GetAccessKeyId(), + DurationSeconds = (int)sessionDurationSeconds, + Protocol = ProtocolType.HTTPS + }; + + var response = await stsClient.GetAcsResponseAsync(request, cancellationToken).ConfigureAwait(false); + + return new BasicSessionCredentials( + response.SessionAccesskey.SessionAccessKeyId, + response.SessionAccesskey.SessionAccessKeySecert, + null, sessionDurationSeconds + ); + } } } diff --git a/aliyun-net-sdk-core/Auth/Provider/STSAssumeRoleSessionCredentialsProvider.cs b/aliyun-net-sdk-core/Auth/Provider/STSAssumeRoleSessionCredentialsProvider.cs index ed4705100a..b63f2cbbd4 100644 --- a/aliyun-net-sdk-core/Auth/Provider/STSAssumeRoleSessionCredentialsProvider.cs +++ b/aliyun-net-sdk-core/Auth/Provider/STSAssumeRoleSessionCredentialsProvider.cs @@ -1,4 +1,4 @@ -/* +/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -18,7 +18,8 @@ */ using System; - +using System.Threading; +using System.Threading.Tasks; using Aliyun.Acs.Core.Auth.Sts; using Aliyun.Acs.Core.Profile; using Aliyun.Acs.Core.Utils; @@ -111,6 +112,16 @@ public AlibabaCloudCredentials GetCredentials() return credentials; } + public async Task GetCredentialsAsync(CancellationToken cancellationToken) + { + if (credentials == null || credentials.WillSoonExpire()) + { + credentials = await GetNewSessionCredentialsAsync().ConfigureAwait(false); + } + + return credentials; + } + public void WithRoleSessionName(string roleSessionName) { this.roleSessionName = roleSessionName; @@ -158,5 +169,27 @@ private BasicSessionCredentials GetNewSessionCredentials() response.Credentials.SecurityToken, roleSessionDurationSeconds ); } + + private async Task GetNewSessionCredentialsAsync() + { + var assumeRoleRequest = new AssumeRoleRequest + { + RoleArn = roleArn, + RoleSessionName = roleSessionName, + DurationSeconds = roleSessionDurationSeconds + }; + + if (!string.IsNullOrEmpty(policy)) + { + assumeRoleRequest.Policy = policy; + } + + var response = await stsClient.GetAcsResponseAsync(assumeRoleRequest).ConfigureAwait(false); + return new BasicSessionCredentials( + response.Credentials.AccessKeyId, + response.Credentials.AccessKeySecret, + response.Credentials.SecurityToken, roleSessionDurationSeconds + ); + } } } diff --git a/aliyun-net-sdk-core/Auth/Provider/StaticCredentialsProvider.cs b/aliyun-net-sdk-core/Auth/Provider/StaticCredentialsProvider.cs index 536c9f2e67..4eeaab6445 100644 --- a/aliyun-net-sdk-core/Auth/Provider/StaticCredentialsProvider.cs +++ b/aliyun-net-sdk-core/Auth/Provider/StaticCredentialsProvider.cs @@ -1,4 +1,4 @@ -/* +/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -17,6 +17,8 @@ * under the License. */ +using System.Threading; +using System.Threading.Tasks; using Aliyun.Acs.Core.Profile; namespace Aliyun.Acs.Core.Auth @@ -52,5 +54,10 @@ public AlibabaCloudCredentials GetCredentials() { return credentials; } + + public Task GetCredentialsAsync(CancellationToken cancellationToken) + { + return Task.FromResult(credentials); + } } } diff --git a/aliyun-net-sdk-core/Auth/Provider/StsCredentialProvider.cs b/aliyun-net-sdk-core/Auth/Provider/StsCredentialProvider.cs index e8e3052431..9def151ddd 100644 --- a/aliyun-net-sdk-core/Auth/Provider/StsCredentialProvider.cs +++ b/aliyun-net-sdk-core/Auth/Provider/StsCredentialProvider.cs @@ -1,4 +1,4 @@ -/* +/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -17,6 +17,9 @@ * under the License. */ +using System.Threading; +using System.Threading.Tasks; + namespace Aliyun.Acs.Core.Auth { public class StsCredentialProvider : AlibabaCloudCredentialsProvider @@ -32,5 +35,10 @@ public AlibabaCloudCredentials GetCredentials() { return stsCredential; } + + public Task GetCredentialsAsync(CancellationToken cancellationToken) + { + return Task.FromResult(stsCredential); + } } } diff --git a/aliyun-net-sdk-core/DefaultAcsClient.cs b/aliyun-net-sdk-core/DefaultAcsClient.cs index 6ccbd0d7d7..8a5af49ef3 100644 --- a/aliyun-net-sdk-core/DefaultAcsClient.cs +++ b/aliyun-net-sdk-core/DefaultAcsClient.cs @@ -24,7 +24,7 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading; - +using System.Threading.Tasks; using Aliyun.Acs.Core.Auth; using Aliyun.Acs.Core.Auth.Provider; using Aliyun.Acs.Core.Exceptions; @@ -329,7 +329,6 @@ public virtual HttpResponse DoAction(AcsRequest request, bool autoRetry, i var watch = Stopwatch.StartNew(); FormatType? requestFormatType = request.AcceptFormat; - format = requestFormatType; var domain = request.ProductDomain ?? Endpoint.FindProductDomain(regionId, request.Product, endpoints); @@ -346,7 +345,7 @@ public virtual HttpResponse DoAction(AcsRequest request, bool autoRetry, i { DictionaryUtil.Add(request.Headers, "x-acs-action", request.ActionName); } - var httpRequest = request.SignRequest(signer, credentials, format, domain); + var httpRequest = request.SignRequest(signer, credentials, requestFormatType, domain); ResolveTimeout(httpRequest, request.Product, request.Version, request.ActionName); SetHttpsInsecure(IgnoreCertificate); ResolveProxy(httpRequest, request); @@ -615,5 +614,225 @@ public static void DisableLogger() { CommonLog.DisableLogger(); } + + #region Async + + public async Task GetAcsResponseAsync(AcsRequest request, CancellationToken cancellationToken) where T : AcsResponse + { + var httpResponse = await DoActionAsync(request, cancellationToken).ConfigureAwait(false); + return ParseAcsResponse(request, httpResponse); + } + + public async Task GetAcsResponseAsync(AcsRequest request, bool autoRetry, int maxRetryNumber, CancellationToken cancellationToken) where T : AcsResponse + { + var httpResponse = await DoActionAsync(request, autoRetry, maxRetryNumber, cancellationToken).ConfigureAwait(false); + return ParseAcsResponse(request, httpResponse); + } + + public async Task GetAcsResponseAsync(AcsRequest request, IClientProfile profile, CancellationToken cancellationToken) where T : AcsResponse + { + var httpResponse = await DoActionAsync(request, profile, cancellationToken).ConfigureAwait(false); + return ParseAcsResponse(request, httpResponse); + } + + public async Task GetAcsResponseAsync(AcsRequest request, string regionId, Credential credential, CancellationToken cancellationToken) + where T : AcsResponse + { + var httpResponse = await DoActionAsync(request, regionId, credential, cancellationToken).ConfigureAwait(false); + return ParseAcsResponse(request, httpResponse); + } + + public async Task GetCommonResponseAsync(CommonRequest request, CancellationToken cancellationToken) + { + var httpResponse = await DoActionAsync(request.BuildRequest(), cancellationToken).ConfigureAwait(false); + string data = null; + if (httpResponse.Content != null) + { + data = Encoding.UTF8.GetString(httpResponse.Content); + } + + var response = new CommonResponse + { + Data = data, + HttpResponse = httpResponse, + HttpStatus = httpResponse.Status + }; + + return response; + } + + public Task DoActionAsync(AcsRequest request, CancellationToken cancellationToken) where T : AcsResponse + { + return DoActionAsync(request, AutoRetry, MaxRetryNumber, clientProfile, cancellationToken); + } + + public Task DoActionAsync(AcsRequest request, bool autoRetry, int maxRetryNumber, CancellationToken cancellationToken) + where T : AcsResponse + { + return DoActionAsync(request, autoRetry, maxRetryNumber, clientProfile, cancellationToken); + } + + public Task DoActionAsync(AcsRequest request, IClientProfile profile, CancellationToken cancellationToken) where T : AcsResponse + { + return DoActionAsync(request, AutoRetry, MaxRetryNumber, profile, cancellationToken); + } + + public async Task DoActionAsync(AcsRequest request, string regionId, Credential credential, CancellationToken cancellationToken) + where T : AcsResponse + { + var signer = Signer.GetSigner(new LegacyCredentials(credential)); + FormatType? format = null; + if (null == request.RegionId) + { + request.RegionId = regionId; + } + + if (request.ProductDomain == null) + { + request.ProductDomain = EndpointUserConfig.GetProductDomain(request.Product, request.RegionId); + if (request.ProductDomain == null) + { + request.SetProductDomain(); + } + } + + List endpoints = null; + if (null != clientProfile) + { + format = clientProfile.GetFormat(); + if (request.ProductDomain == null) + { + endpoints = await clientProfile.GetEndpointsAsync(request.Product, request.RegionId, request.LocationProduct, + request.LocationEndpointType, cancellationToken).ConfigureAwait(false); + } + } + + return await DoActionAsync(request, AutoRetry, MaxRetryNumber, request.RegionId, credential, signer, format, endpoints, cancellationToken).ConfigureAwait(false); + } + + public async Task DoActionAsync(AcsRequest request, bool autoRetry, int maxRetryNumber, IClientProfile profile, CancellationToken cancellationToken) where T : AcsResponse + { + if (null == profile) + { + throw new ClientException("SDK.InvalidProfile", "No active profile found."); + } + + var retry = autoRetry; + var retryNumber = maxRetryNumber; + var region = profile.GetRegionId(); + if (null == request.RegionId) + { + request.RegionId = region; + } + + if (request.ProductDomain == null) + { + request.ProductDomain = EndpointUserConfig.GetProductDomain(request.Product, request.RegionId); + if (request.ProductDomain == null) + { + request.SetProductDomain(); + } + } + + var credentials = await credentialsProvider.GetCredentialsAsync(cancellationToken).ConfigureAwait(false); + if (credentials == null) + { + credentials = await new DefaultCredentialProvider().GetAlibabaCloudClientCredentialAsync(cancellationToken).ConfigureAwait(false); + } + + var signer = Signer.GetSigner(credentials); + var format = profile.GetFormat(); + List endpoints = null; + + if (request.ProductDomain == null) + { + endpoints = await clientProfile.GetEndpointsAsync(request.Product, request.RegionId, + request.LocationProduct, + request.LocationEndpointType, cancellationToken).ConfigureAwait(false); + } + + return await DoActionAsync(request, retry, retryNumber, request.RegionId, credentials, signer, format, endpoints, cancellationToken).ConfigureAwait(false); + } + + public Task DoActionAsync(AcsRequest request, bool autoRetry, int maxRetryNumber, string regionId, + Credential credential, Signer signer, FormatType? format, List endpoints, CancellationToken cancellationToken) where T : AcsResponse + { + return DoActionAsync(request, autoRetry, maxRetryNumber, regionId, new LegacyCredentials(credential), signer, + format, endpoints, cancellationToken); + } + + public virtual async Task DoActionAsync(AcsRequest request, bool autoRetry, int maxRetryNumber, + string regionId, + AlibabaCloudCredentials credentials, Signer signer, FormatType? format, List endpoints, + CancellationToken cancellationToken) + where T : AcsResponse + { + var httpStatusCode = ""; + var retryAttemptTimes = 0; + ClientException exception; + RetryPolicyContext retryPolicyContext; + + do + { + try + { + var watch = Stopwatch.StartNew(); + + FormatType? requestFormatType = request.AcceptFormat; + + var domain = request.ProductDomain ?? + Endpoint.FindProductDomain(regionId, request.Product, endpoints); + + if (null == domain) + { + throw new ClientException("SDK.InvalidRegionId", "Can not find endpoint to access."); + } + + var userAgent = UserAgent.Resolve(request.GetSysUserAgentConfig(), userAgentConfig); + DictionaryUtil.Add(request.Headers, "User-Agent", userAgent); + DictionaryUtil.Add(request.Headers, "x-acs-version", request.Version); + if (!string.IsNullOrWhiteSpace(request.ActionName)) + { + DictionaryUtil.Add(request.Headers, "x-acs-action", request.ActionName); + } + var httpRequest = request.SignRequest(signer, credentials, requestFormatType, domain); + ResolveTimeout(httpRequest, request.Product, request.Version, request.ActionName); + SetHttpsInsecure(IgnoreCertificate); + ResolveProxy(httpRequest, request); + var response = await GetResponseAsync(httpRequest, cancellationToken).ConfigureAwait(false); + + httpStatusCode = response.Status.ToString(); + PrintHttpDebugMsg(request, response); + watch.Stop(); + CommonLog.ExecuteTime = watch.ElapsedMilliseconds; + return response; + } + catch (ClientException ex) + { + retryPolicyContext = new RetryPolicyContext(ex, httpStatusCode, retryAttemptTimes, request.Product, + request.Version, + request.ActionName, RetryCondition.BlankStatus); + + CommonLog.LogException(ex, ex.ErrorCode, ex.ErrorMessage); + exception = ex; + } + + await Task.Delay(retryPolicy.GetDelayTimeBeforeNextRetry(retryPolicyContext), cancellationToken).ConfigureAwait(false); + } while ((retryPolicy.ShouldRetry(retryPolicyContext) & RetryCondition.NoRetry) != RetryCondition.NoRetry); + + if (exception != null) + { + CommonLog.LogException(exception, exception.ErrorCode, exception.ErrorMessage); + throw new ClientException(exception.ErrorCode, exception.ErrorMessage); + } + + return null; + } + + public virtual Task GetResponseAsync(HttpRequest httpRequest, CancellationToken cancellationToken) + { + return HttpResponse.GetResponseAsync(httpRequest, cancellationToken); + } + #endregion } } diff --git a/aliyun-net-sdk-core/Http/HttpResponse.cs b/aliyun-net-sdk-core/Http/HttpResponse.cs index d6fbb10881..2e458b3afc 100644 --- a/aliyun-net-sdk-core/Http/HttpResponse.cs +++ b/aliyun-net-sdk-core/Http/HttpResponse.cs @@ -21,7 +21,8 @@ using System.Collections.Generic; using System.IO; using System.Net; - +using System.Threading; +using System.Threading.Tasks; using Aliyun.Acs.Core.Exceptions; using Aliyun.Acs.Core.Utils; @@ -88,24 +89,14 @@ public static byte[] ReadContent(HttpResponse response, HttpWebResponse rsp) using (var ms = new MemoryStream()) using (var stream = rsp.GetResponseStream()) { + if (stream == null) { - var buffer = new byte[bufferLength]; - while (true) - { - var length = stream.Read(buffer, 0, bufferLength); - if (length == 0) - { - break; - } + return new byte[0]; + } - ms.Write(buffer, 0, length); - } + stream.CopyTo(ms, bufferLength); - ms.Seek(0, SeekOrigin.Begin); - var bytes = new byte[ms.Length]; - ms.Read(bytes, 0, bytes.Length); - return bytes; - } + return ms.ToArray(); } } @@ -230,5 +221,203 @@ public bool isSuccess() { return 200 <= Status && 300 > Status; } + + #region Async + + private static async Task ParseHttpResponseAsync(HttpResponse httpResponse, HttpWebResponse httpWebResponse, CancellationToken cancellationToken) + { + httpResponse.Content = await ReadContentAsync(httpResponse, httpWebResponse, cancellationToken).ConfigureAwait(false); + httpResponse.Status = (int)httpWebResponse.StatusCode; + httpResponse.Headers = new Dictionary(); + httpResponse.Method = ParameterHelper.StringToMethodType(httpWebResponse.Method); + httpResponse.HttpVersion = httpWebResponse.ProtocolVersion.ToString(); + + foreach (var key in httpWebResponse.Headers.AllKeys) + { + httpResponse.Headers.Add(key, httpWebResponse.Headers[key]); + } + + var contentType = DictionaryUtil.Get(httpResponse.Headers, "Content-Type"); + + if (null != contentType) + { + httpResponse.Encoding = "UTF-8"; + var split = contentType.Split(';'); + httpResponse.ContentType = ParameterHelper.StingToFormatType(split[0].Trim()); + if (split.Length > 1 && split[1].Contains("=")) + { + var codings = split[1].Split('='); + httpResponse.Encoding = codings[1].Trim().ToUpper(); + } + } + } + + public static async Task ReadContentAsync(HttpResponse response, HttpWebResponse rsp, CancellationToken cancellationToken) + { + using (var ms = new MemoryStream()) + using (var stream = rsp.GetResponseStream()) + { + if (stream == null) + { + return new byte[0]; + } + + await stream.CopyToAsync(ms, bufferLength, cancellationToken).ConfigureAwait(false); + + return ms.ToArray(); + } + } + + public static Task GetResponseAsync(HttpRequest request) + { + return GetResponseAsync(request, default(CancellationToken), null); + } + + public static Task GetResponseAsync(HttpRequest request, CancellationToken cancellationToken) + { + return GetResponseAsync(request, cancellationToken, null); + } + + public static async Task GetResponseAsync(HttpRequest request, CancellationToken cancellationToken, int? timeout) + { + var result = await GetWebRequestAsync(request, timeout, cancellationToken).ConfigureAwait(false); + + var httpWebRequest = result.Item1; + + HttpWebResponse httpWebResponse; + var httpResponse = new HttpResponse(httpWebRequest.RequestUri.AbsoluteUri); + + try + { + using (httpWebResponse = (HttpWebResponse)await httpWebRequest.GetResponseAsync().ConfigureAwait(false)) + { + await ParseHttpResponseAsync(httpResponse, httpWebResponse, cancellationToken).ConfigureAwait(false); + return httpResponse; + } + } + catch (WebException ex) + { + if (ex.Response != null) + { + using (httpWebResponse = ex.Response as HttpWebResponse) + { + await ParseHttpResponseAsync(httpResponse, httpWebResponse, cancellationToken).ConfigureAwait(false); + return httpResponse; + } + } + + if (result.Item2.IsCancellationRequested) + { + throw new ClientException("SDK.WebException", + string.Format("HttpWebRequest timeout, the request url is {0} {1}", + httpWebRequest.RequestUri == null ? "empty" : httpWebRequest.RequestUri.Host, ex)); + } + + throw new ClientException("SDK.WebException", + string.Format("HttpWebRequest WebException occured, the request url is {0} {1}", + httpWebRequest.RequestUri == null ? "empty" : httpWebRequest.RequestUri.Host, ex)); + } + catch (IOException ex) + { + throw new ClientException("SDK.ServerUnreachable:", + string.Format("Server unreachable: connection to url: {0} failed. {1}", + httpWebRequest.RequestUri == null ? "empty" : httpWebRequest.RequestUri.Host, + ex)); + } + catch (Exception ex) + { + throw new ClientException("SDK.Exception", + string.Format("The request url is {0} {1}", + httpWebRequest.RequestUri == null ? "empty" : httpWebRequest.RequestUri.Host, ex)); + } + finally + { + result.Item2.Dispose(); + } + } + + private static async Task> GetWebRequestAsync(HttpRequest request, int? timeout, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var uri = new Uri(request.Url); + var httpWebRequest = (HttpWebRequest)WebRequest.Create(uri); + + httpWebRequest.Proxy = request.WebProxy; + httpWebRequest.Method = request.Method.ToString(); + httpWebRequest.KeepAlive = true; + + httpWebRequest.Timeout = timeout ?? (request.ConnectTimeout > 0 + ? request.ConnectTimeout + : DEFAULT_CONNECT_TIMEOUT_In_MilliSeconds); + + httpWebRequest.ReadWriteTimeout = + request.ReadTimeout > 0 ? request.ReadTimeout : DEFAULT_READ_TIMEOUT_IN_MilliSeconds; + + if (request.IgnoreCertificate) + { + httpWebRequest.ServerCertificateValidationCallback = (s, cert, chains, sslPolicyError) => true; + } + + if (DictionaryUtil.Get(request.Headers, "Accept") != null) + { + httpWebRequest.Accept = DictionaryUtil.Pop(request.Headers, "Accept"); + } + + if (DictionaryUtil.Get(request.Headers, "Date") != null) + { + var headerDate = DictionaryUtil.Pop(request.Headers, "Date"); + httpWebRequest.Date = Convert.ToDateTime(headerDate); + } + + foreach (var header in request.Headers) + { + if (header.Key.Equals("Content-Length")) + { + httpWebRequest.ContentLength = long.Parse(header.Value); + continue; + } + + if (header.Key.Equals("Content-Type")) + { + httpWebRequest.ContentType = header.Value; + continue; + } + + if (header.Key.Equals("User-Agent")) + { + httpWebRequest.UserAgent = header.Value; + continue; + } + + httpWebRequest.Headers.Add(header.Key, header.Value); + } + + var cts = cancellationToken.CanBeCanceled ? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken) : new CancellationTokenSource(); + + cts.CancelAfter(timeout > 0 ? timeout.Value : Math.Max(httpWebRequest.Timeout, httpWebRequest.ReadWriteTimeout)); + + cts.Token.Register(httpWebRequest.Abort); + + if ((request.Method == MethodType.POST || request.Method == MethodType.PUT) && request.Content != null) + { + try + { + using (var stream = await httpWebRequest.GetRequestStreamAsync().ConfigureAwait(false)) + { + await stream.WriteAsync(request.Content, 0, request.Content.Length, cancellationToken).ConfigureAwait(false); + } + } + catch + { + cts.Dispose(); + + throw; + } + } + + return Tuple.Create(httpWebRequest, cts); + } + #endregion } } diff --git a/aliyun-net-sdk-core/IAcsClient.cs b/aliyun-net-sdk-core/IAcsClient.cs index 4ea63403dc..5464e62b85 100644 --- a/aliyun-net-sdk-core/IAcsClient.cs +++ b/aliyun-net-sdk-core/IAcsClient.cs @@ -1,4 +1,4 @@ -/* +/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -18,7 +18,8 @@ */ using System.Collections.Generic; - +using System.Threading; +using System.Threading.Tasks; using Aliyun.Acs.Core.Auth; using Aliyun.Acs.Core.Http; using Aliyun.Acs.Core.Profile; @@ -28,6 +29,7 @@ namespace Aliyun.Acs.Core { public interface IAcsClient { + #region Sync T GetAcsResponse(AcsRequest request) where T : AcsResponse; T GetAcsResponse(AcsRequest request, bool autoRetry, int maxRetryCounts) where T : AcsResponse; @@ -54,5 +56,69 @@ HttpResponse DoAction(AcsRequest request, string regionId, Credential credential, Signer signer, FormatType? format, List endpoints) where T : AcsResponse; + #endregion + + #region Async + + Task GetAcsResponseAsync(AcsRequest request, + CancellationToken cancellationToken) + where T : AcsResponse; + + Task GetAcsResponseAsync(AcsRequest request, + bool autoRetry, + int maxRetryCounts, + CancellationToken cancellationToken) + where T : AcsResponse; + + Task GetAcsResponseAsync(AcsRequest request, + IClientProfile profile, + CancellationToken cancellationToken) + where T : AcsResponse; + + Task GetAcsResponseAsync(AcsRequest request, + string regionId, + Credential credential, + CancellationToken cancellationToken) + where T : AcsResponse; + + Task GetCommonResponseAsync(CommonRequest request, + CancellationToken cancellationToken); + + Task DoActionAsync(AcsRequest request, + CancellationToken cancellationToken) + where T : AcsResponse; + + Task DoActionAsync(AcsRequest request, + bool autoRetry, + int maxRetryCounts, + CancellationToken cancellationToken) + where T : AcsResponse; + + Task DoActionAsync(AcsRequest request, + IClientProfile profile, + CancellationToken cancellationToken) + where T : AcsResponse; + + Task DoActionAsync(AcsRequest request, + string regionId, + Credential credential, + CancellationToken cancellationToken) + where T : AcsResponse; + + Task DoActionAsync(AcsRequest request, + bool autoRetry, + int maxRetryCounts, + IClientProfile profile, + CancellationToken cancellationToken) + where T : AcsResponse; + + Task DoActionAsync(AcsRequest request, + bool autoRetry, int maxRetryNumber, + string regionId, Credential credential, + Signer signer, FormatType? format, + List endpoints, + CancellationToken cancellationToken) + where T : AcsResponse; + #endregion } } diff --git a/aliyun-net-sdk-core/Profile/DefaultProfile.cs b/aliyun-net-sdk-core/Profile/DefaultProfile.cs index e3a6fbfdf6..c5e7ddcb2b 100644 --- a/aliyun-net-sdk-core/Profile/DefaultProfile.cs +++ b/aliyun-net-sdk-core/Profile/DefaultProfile.cs @@ -19,7 +19,8 @@ using System; using System.Collections.Generic; - +using System.Threading; +using System.Threading.Tasks; using Aliyun.Acs.Core.Auth; using Aliyun.Acs.Core.Http; using Aliyun.Acs.Core.Regions; @@ -100,11 +101,21 @@ public List GetEndpoints(string product, string regionId, string servi return endpointResolve.Resolve(product, regionId, serviceCode, endpointType, _credential, locationConfig); } + public async Task> GetEndpointsAsync(string product, string regionId, string serviceCode, string endpointType, CancellationToken cancellationToken) + { + return await endpointResolve.ResolveAsync(product, regionId, serviceCode, endpointType, _credential, locationConfig, cancellationToken).ConfigureAwait(false); + } + public List GetEndpoints(string regionId, string product) { return endpointResolve.GetEndpoints(regionId, product); } + public Task> GetEndpointsAsync(string regionId, string product, CancellationToken cancellationToken) + { + return endpointResolve.GetEndpointsAsync(regionId, product, cancellationToken); + } + public void AddEndpoint(string endpointName, string regionId, string product, string domain, bool isNeverExpire = false) { @@ -112,6 +123,14 @@ public void AddEndpoint(string endpointName, string regionId, string product, st endpointResolve.AddEndpoint(endpointName, regionId, product, domain, isNeverExpire); } + public Task AddEndpointAsync(string endpointName, string regionId, string product, string domain, + bool isNeverExpire, + CancellationToken cancellationToken) + { + EndpointUserConfig.AddEndpoint(product, regionId, domain); + return endpointResolve.AddEndpointAsync(endpointName, regionId, product, domain, isNeverExpire, cancellationToken); + } + public void SetCredentialsProvider(AlibabaCloudCredentialsProvider credentialsProvider) { if (_credential != null) diff --git a/aliyun-net-sdk-core/Profile/IClientProfile.cs b/aliyun-net-sdk-core/Profile/IClientProfile.cs index edee601ee9..da810b1129 100644 --- a/aliyun-net-sdk-core/Profile/IClientProfile.cs +++ b/aliyun-net-sdk-core/Profile/IClientProfile.cs @@ -18,7 +18,8 @@ */ using System.Collections.Generic; - +using System.Threading; +using System.Threading.Tasks; using Aliyun.Acs.Core.Auth; using Aliyun.Acs.Core.Http; using Aliyun.Acs.Core.Regions; @@ -38,6 +39,8 @@ public interface IClientProfile List GetEndpoints(string product, string regionId, string serviceCode, string endpointType); + Task> GetEndpointsAsync(string product, string regionId, string serviceCode, string endpointType, CancellationToken cancellationToken); + void SetLocationConfig(string regionId, string product, string endpoint); void SetCredentialsProvider(AlibabaCloudCredentialsProvider credentialsProvider); @@ -45,5 +48,9 @@ public interface IClientProfile void AddEndpoint(string endpointName, string regionId, string product, string domain, bool isNeverExpire = false); + Task AddEndpointAsync(string endpointName, string regionId, string product, string domain, + bool isNeverExpire, + CancellationToken cancellationToken); + } } diff --git a/aliyun-net-sdk-core/Regions/DescribeEndpointService.cs b/aliyun-net-sdk-core/Regions/DescribeEndpointService.cs index cfcea8d6b9..17f99fc9c2 100644 --- a/aliyun-net-sdk-core/Regions/DescribeEndpointService.cs +++ b/aliyun-net-sdk-core/Regions/DescribeEndpointService.cs @@ -1,4 +1,4 @@ -/* +/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -17,6 +17,8 @@ * under the License. */ +using System.Threading; +using System.Threading.Tasks; using Aliyun.Acs.Core.Auth; using Aliyun.Acs.Core.Regions.Location; using Aliyun.Acs.Core.Regions.Location.Model; @@ -28,5 +30,10 @@ internal interface DescribeEndpointService DescribeEndpointResponse DescribeEndpoint(string regionId, string serviceCode, string endpointType, Credential credential, LocationConfig locationConfig); + + Task DescribeEndpointAsync(string regionId, string serviceCode, string endpointType, + Credential credential, + LocationConfig locationConfig, + CancellationToken cancellationToken = default(CancellationToken)); } } diff --git a/aliyun-net-sdk-core/Regions/DescribeEndpointServiceImpl.cs b/aliyun-net-sdk-core/Regions/DescribeEndpointServiceImpl.cs index fa4e7363f3..8b7cad997d 100644 --- a/aliyun-net-sdk-core/Regions/DescribeEndpointServiceImpl.cs +++ b/aliyun-net-sdk-core/Regions/DescribeEndpointServiceImpl.cs @@ -20,7 +20,8 @@ using System; using System.Runtime.CompilerServices; using System.Text; - +using System.Threading; +using System.Threading.Tasks; using Aliyun.Acs.Core.Auth; using Aliyun.Acs.Core.Http; using Aliyun.Acs.Core.Reader; @@ -87,6 +88,59 @@ public DescribeEndpointResponse DescribeEndpoint(string regionId, string service return null; } + public async Task DescribeEndpointAsync(string regionId, string serviceCode, string endpointType, + Credential credential, LocationConfig locationConfig, + CancellationToken cancellationToken = default(CancellationToken)) + { + if (string.IsNullOrEmpty(serviceCode)) + { + return null; + } + + if (string.IsNullOrEmpty(endpointType)) + { + endpointType = DEFAULT_ENDPOINT_TYPE; + } + + var request = new DescribeEndpointRequest + { + AcceptFormat = FormatType.JSON, + Id = regionId, + RegionId = locationConfig.RegionId, + LocationProduct = serviceCode, + SecurityToken = credential.SecurityToken, + EndpointType = endpointType + }; + + var signer = Signer.GetSigner(new LegacyCredentials(credential)); + var domain = new ProductDomain(locationConfig.Product, locationConfig.Endpoint); + + var httpRequest = request.SignRequest(signer, credential, FormatType.JSON, domain); + httpRequest.SetConnectTimeoutInMilliSeconds(100000); + httpRequest.SetReadTimeoutInMilliSeconds(100000); + var httpResponse = await GetResponseAsync(httpRequest, cancellationToken).ConfigureAwait(false); + + if (httpResponse.isSuccess()) + { + var data = Encoding.UTF8.GetString(httpResponse.Content); + var response = GetEndpointResponse(data, endpointType); + if (response == null || string.IsNullOrEmpty(response.Endpoint)) + { + return null; + } + + return response; + } + + var error = ReadError(httpResponse, FormatType.JSON); + if (500 <= httpResponse.Status) + { + return null; + } + + return null; + } + private DescribeEndpointResponse GetEndpointResponse(string data, string endpointType) { var reader = ReaderFactory.CreateInstance(FormatType.JSON); @@ -144,5 +198,10 @@ public virtual HttpResponse GetResponse(HttpRequest httpRequest) { return HttpResponse.GetResponse(httpRequest); } + + public virtual Task GetResponseAsync(HttpRequest httpRequest, CancellationToken cancellationToken) + { + return HttpResponse.GetResponseAsync(httpRequest, cancellationToken); + } } } diff --git a/aliyun-net-sdk-core/Regions/Endpoints/EndpointResolve.cs b/aliyun-net-sdk-core/Regions/Endpoints/EndpointResolve.cs index ebcf62375a..ca6500e073 100644 --- a/aliyun-net-sdk-core/Regions/Endpoints/EndpointResolve.cs +++ b/aliyun-net-sdk-core/Regions/Endpoints/EndpointResolve.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; - +using System.Threading; +using System.Threading.Tasks; using Aliyun.Acs.Core.Auth; using Aliyun.Acs.Core.Exceptions; -using Aliyun.Acs.Core.Profile; using Aliyun.Acs.Core.Regions.Location; using Aliyun.Acs.Core.Utils; @@ -187,5 +187,126 @@ private static ProductDomain FindProductDomain(List productDomain return null; } + + #region Async + + public async Task> ResolveAsync(string product, string regionId, string serviceCode, string endpointType, + Credential credential, LocationConfig locationConfig, CancellationToken cancellationToken) + { + try + { + if (product == null) + { + return _endpoints; + } + + if (_endpoints.FirstOrDefault(x => x.Name == product) != null) + { + var endpoint = await internalEndpointProvider.GetEndpointAsync(regionId, product, cancellationToken).ConfigureAwait(false); + + if (serviceCode != null && endpoint == null) + { + endpoint = await remoteEndpointProvider.GetEndpointAsync(regionId, product, serviceCode, endpointType, + credential, locationConfig, cancellationToken).ConfigureAwait(false); + } + + if (endpoint != null) + { + foreach (var region in endpoint.RegionIds) + { + foreach (var productDomain in endpoint.ProductDomains.ToList()) + { + AddEndpoint(endpoint.Name, region, product, productDomain.DomainName); + CacheTimeHelper.AddLastClearTimePerProduct(product, region, DateTime.UtcNow); + } + } + } + else + { + throw new ClientException("SDK.InvalidRegionId", "Can not find endpoint to access."); + } + } + else if (Endpoint.FindProductDomain(regionId, product, _endpoints) == null || + CacheTimeHelper.CheckCacheIsExpire(product, regionId)) + { + var endpoint = await internalEndpointProvider.GetEndpointAsync(regionId, product, cancellationToken).ConfigureAwait(false); + + if (serviceCode != null && endpoint == null) + { + endpoint = await remoteEndpointProvider.GetEndpointAsync(regionId, product, serviceCode, endpointType, + credential, locationConfig, cancellationToken).ConfigureAwait(false); + } + + if (endpoint != null) + { + foreach (var region in endpoint.RegionIds) + { + foreach (var productDomain in endpoint.ProductDomains.ToList()) + { + AddEndpoint(endpoint.Name, region, product, productDomain.DomainName); + CacheTimeHelper.AddLastClearTimePerProduct(product, region, DateTime.UtcNow); + } + } + } + } + } + catch (ClientException ex) + { + CommonLog.LogException(ex, ex.ErrorCode, ex.ErrorMessage); + throw new ClientException(ex.ErrorCode, ex.ErrorMessage); + } + + return _endpoints; + } + + public async Task AddEndpointAsync(string endpointName, string regionId, string product, string domain, + bool isNeverExpire = false, + CancellationToken cancellationToken = default(CancellationToken)) + { + if (0 == _endpoints.Count) + { + _endpoints = await GetEndpointsAsync(regionId, product, cancellationToken).ConfigureAwait(false); + } + + var endpoint = FindEndpointByRegionId(regionId); + if (null == endpoint) + { + var regions = new HashSet { regionId }; + + var productDomains = new List { new ProductDomain(product, domain) }; + endpoint = new Endpoint(endpointName, regions, productDomains); + if (_endpoints == null) + { + _endpoints = new List(); + } + + _endpoints.Add(endpoint); + } + else + { + UpdateEndpoint(regionId, product, domain, endpoint); + } + + if (isNeverExpire) + { + var date = DateTime.UtcNow.AddYears(100); + CacheTimeHelper.AddLastClearTimePerProduct(product, regionId, date); + } + } + + public async Task> GetEndpointsAsync(string regionId, string product, CancellationToken cancellationToken) + { + if (null == _endpoints) + { + var endpoint = await internalEndpointProvider.GetEndpointAsync(regionId, product, cancellationToken).ConfigureAwait(false); + if (endpoint != null) + { + _endpoints = new List { endpoint }; + } + } + + return _endpoints; + } + #endregion } } diff --git a/aliyun-net-sdk-core/Regions/IEndpointsProvider.cs b/aliyun-net-sdk-core/Regions/IEndpointsProvider.cs index c550c16608..71ea6c0b43 100644 --- a/aliyun-net-sdk-core/Regions/IEndpointsProvider.cs +++ b/aliyun-net-sdk-core/Regions/IEndpointsProvider.cs @@ -1,4 +1,4 @@ -/* +/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -17,6 +17,8 @@ * under the License. */ +using System.Threading; +using System.Threading.Tasks; using Aliyun.Acs.Core.Auth; using Aliyun.Acs.Core.Regions.Location; @@ -29,5 +31,13 @@ internal interface IEndpointsProvider Endpoint GetEndpoint(string region, string product, string serviceCode, string endpointType, Credential credential, LocationConfig locationConfig); + + Task GetEndpointAsync(string region, string product, + CancellationToken cancellationToken = default(CancellationToken)); + + Task GetEndpointAsync(string region, string product, string serviceCode, string endpointType, + Credential credential, + LocationConfig locationConfig, + CancellationToken cancellationToken = default(CancellationToken)); } } diff --git a/aliyun-net-sdk-core/Regions/InternalEndpointsParser.cs b/aliyun-net-sdk-core/Regions/InternalEndpointsParser.cs index dc60866f39..a07c2ec0f6 100644 --- a/aliyun-net-sdk-core/Regions/InternalEndpointsParser.cs +++ b/aliyun-net-sdk-core/Regions/InternalEndpointsParser.cs @@ -19,11 +19,9 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Reflection; - +using System.Threading; +using System.Threading.Tasks; using Aliyun.Acs.Core.Auth; -using Aliyun.Acs.Core.Exceptions; using Aliyun.Acs.Core.Regions.Location; namespace Aliyun.Acs.Core.Regions @@ -68,6 +66,16 @@ public Endpoint GetEndpoint(string region, string product, string serviceCode, s throw new NotSupportedException(); } + public Task GetEndpointAsync(string region, string product, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(GetEndpoint(region, product)); + } + + public Task GetEndpointAsync(string region, string product, string serviceCode, string endpointType, Credential credential, LocationConfig locationConfig, CancellationToken cancellationToken = default(CancellationToken)) + { + throw new NotSupportedException(); + } + internal class Product { public string Code { get; set; } diff --git a/aliyun-net-sdk-core/Regions/RemoteEndpointsParser.cs b/aliyun-net-sdk-core/Regions/RemoteEndpointsParser.cs index 46e3a73a48..77a9472cbb 100644 --- a/aliyun-net-sdk-core/Regions/RemoteEndpointsParser.cs +++ b/aliyun-net-sdk-core/Regions/RemoteEndpointsParser.cs @@ -19,7 +19,8 @@ using System; using System.Collections.Generic; - +using System.Threading; +using System.Threading.Tasks; using Aliyun.Acs.Core.Auth; using Aliyun.Acs.Core.Regions.Location; @@ -47,12 +48,39 @@ public Endpoint GetEndpoint(string regionId, string product, string serviceCode, return null; } - Endpoint endpoint = null; - var response = describeEndpointService.DescribeEndpoint(regionId, serviceCode, endpointType, credential, locationConfig); if (response == null) { - return endpoint; + return null; + } + + ISet regionIds = new HashSet(); + regionIds.Add(regionId); + + var productDomainList = new List(); + productDomainList.Add(new ProductDomain(product, response.Endpoint)); + + return new Endpoint(response.RegionId, regionIds, productDomainList); + } + + public Task GetEndpointAsync(string region, string product, CancellationToken cancellationToken = default(CancellationToken)) + { + throw new NotSupportedException(); + } + + public async Task GetEndpointAsync(string regionId, string product, string serviceCode, string endpointType, + Credential credential, LocationConfig locationConfig, + CancellationToken cancellationToken = default(CancellationToken)) + { + if (serviceCode == null) + { + return null; + } + + var response = await describeEndpointService.DescribeEndpointAsync(regionId, serviceCode, endpointType, credential, locationConfig, cancellationToken).ConfigureAwait(false); + if (response == null) + { + return null; } ISet regionIds = new HashSet(); @@ -61,8 +89,7 @@ public Endpoint GetEndpoint(string regionId, string product, string serviceCode, var productDomainList = new List(); productDomainList.Add(new ProductDomain(product, response.Endpoint)); - endpoint = new Endpoint(response.RegionId, regionIds, productDomainList); - return endpoint; + return new Endpoint(response.RegionId, regionIds, productDomainList); } } } diff --git a/aliyun-net-sdk-core/aliyun-net-sdk-core.vs2017.csproj b/aliyun-net-sdk-core/aliyun-net-sdk-core.vs2017.csproj index fe11f49ff3..dc9b5a8389 100644 --- a/aliyun-net-sdk-core/aliyun-net-sdk-core.vs2017.csproj +++ b/aliyun-net-sdk-core/aliyun-net-sdk-core.vs2017.csproj @@ -15,7 +15,7 @@ aliyun-net-sdk-core false - 4 + 6 @@ -42,19 +42,16 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive all - + all runtime; build; native; contentfiles; analyzers - - - - \ No newline at end of file diff --git a/aliyun-sdk-feature-test/Timeout/TimeoutTest.Async.cs b/aliyun-sdk-feature-test/Timeout/TimeoutTest.Async.cs new file mode 100644 index 0000000000..740b30d3fe --- /dev/null +++ b/aliyun-sdk-feature-test/Timeout/TimeoutTest.Async.cs @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 System; +using System.Threading; +using System.Threading.Tasks; +using Aliyun.Acs.Core; +using Aliyun.Acs.Core.Exceptions; +using Aliyun.Acs.Core.Http; +using Aliyun.Acs.Vpc.Model.V20160428; + +using Xunit; + +namespace Aliyun.Acs.Feature.Test.Timeout +{ + [Trait("Category", "FeatureTest")] + public class AsyncTimeoutTest + { + [Fact] + public async Task TestConnectTimeoutWithException() + { + var request = new HttpRequest("https://alibaba.great"); + request.Method = MethodType.GET; + request.SetConnectTimeoutInMilliSeconds(1); + + var exception = await Assert.ThrowsAsync(() => HttpResponse.GetResponseAsync(request)); + + Assert.NotNull(exception.Message); + + request = new HttpRequest("https://alibaba.great"); + request.Method = MethodType.GET; + + using (var cts = new CancellationTokenSource(10)) + { + exception = await Assert.ThrowsAsync(() => HttpResponse.GetResponseAsync(request, cts.Token)); + + Assert.NotNull(exception.Message); + } + } + + [Fact] + public async Task TestVPCConnectTimeoutWithException() + { + var request = new DescribeAccessPointsRequest(); + request.SetConnectTimeoutInMilliSeconds(1); + + Exception exception = await Assert.ThrowsAsync(() => FeatureTest.DefaultClient.GetAcsResponseAsync(request)); + + Assert.NotNull(exception.Message); + + request = new DescribeAccessPointsRequest(); + + using (var cts = new CancellationTokenSource(10)) + { + exception = null; + + try + { + await FeatureTest.DefaultClient.GetAcsResponseAsync(request, cts.Token); + } + catch (Exception e) + { + exception = e; + } + + Assert.NotNull(exception); + Assert.NotNull(exception.Message); + } + } + } +}