From 23ed13eb7a4166a7d73f897a6262ad562f0cc1b1 Mon Sep 17 00:00:00 2001 From: Zitong Yang Date: Wed, 25 Oct 2023 16:24:50 +0800 Subject: [PATCH] Remove generated REST client which depends on deprecated package. * For `RestHealthCheckService` (not really used now), the HttpClient timeout is not impacted by user settings. * For `ServiceManager.IsServiceHeathy`, the http client timeout is impacted by user settings. * For AAD related REST API calls, because they uses a static `HttpClientFactory` instance, they have never impacted by the user settings. --- Directory.Build.props | 1 - build/dependencies.props | 1 - src/Directory.Build.props | 1 - .../Constants.cs | 2 + .../Exceptions/ExceptionExtensions.cs | 30 -- .../RestClients/Generated/HealthApi.cs | 157 --------- .../Generated/HealthApiExtensions.cs | 47 --- .../RestClients/Generated/IHealthApi.cs | 38 --- .../Generated/ISignalRServiceRestClient.cs | 48 --- .../Generated/SignalRServiceRestClient.cs | 319 ------------------ .../RestClients/JwtTokenCredentials.cs | 37 -- .../RestClients/RestClientFactory.cs | 35 -- .../RestClients/SignalRServiceRestClient.cs | 54 --- .../RestClients/health.json | 23 -- .../RestClients/readme.md | 19 -- .../Utilities/RestClient.cs | 3 +- .../DependencyInjectionExtensions.cs | 56 +-- .../HealthCheckOptions.cs | 18 +- .../RestHealthCheckService.cs | 131 ++++--- .../RestApiProvider.cs | 1 + .../ServiceManagerImpl.cs | 128 ++++--- .../RestClients/HealthApiFacts.cs | 46 --- .../RestClients/RestClientBuilderFacts.cs | 61 ---- .../RestClients/RestClientFacts.cs | 3 +- .../DependencyInjectionExtensionFacts.cs | 63 +++- .../RestHealthCheckServiceFacts.cs | 55 ++- .../ServiceManagerFacts.cs | 19 +- .../TestRestClientFactory.cs | 37 -- .../TestRootHandler.cs | 17 +- 29 files changed, 315 insertions(+), 1135 deletions(-) delete mode 100644 src/Microsoft.Azure.SignalR.Common/RestClients/Generated/HealthApi.cs delete mode 100644 src/Microsoft.Azure.SignalR.Common/RestClients/Generated/HealthApiExtensions.cs delete mode 100644 src/Microsoft.Azure.SignalR.Common/RestClients/Generated/IHealthApi.cs delete mode 100644 src/Microsoft.Azure.SignalR.Common/RestClients/Generated/ISignalRServiceRestClient.cs delete mode 100644 src/Microsoft.Azure.SignalR.Common/RestClients/Generated/SignalRServiceRestClient.cs delete mode 100644 src/Microsoft.Azure.SignalR.Common/RestClients/JwtTokenCredentials.cs delete mode 100644 src/Microsoft.Azure.SignalR.Common/RestClients/RestClientFactory.cs delete mode 100644 src/Microsoft.Azure.SignalR.Common/RestClients/SignalRServiceRestClient.cs delete mode 100644 src/Microsoft.Azure.SignalR.Common/RestClients/health.json delete mode 100644 src/Microsoft.Azure.SignalR.Common/RestClients/readme.md delete mode 100644 test/Microsoft.Azure.SignalR.Common.Tests/RestClients/HealthApiFacts.cs delete mode 100644 test/Microsoft.Azure.SignalR.Common.Tests/RestClients/RestClientBuilderFacts.cs delete mode 100644 test/Microsoft.Azure.SignalR.Tests.Common/TestRestClientFactory.cs diff --git a/Directory.Build.props b/Directory.Build.props index 0cbccdae9..2aef6a2cf 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -74,7 +74,6 @@ - diff --git a/build/dependencies.props b/build/dependencies.props index 8fccb3104..80e8b77ad 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -30,7 +30,6 @@ 2.1.0 2.1.1 1.1.0 - 2.3.21 diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 668762bf7..5171d0633 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -26,7 +26,6 @@ - diff --git a/src/Microsoft.Azure.SignalR.Common/Constants.cs b/src/Microsoft.Azure.SignalR.Common/Constants.cs index 790bb8633..d4c0a6287 100644 --- a/src/Microsoft.Azure.SignalR.Common/Constants.cs +++ b/src/Microsoft.Azure.SignalR.Common/Constants.cs @@ -118,6 +118,8 @@ public static class HttpClientNames { public const string Resilient = "Resilient"; public const string MessageResilient = "MessageResilient"; + public const string UserDefault = "UserDefault"; + public const string InternalDefault = "InternalDefault"; } } } \ No newline at end of file diff --git a/src/Microsoft.Azure.SignalR.Common/Exceptions/ExceptionExtensions.cs b/src/Microsoft.Azure.SignalR.Common/Exceptions/ExceptionExtensions.cs index 627eb780e..7f52aec71 100644 --- a/src/Microsoft.Azure.SignalR.Common/Exceptions/ExceptionExtensions.cs +++ b/src/Microsoft.Azure.SignalR.Common/Exceptions/ExceptionExtensions.cs @@ -2,42 +2,12 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using System.Net; -using System.Net.Http; using System.Net.WebSockets; -using Microsoft.Rest; - namespace Microsoft.Azure.SignalR.Common { internal static class ExceptionExtensions { - internal static Exception WrapAsAzureSignalRException(this Exception e, Uri baseUri) - { - switch (e) - { - case HttpOperationException operationException: - var response = operationException.Response; - var request = operationException.Request; - var detail = response.Content; - - var innerException = new HttpRequestException( - $"Response status code does not indicate success: {(int)response.StatusCode} ({response.ReasonPhrase})", operationException); - - return response.StatusCode switch - { - HttpStatusCode.BadRequest => new AzureSignalRInvalidArgumentException(request.RequestUri.ToString(), innerException, detail), - HttpStatusCode.Unauthorized => new AzureSignalRUnauthorizedException(request.RequestUri.ToString(), innerException), - HttpStatusCode.NotFound => new AzureSignalRInaccessibleEndpointException(request.RequestUri.ToString(), innerException), - _ => new AzureSignalRRuntimeException(baseUri.ToString(), innerException), - }; - case HttpRequestException requestException: - return new AzureSignalRInaccessibleEndpointException(baseUri.ToString(), requestException); - default: - return e; - } - } - internal static Exception WrapAsAzureSignalRException(this Exception e) { switch (e) diff --git a/src/Microsoft.Azure.SignalR.Common/RestClients/Generated/HealthApi.cs b/src/Microsoft.Azure.SignalR.Common/RestClients/Generated/HealthApi.cs deleted file mode 100644 index cd0bba1c4..000000000 --- a/src/Microsoft.Azure.SignalR.Common/RestClients/Generated/HealthApi.cs +++ /dev/null @@ -1,157 +0,0 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for -// license information. -// -// Code generated by Microsoft (R) AutoRest Code Generator. -// Changes may cause incorrect behavior and will be lost if the code is -// regenerated. -// - -namespace Microsoft.Azure.SignalR -{ - using Microsoft.Rest; - using System.Collections; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Net; - using System.Net.Http; - using System.Threading; - using System.Threading.Tasks; - - /// - /// HealthApi operations. - /// - public partial class HealthApi : IServiceOperations, IHealthApi - { - /// - /// Initializes a new instance of the HealthApi class. - /// - /// - /// Reference to the service client. - /// - /// - /// Thrown when a required parameter is null - /// - public HealthApi(SignalRServiceRestClient client) - { - if (client == null) - { - throw new System.ArgumentNullException("client"); - } - Client = client; - } - - /// - /// Gets a reference to the SignalRServiceRestClient - /// - public SignalRServiceRestClient Client { get; private set; } - - /// - /// Get service health status. - /// - /// - /// Headers that will be added to request. - /// - /// - /// The cancellation token. - /// - /// - /// Thrown when the operation returned an invalid status code - /// - /// - /// A response object containing the response body and response headers. - /// - public async Task GetHealthStatusWithHttpMessagesAsync(Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)) - { - // Tracing - bool _shouldTrace = ServiceClientTracing.IsEnabled; - string _invocationId = null; - if (_shouldTrace) - { - _invocationId = ServiceClientTracing.NextInvocationId.ToString(); - Dictionary tracingParameters = new Dictionary(); - tracingParameters.Add("cancellationToken", cancellationToken); - ServiceClientTracing.Enter(_invocationId, this, "GetHealthStatus", tracingParameters); - } - // Construct URL - var _baseUrl = Client.BaseUri.AbsoluteUri; - var _url = new System.Uri(new System.Uri(_baseUrl + (_baseUrl.EndsWith("/") ? "" : "/")), "api/v1/health").ToString(); - // Create HTTP transport objects - var _httpRequest = new HttpRequestMessage(); - HttpResponseMessage _httpResponse = null; - _httpRequest.Method = new HttpMethod("HEAD"); - _httpRequest.RequestUri = new System.Uri(_url); - // Set Headers - - - if (customHeaders != null) - { - foreach(var _header in customHeaders) - { - if (_httpRequest.Headers.Contains(_header.Key)) - { - _httpRequest.Headers.Remove(_header.Key); - } - _httpRequest.Headers.TryAddWithoutValidation(_header.Key, _header.Value); - } - } - - // Serialize Request - string _requestContent = null; - // Set Credentials - if (Client.Credentials != null) - { - cancellationToken.ThrowIfCancellationRequested(); - await Client.Credentials.ProcessHttpRequestAsync(_httpRequest, cancellationToken).ConfigureAwait(false); - } - // Send Request - if (_shouldTrace) - { - ServiceClientTracing.SendRequest(_invocationId, _httpRequest); - } - cancellationToken.ThrowIfCancellationRequested(); - _httpResponse = await Client.HttpClient.SendAsync(_httpRequest, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - if (_shouldTrace) - { - ServiceClientTracing.ReceiveResponse(_invocationId, _httpResponse); - } - HttpStatusCode _statusCode = _httpResponse.StatusCode; - cancellationToken.ThrowIfCancellationRequested(); - string _responseContent = null; - if ((int)_statusCode != 200) - { - var ex = new HttpOperationException(string.Format("Operation returned an invalid status code '{0}'", _statusCode)); - if (_httpResponse.Content != null) { - _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); - } - else { - _responseContent = string.Empty; - } - ex.Request = new HttpRequestMessageWrapper(_httpRequest, _requestContent); - ex.Response = new HttpResponseMessageWrapper(_httpResponse, _responseContent); - if (_shouldTrace) - { - ServiceClientTracing.Error(_invocationId, ex); - } - _httpRequest.Dispose(); - if (_httpResponse != null) - { - _httpResponse.Dispose(); - } - throw ex; - } - // Create Result - var _result = new HttpOperationResponse(); - _result.Request = _httpRequest; - _result.Response = _httpResponse; - if (_shouldTrace) - { - ServiceClientTracing.Exit(_invocationId, _result); - } - return _result; - } - - } -} diff --git a/src/Microsoft.Azure.SignalR.Common/RestClients/Generated/HealthApiExtensions.cs b/src/Microsoft.Azure.SignalR.Common/RestClients/Generated/HealthApiExtensions.cs deleted file mode 100644 index 56db722f6..000000000 --- a/src/Microsoft.Azure.SignalR.Common/RestClients/Generated/HealthApiExtensions.cs +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for -// license information. -// -// Code generated by Microsoft (R) AutoRest Code Generator. -// Changes may cause incorrect behavior and will be lost if the code is -// regenerated. -// - -namespace Microsoft.Azure.SignalR -{ - using System.Threading; - using System.Threading.Tasks; - - /// - /// Extension methods for HealthApi. - /// - public static partial class HealthApiExtensions - { - /// - /// Get service health status. - /// - /// - /// The operations group for this extension method. - /// - public static void GetHealthStatus(this IHealthApi operations) - { - operations.GetHealthStatusAsync().GetAwaiter().GetResult(); - } - - /// - /// Get service health status. - /// - /// - /// The operations group for this extension method. - /// - /// - /// The cancellation token. - /// - public static async Task GetHealthStatusAsync(this IHealthApi operations, CancellationToken cancellationToken = default(CancellationToken)) - { - (await operations.GetHealthStatusWithHttpMessagesAsync(null, cancellationToken).ConfigureAwait(false)).Dispose(); - } - - } -} diff --git a/src/Microsoft.Azure.SignalR.Common/RestClients/Generated/IHealthApi.cs b/src/Microsoft.Azure.SignalR.Common/RestClients/Generated/IHealthApi.cs deleted file mode 100644 index 7cc24b1e9..000000000 --- a/src/Microsoft.Azure.SignalR.Common/RestClients/Generated/IHealthApi.cs +++ /dev/null @@ -1,38 +0,0 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for -// license information. -// -// Code generated by Microsoft (R) AutoRest Code Generator. -// Changes may cause incorrect behavior and will be lost if the code is -// regenerated. -// - -namespace Microsoft.Azure.SignalR -{ - using Microsoft.Rest; - using System.Collections; - using System.Collections.Generic; - using System.Threading; - using System.Threading.Tasks; - - /// - /// HealthApi operations. - /// - public partial interface IHealthApi - { - /// - /// Get service health status. - /// - /// - /// The headers that will be added to request. - /// - /// - /// The cancellation token. - /// - /// - /// Thrown when the operation returned an invalid status code - /// - Task GetHealthStatusWithHttpMessagesAsync(Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)); - } -} diff --git a/src/Microsoft.Azure.SignalR.Common/RestClients/Generated/ISignalRServiceRestClient.cs b/src/Microsoft.Azure.SignalR.Common/RestClients/Generated/ISignalRServiceRestClient.cs deleted file mode 100644 index 2c65eec6b..000000000 --- a/src/Microsoft.Azure.SignalR.Common/RestClients/Generated/ISignalRServiceRestClient.cs +++ /dev/null @@ -1,48 +0,0 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for -// license information. -// -// Code generated by Microsoft (R) AutoRest Code Generator. -// Changes may cause incorrect behavior and will be lost if the code is -// regenerated. -// - -namespace Microsoft.Azure.SignalR -{ - using Microsoft.Rest; - using Newtonsoft.Json; - - /// - /// - public partial interface ISignalRServiceRestClient : System.IDisposable - { - /// - /// The base URI of the service. - /// - System.Uri BaseUri { get; set; } - - /// - /// Gets or sets json serialization settings. - /// - JsonSerializerSettings SerializationSettings { get; } - - /// - /// Gets or sets json deserialization settings. - /// - JsonSerializerSettings DeserializationSettings { get; } - - /// - /// Subscription credentials which uniquely identify client - /// subscription. - /// - ServiceClientCredentials Credentials { get; } - - - /// - /// Gets the IHealthApi. - /// - IHealthApi HealthApi { get; } - - } -} diff --git a/src/Microsoft.Azure.SignalR.Common/RestClients/Generated/SignalRServiceRestClient.cs b/src/Microsoft.Azure.SignalR.Common/RestClients/Generated/SignalRServiceRestClient.cs deleted file mode 100644 index a9bc680c9..000000000 --- a/src/Microsoft.Azure.SignalR.Common/RestClients/Generated/SignalRServiceRestClient.cs +++ /dev/null @@ -1,319 +0,0 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for -// license information. -// -// Code generated by Microsoft (R) AutoRest Code Generator. -// Changes may cause incorrect behavior and will be lost if the code is -// regenerated. -// - -namespace Microsoft.Azure.SignalR -{ - using Microsoft.Rest; - using Microsoft.Rest.Serialization; - using Newtonsoft.Json; - using System.Collections; - using System.Collections.Generic; - using System.Net; - using System.Net.Http; - - public partial class SignalRServiceRestClient : ServiceClient, ISignalRServiceRestClient - { - /// - /// The base URI of the service. - /// - public System.Uri BaseUri { get; set; } - - /// - /// Gets or sets json serialization settings. - /// - public JsonSerializerSettings SerializationSettings { get; private set; } - - /// - /// Gets or sets json deserialization settings. - /// - public JsonSerializerSettings DeserializationSettings { get; private set; } - - /// - /// Subscription credentials which uniquely identify client subscription. - /// - public ServiceClientCredentials Credentials { get; private set; } - - /// - /// Gets the IHealthApi. - /// - public virtual IHealthApi HealthApi { get; private set; } - - /// - /// Initializes a new instance of the SignalRServiceRestClient class. - /// - /// - /// HttpClient to be used - /// - /// - /// True: will dispose the provided httpClient on calling SignalRServiceRestClient.Dispose(). False: will not dispose provided httpClient - protected SignalRServiceRestClient(HttpClient httpClient, bool disposeHttpClient) : base(httpClient, disposeHttpClient) - { - Initialize(); - } - - /// - /// Initializes a new instance of the SignalRServiceRestClient class. - /// - /// - /// Optional. The delegating handlers to add to the http client pipeline. - /// - protected SignalRServiceRestClient(params DelegatingHandler[] handlers) : base(handlers) - { - Initialize(); - } - - /// - /// Initializes a new instance of the SignalRServiceRestClient class. - /// - /// - /// Optional. The http client handler used to handle http transport. - /// - /// - /// Optional. The delegating handlers to add to the http client pipeline. - /// - protected SignalRServiceRestClient(HttpClientHandler rootHandler, params DelegatingHandler[] handlers) : base(rootHandler, handlers) - { - Initialize(); - } - - /// - /// Initializes a new instance of the SignalRServiceRestClient class. - /// - /// - /// Optional. The base URI of the service. - /// - /// - /// Optional. The delegating handlers to add to the http client pipeline. - /// - /// - /// Thrown when a required parameter is null - /// - protected SignalRServiceRestClient(System.Uri baseUri, params DelegatingHandler[] handlers) : this(handlers) - { - if (baseUri == null) - { - throw new System.ArgumentNullException("baseUri"); - } - BaseUri = baseUri; - } - - /// - /// Initializes a new instance of the SignalRServiceRestClient class. - /// - /// - /// Optional. The base URI of the service. - /// - /// - /// Optional. The http client handler used to handle http transport. - /// - /// - /// Optional. The delegating handlers to add to the http client pipeline. - /// - /// - /// Thrown when a required parameter is null - /// - protected SignalRServiceRestClient(System.Uri baseUri, HttpClientHandler rootHandler, params DelegatingHandler[] handlers) : this(rootHandler, handlers) - { - if (baseUri == null) - { - throw new System.ArgumentNullException("baseUri"); - } - BaseUri = baseUri; - } - - /// - /// Initializes a new instance of the SignalRServiceRestClient class. - /// - /// - /// Required. Subscription credentials which uniquely identify client subscription. - /// - /// - /// Optional. The delegating handlers to add to the http client pipeline. - /// - /// - /// Thrown when a required parameter is null - /// - internal SignalRServiceRestClient(ServiceClientCredentials credentials, params DelegatingHandler[] handlers) : this(handlers) - { - if (credentials == null) - { - throw new System.ArgumentNullException("credentials"); - } - Credentials = credentials; - if (Credentials != null) - { - Credentials.InitializeServiceClient(this); - } - } - - /// - /// Initializes a new instance of the SignalRServiceRestClient class. - /// - /// - /// Required. Subscription credentials which uniquely identify client subscription. - /// - /// - /// HttpClient to be used - /// - /// - /// True: will dispose the provided httpClient on calling SignalRServiceRestClient.Dispose(). False: will not dispose provided httpClient - /// - /// Thrown when a required parameter is null - /// - internal SignalRServiceRestClient(ServiceClientCredentials credentials, HttpClient httpClient, bool disposeHttpClient) : this(httpClient, disposeHttpClient) - { - if (credentials == null) - { - throw new System.ArgumentNullException("credentials"); - } - Credentials = credentials; - if (Credentials != null) - { - Credentials.InitializeServiceClient(this); - } - } - - /// - /// Initializes a new instance of the SignalRServiceRestClient class. - /// - /// - /// Required. Subscription credentials which uniquely identify client subscription. - /// - /// - /// Optional. The http client handler used to handle http transport. - /// - /// - /// Optional. The delegating handlers to add to the http client pipeline. - /// - /// - /// Thrown when a required parameter is null - /// - internal SignalRServiceRestClient(ServiceClientCredentials credentials, HttpClientHandler rootHandler, params DelegatingHandler[] handlers) : this(rootHandler, handlers) - { - if (credentials == null) - { - throw new System.ArgumentNullException("credentials"); - } - Credentials = credentials; - if (Credentials != null) - { - Credentials.InitializeServiceClient(this); - } - } - - /// - /// Initializes a new instance of the SignalRServiceRestClient class. - /// - /// - /// Optional. The base URI of the service. - /// - /// - /// Required. Subscription credentials which uniquely identify client subscription. - /// - /// - /// Optional. The delegating handlers to add to the http client pipeline. - /// - /// - /// Thrown when a required parameter is null - /// - internal SignalRServiceRestClient(System.Uri baseUri, ServiceClientCredentials credentials, params DelegatingHandler[] handlers) : this(handlers) - { - if (baseUri == null) - { - throw new System.ArgumentNullException("baseUri"); - } - if (credentials == null) - { - throw new System.ArgumentNullException("credentials"); - } - BaseUri = baseUri; - Credentials = credentials; - if (Credentials != null) - { - Credentials.InitializeServiceClient(this); - } - } - - /// - /// Initializes a new instance of the SignalRServiceRestClient class. - /// - /// - /// Optional. The base URI of the service. - /// - /// - /// Required. Subscription credentials which uniquely identify client subscription. - /// - /// - /// Optional. The http client handler used to handle http transport. - /// - /// - /// Optional. The delegating handlers to add to the http client pipeline. - /// - /// - /// Thrown when a required parameter is null - /// - internal SignalRServiceRestClient(System.Uri baseUri, ServiceClientCredentials credentials, HttpClientHandler rootHandler, params DelegatingHandler[] handlers) : this(rootHandler, handlers) - { - if (baseUri == null) - { - throw new System.ArgumentNullException("baseUri"); - } - if (credentials == null) - { - throw new System.ArgumentNullException("credentials"); - } - BaseUri = baseUri; - Credentials = credentials; - if (Credentials != null) - { - Credentials.InitializeServiceClient(this); - } - } - - /// - /// An optional partial-method to perform custom initialization. - /// - partial void CustomInitialize(); - /// - /// Initializes client properties. - /// - private void Initialize() - { - HealthApi = new HealthApi(this); - BaseUri = new System.Uri("http://localhost"); - SerializationSettings = new JsonSerializerSettings - { - Formatting = Newtonsoft.Json.Formatting.Indented, - DateFormatHandling = Newtonsoft.Json.DateFormatHandling.IsoDateFormat, - DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc, - NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore, - ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize, - ContractResolver = new ReadOnlyJsonContractResolver(), - Converters = new List - { - new Iso8601TimeSpanConverter() - } - }; - DeserializationSettings = new JsonSerializerSettings - { - DateFormatHandling = Newtonsoft.Json.DateFormatHandling.IsoDateFormat, - DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc, - NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore, - ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize, - ContractResolver = new ReadOnlyJsonContractResolver(), - Converters = new List - { - new Iso8601TimeSpanConverter() - } - }; - CustomInitialize(); - } - } -} diff --git a/src/Microsoft.Azure.SignalR.Common/RestClients/JwtTokenCredentials.cs b/src/Microsoft.Azure.SignalR.Common/RestClients/JwtTokenCredentials.cs deleted file mode 100644 index 6bcd39228..000000000 --- a/src/Microsoft.Azure.SignalR.Common/RestClients/JwtTokenCredentials.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Rest; - -namespace Microsoft.Azure.SignalR -{ - internal class JwtTokenCredentials : ServiceClientCredentials - { - private readonly RestApiAccessTokenGenerator _restApiAccessTokenGenerator; - - public JwtTokenCredentials(AccessKey accessKey, string serverName = null) - { - _restApiAccessTokenGenerator = new RestApiAccessTokenGenerator(accessKey, serverName); - } - - public override async Task ProcessHttpRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - if (request == null) - { - throw new ArgumentNullException(nameof(request)); - } - var uri = request.RequestUri; - var uriWithoutPort = uri.GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped); - - var tokenString = await _restApiAccessTokenGenerator.Generate(uriWithoutPort); - HttpRequestHeaders headers = request.Headers; - headers.Authorization = new AuthenticationHeaderValue("Bearer", tokenString); - await base.ProcessHttpRequestAsync(request, cancellationToken); - } - } -} diff --git a/src/Microsoft.Azure.SignalR.Common/RestClients/RestClientFactory.cs b/src/Microsoft.Azure.SignalR.Common/RestClients/RestClientFactory.cs deleted file mode 100644 index a467fa59b..000000000 --- a/src/Microsoft.Azure.SignalR.Common/RestClients/RestClientFactory.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Net.Http; - -namespace Microsoft.Azure.SignalR -{ - internal class RestClientFactory - { - protected readonly IHttpClientFactory _httpClientFactory; - private readonly string _userAgent; - private readonly string _serverName; - - public RestClientFactory(string userAgent, IHttpClientFactory httpClientFactory) - { - _httpClientFactory = httpClientFactory; - _userAgent = userAgent; - _serverName = RestApiAccessTokenGenerator.GenerateServerName(); - } - - protected virtual HttpClient CreateHttpClient() => _httpClientFactory.CreateClient(); - - internal SignalRServiceRestClient Create(ServiceEndpoint endpoint) - { - var httpClient = CreateHttpClient(); - var credentials = new JwtTokenCredentials(endpoint.AccessKey, _serverName); - var restClient = new SignalRServiceRestClient(_userAgent, credentials, httpClient, true) - { - BaseUri = endpoint.ServerEndpoint - }; - return restClient; - } - } -} \ No newline at end of file diff --git a/src/Microsoft.Azure.SignalR.Common/RestClients/SignalRServiceRestClient.cs b/src/Microsoft.Azure.SignalR.Common/RestClients/SignalRServiceRestClient.cs deleted file mode 100644 index ba6ed0ea2..000000000 --- a/src/Microsoft.Azure.SignalR.Common/RestClients/SignalRServiceRestClient.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; - -using Microsoft.Azure.SignalR.Common; -using Microsoft.Rest; - -namespace Microsoft.Azure.SignalR -{ - public partial class SignalRServiceRestClient - { - private readonly string _userAgent; - - public SignalRServiceRestClient(string userAgent, - ServiceClientCredentials credentials, - HttpClient httpClient, - bool disposeHttpClient) : this(credentials, httpClient, disposeHttpClient) - { - if (string.IsNullOrWhiteSpace(userAgent)) - { - throw new ArgumentException($"'{nameof(userAgent)}' cannot be null or whitespace", nameof(userAgent)); - } - - _userAgent = userAgent; - } - - partial void CustomInitialize() - { - HttpClient.DefaultRequestHeaders.Add(Constants.AsrsUserAgent, _userAgent); - } - - public async Task IsServiceHealthy(CancellationToken cancellationToken) - { - try - { - var healthApi = HealthApi; - using var response = await healthApi.GetHealthStatusWithHttpMessagesAsync(cancellationToken: cancellationToken); - return true; - } - catch (HttpOperationException e) when ((int)e.Response.StatusCode >= 500 && (int)e.Response.StatusCode < 600) - { - return false; - } - catch (Exception ex) - { - throw ex.WrapAsAzureSignalRException(BaseUri); - } - } - } -} \ No newline at end of file diff --git a/src/Microsoft.Azure.SignalR.Common/RestClients/health.json b/src/Microsoft.Azure.SignalR.Common/RestClients/health.json deleted file mode 100644 index 04614d86e..000000000 --- a/src/Microsoft.Azure.SignalR.Common/RestClients/health.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "swagger": "2.0", - "info": { - "title": "Azure SignalR Service REST API", - "version": "v1" - }, - "paths": { - "/api/v1/health": { - "head": { - "tags": [ - "general" - ], - "summary": "Get service health status.", - "operationId": "HealthApi_GetHealthStatus", - "responses": { - "200": { - "description": "The service is healthy" - } - } - } - } - } -} \ No newline at end of file diff --git a/src/Microsoft.Azure.SignalR.Common/RestClients/readme.md b/src/Microsoft.Azure.SignalR.Common/RestClients/readme.md deleted file mode 100644 index 9cd69d7b8..000000000 --- a/src/Microsoft.Azure.SignalR.Common/RestClients/readme.md +++ /dev/null @@ -1,19 +0,0 @@ -# Azure SignalR Service REST API - -> see https://aka.ms/autorest - -This is the AutoRest configuration file for SignalR. - -``` yaml -input-file: health.json -``` - -## Alternate settings - -``` yaml $(csharp) -license-header: MICROSOFT_MIT_NO_VERSION -override-client-name: SignalRServiceRestClient -namespace: Microsoft.Azure.SignalR -output-folder: Generated -add-credentials: true -use-internal-constructors: true \ No newline at end of file diff --git a/src/Microsoft.Azure.SignalR.Common/Utilities/RestClient.cs b/src/Microsoft.Azure.SignalR.Common/Utilities/RestClient.cs index 4ecf5cf45..1a22eec55 100644 --- a/src/Microsoft.Azure.SignalR.Common/Utilities/RestClient.cs +++ b/src/Microsoft.Azure.SignalR.Common/Utilities/RestClient.cs @@ -11,7 +11,6 @@ using System.Threading.Tasks; using Azure.Core.Serialization; using Microsoft.Azure.SignalR.Common; -using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; #nullable enable @@ -63,7 +62,7 @@ public Task SendAsync( Func>? handleExpectedResponseAsync = null, CancellationToken cancellationToken = default) { - return SendAsyncCore(Options.DefaultName, api, httpMethod, methodName, args, handleExpectedResponseAsync, cancellationToken); + return SendAsyncCore(Constants.HttpClientNames.UserDefault, api, httpMethod, methodName, args, handleExpectedResponseAsync, cancellationToken); } public Task SendWithRetryAsync( diff --git a/src/Microsoft.Azure.SignalR.Management/DependencyInjectionExtensions.cs b/src/Microsoft.Azure.SignalR.Management/DependencyInjectionExtensions.cs index 0a9f3bfe1..1967e912a 100644 --- a/src/Microsoft.Azure.SignalR.Management/DependencyInjectionExtensions.cs +++ b/src/Microsoft.Azure.SignalR.Management/DependencyInjectionExtensions.cs @@ -117,7 +117,7 @@ private static IServiceCollection AddSignalRServiceCore(this IServiceCollection //add dependencies for transient mode only services.AddSingleton(); - services.AddRestClientFactory(); + services.AddRestClient(); services.AddSingleton(); return services.TrySetProductInfo(); } @@ -158,31 +158,37 @@ private static IServiceCollection TrySetProductInfo(this IServiceCollection serv return services.Configure(o => o.ProductInfo ??= productInfo); } - private static IServiceCollection AddRestClientFactory(this IServiceCollection services) + private static IServiceCollection AddRestClient(this IServiceCollection services) { - // For AAD, health check. + // For internal health check. Not impacted by user set timeout. services - .AddHttpClient(Options.DefaultName, (sp, client) => + .AddHttpClient(Constants.HttpClientNames.InternalDefault, ConfigureProduceInfo) + .ConfigurePrimaryHttpMessageHandler(ConfigureProxy); + + // Used by user. Impacted by user set timeout. + services.AddSingleton(sp => sp.GetRequiredService().GetPayloadContentBuilder()) + .AddSingleton() + .AddSingleton(sp => + { + var options = sp.GetRequiredService>().Value; + var retryOptions = options.RetryOptions; + return retryOptions == null + ? new DummyBackOffPolicy() + : retryOptions.Mode switch + { + ServiceManagerRetryMode.Fixed => ActivatorUtilities.CreateInstance(sp), + ServiceManagerRetryMode.Exponential => ActivatorUtilities.CreateInstance(sp), + _ => throw new NotSupportedException($"Retry mode {retryOptions.Mode} is not supported.") + }; + }); + services + .AddHttpClient(Constants.HttpClientNames.UserDefault, (sp, client) => { - client.Timeout = sp.GetRequiredService>().Value.HttpClientTimeout; + ConfigureUserTimeout(sp, client); ConfigureProduceInfo(sp, client); }) .ConfigurePrimaryHttpMessageHandler(ConfigureProxy); - // For other data plane APIs. - services.AddSingleton(sp => - { - var options = sp.GetRequiredService>().Value; - var retryOptions = options.RetryOptions; - return retryOptions == null - ? new DummyBackOffPolicy() - : retryOptions.Mode switch - { - ServiceManagerRetryMode.Fixed => ActivatorUtilities.CreateInstance(sp), - ServiceManagerRetryMode.Exponential => ActivatorUtilities.CreateInstance(sp), - _ => throw new NotSupportedException($"Retry mode {retryOptions.Mode} is not supported.") - }; - }); services .AddHttpClient(Constants.HttpClientNames.Resilient, (sp, client) => { @@ -206,21 +212,13 @@ private static IServiceCollection AddRestClientFactory(this IServiceCollection s services .AddHttpClient(Constants.HttpClientNames.MessageResilient, (sp, client) => { - client.Timeout = sp.GetRequiredService>().Value.HttpClientTimeout; + ConfigureUserTimeout(sp, client); ConfigureProduceInfo(sp, client); ConfigureMessageTracingId(sp, client); }) .ConfigurePrimaryHttpMessageHandler(ConfigureProxy) .AddHttpMessageHandler(sp => ActivatorUtilities.CreateInstance(sp, (HttpStatusCode code) => IsTransientErrorAndIdempotentForMessageApi(code))); - services.AddSingleton(sp => - { - var options = sp.GetRequiredService>().Value; - var productInfo = options.ProductInfo; - var httpClientFactory = sp.GetRequiredService(); - return new RestClientFactory(productInfo, httpClientFactory); - }); - return services; static HttpMessageHandler ConfigureProxy(IServiceProvider sp) => new HttpClientHandler() { Proxy = sp.GetRequiredService>().Value.Proxy }; @@ -233,6 +231,8 @@ static bool IsTransientErrorForNonMessageApi(HttpStatusCode code) => code >= HttpStatusCode.InternalServerError || code == HttpStatusCode.RequestTimeout; + static void ConfigureUserTimeout(IServiceProvider sp, HttpClient client) => client.Timeout = sp.GetRequiredService>().Value.HttpClientTimeout; + static void ConfigureProduceInfo(IServiceProvider sp, HttpClient client) => client.DefaultRequestHeaders.Add(Constants.AsrsUserAgent, sp.GetRequiredService>().Value.ProductInfo ?? // The following value should not be used. diff --git a/src/Microsoft.Azure.SignalR.Management/HubInstanceFactories/HealthCheckOptions.cs b/src/Microsoft.Azure.SignalR.Management/HubInstanceFactories/HealthCheckOptions.cs index 49e56e770..8fad392ff 100644 --- a/src/Microsoft.Azure.SignalR.Management/HubInstanceFactories/HealthCheckOptions.cs +++ b/src/Microsoft.Azure.SignalR.Management/HubInstanceFactories/HealthCheckOptions.cs @@ -3,13 +3,15 @@ using System; -namespace Microsoft.Azure.SignalR.Management +namespace Microsoft.Azure.SignalR.Management; + +#nullable enable + +//used to reduce test time +internal class HealthCheckOption { - //used to reduce test time - internal class HealthCheckOption - { - public TimeSpan? CheckInterval { get; set; } - public TimeSpan? RetryInterval { get; set; } - public bool EnabledForSingleEndpoint { get; set; } = false; - } + public TimeSpan CheckInterval { get; set; } = TimeSpan.FromMinutes(2); + public TimeSpan RetryInterval { get; set; } = TimeSpan.FromSeconds(3); + public TimeSpan HttpTimeout { get; set; } = TimeSpan.FromSeconds(2); + public bool EnabledForSingleEndpoint { get; set; } = false; } \ No newline at end of file diff --git a/src/Microsoft.Azure.SignalR.Management/HubInstanceFactories/RestHealthCheckService.cs b/src/Microsoft.Azure.SignalR.Management/HubInstanceFactories/RestHealthCheckService.cs index 74d078876..b569f50e7 100644 --- a/src/Microsoft.Azure.SignalR.Management/HubInstanceFactories/RestHealthCheckService.cs +++ b/src/Microsoft.Azure.SignalR.Management/HubInstanceFactories/RestHealthCheckService.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; @@ -16,43 +17,40 @@ internal class RestHealthCheckService : IHostedService internal const int MaxRetries = 2; //An acceptable time to wait before retry when clients negotiate fail - private readonly TimeSpan _checkInterval = TimeSpan.FromMinutes(2); + private readonly TimeSpan _checkInterval; - private readonly TimeSpan _retryInterval = TimeSpan.FromSeconds(3); + private readonly TimeSpan _httpTimeout; + private readonly TimeSpan _retryInterval; - private readonly RestClientFactory _clientFactory; private readonly IServiceEndpointManager _serviceEndpointManager; private readonly ILogger _logger; private readonly string _hubName; private readonly TimerAwaitable _timer; - private readonly bool _enable; + private readonly IHttpClientFactory _httpClientFactory; + private readonly bool _enabledForSingleEndpoint; - public RestHealthCheckService(RestClientFactory clientFactory, IServiceEndpointManager serviceEndpointManager, ILogger logger, string hubName, IOptions options) + public RestHealthCheckService(IServiceEndpointManager serviceEndpointManager, ILogger logger, string hubName, IOptions options, IHttpClientFactory httpClientFactory) { var checkOptions = options.Value; - _enable = serviceEndpointManager.Endpoints.Count > 1 || checkOptions.EnabledForSingleEndpoint; - if (_enable) - { - _clientFactory = clientFactory; - _serviceEndpointManager = serviceEndpointManager; - _logger = logger; - _hubName = hubName; - - _checkInterval = checkOptions.CheckInterval.GetValueOrDefault(_checkInterval); - _retryInterval = checkOptions.RetryInterval.GetValueOrDefault(_retryInterval); - _timer = new TimerAwaitable(_checkInterval, _checkInterval); - } + _serviceEndpointManager = serviceEndpointManager; + _logger = logger; + _hubName = hubName; + + _checkInterval = checkOptions.CheckInterval; + _retryInterval = checkOptions.RetryInterval; + _httpTimeout = checkOptions.HttpTimeout; + + _timer = new TimerAwaitable(_checkInterval, _checkInterval); + _httpClientFactory = httpClientFactory; + _enabledForSingleEndpoint = checkOptions.EnabledForSingleEndpoint; } public async Task StartAsync(CancellationToken cancellationToken) { - if (_enable) - { - // wait for the first health check finished - await CheckEndpointHealthAsync(); - _ = LoopAsync(); - } + // wait for the first health check finished + await CheckEndpointHealthAsync(); + _ = LoopAsync(); } public Task StopAsync(CancellationToken _) @@ -63,32 +61,31 @@ public Task StopAsync(CancellationToken _) private async Task CheckEndpointHealthAsync() { - await Task.WhenAll(_serviceEndpointManager.GetEndpoints(_hubName).Select(async endpoint => + if (_serviceEndpointManager.Endpoints.Count > 1 || _enabledForSingleEndpoint) { - var retry = 0; - var isHealthy = false; - bool needRetry; - do + await Task.WhenAll(_serviceEndpointManager.GetEndpoints(_hubName).Select(async endpoint => { - try - { - using var client = _clientFactory.Create(endpoint); - isHealthy = await client.IsServiceHealthy(default); - } - catch (Exception ex) + var retry = 0; + var isHealthy = false; + bool needRetry; + do { - //Hard to tell if it is transient error, retry anyway. - Log.RestHealthCheckFailed(_logger, endpoint.Endpoint, ex, retry == MaxRetries, _retryInterval); - } - needRetry = !isHealthy && retry < MaxRetries; - if (needRetry) + isHealthy = await IsServiceHealthy(endpoint); + needRetry = !isHealthy && retry < MaxRetries; + if (needRetry) + { + Log.WillRetryHealthCheck(_logger, endpoint.Endpoint, _retryInterval); + await Task.Delay(_retryInterval); + } + retry++; + } while (needRetry); + endpoint.Online = isHealthy; + if (!isHealthy) { - await Task.Delay(_retryInterval); + Log.RestHealthCheckFailed(_logger, endpoint.Endpoint); } - retry++; - } while (needRetry); - endpoint.Online = isHealthy; - })); + })); + } } private async Task LoopAsync() @@ -103,22 +100,46 @@ private async Task LoopAsync() } } + private async Task IsServiceHealthy(ServiceEndpoint endpoint) + { + using var httpClient = _httpClientFactory.CreateClient(Constants.HttpClientNames.InternalDefault); + try + { + httpClient.BaseAddress = endpoint.ServerEndpoint; + httpClient.Timeout = _httpTimeout; + var request = new HttpRequestMessage(HttpMethod.Head, RestApiProvider.HealthApiPath); + using var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); + response.EnsureSuccessStatusCode(); + return true; + } + catch (Exception ex) + { + //Hard to tell if it is transient error, retry anyway. + Log.RestHealthCheckGetUnexpectedResult(_logger, endpoint.Endpoint, ex); + return false; + } + } + private static class Log { - private static readonly Action _restHealthCheckFailed = LoggerMessage.Define(LogLevel.Information, new EventId(1, nameof(RestHealthCheckFailed)), "Will retry health check for endpoint {endpoint} after a delay of {retryInterval} due to exception."); + private static readonly Action _restHealthCheckGetUnexpectedResult = LoggerMessage.Define(LogLevel.Information, new EventId(1, nameof(RestHealthCheckGetUnexpectedResult)), "Got unexpected result when checking health of endpoint {endpoint}. It may be transient."); + + private static readonly Action _restHealthyCheckFailed = LoggerMessage.Define(LogLevel.Error, new EventId(2, nameof(RestHealthCheckFailed)), "Service {endpoint} is unhealthy."); - private static readonly Action _finalRestHealthCheckFailed = LoggerMessage.Define(LogLevel.Error, new EventId(2, nameof(RestHealthCheckFailed)), "Failed to check health state for endpoint {endpoint}."); + private static readonly Action _willRetryHealthCheck = LoggerMessage.Define(LogLevel.Information, new EventId(3, nameof(RestHealthCheckFailed)), "Will retry health check for endpoint {endpoint} after a delay of {retryInterval} due to exception."); - public static void RestHealthCheckFailed(ILogger logger, string endpoint, Exception exception, bool lastRetry, TimeSpan retryInterval) + public static void WillRetryHealthCheck(ILogger logger, string endpoint, TimeSpan retryInterval) { - if (lastRetry) - { - _finalRestHealthCheckFailed(logger, endpoint, exception); - } - else - { - _restHealthCheckFailed(logger, endpoint, retryInterval, exception); - } + _willRetryHealthCheck(logger, endpoint, retryInterval, null); + } + + public static void RestHealthCheckGetUnexpectedResult(ILogger logger, string endpoint, Exception exception) + { + _restHealthCheckGetUnexpectedResult(logger, endpoint, exception); + } + public static void RestHealthCheckFailed(ILogger logger, string endpoint) + { + _restHealthyCheckFailed(logger, endpoint, null); } } } diff --git a/src/Microsoft.Azure.SignalR.Management/RestApiProvider.cs b/src/Microsoft.Azure.SignalR.Management/RestApiProvider.cs index a27a73313..3a3f58225 100644 --- a/src/Microsoft.Azure.SignalR.Management/RestApiProvider.cs +++ b/src/Microsoft.Azure.SignalR.Management/RestApiProvider.cs @@ -13,6 +13,7 @@ namespace Microsoft.Azure.SignalR.Management internal class RestApiProvider { private const string Version = "2022-06-01"; + public const string HealthApiPath = $"api/health?api-version={Version}"; private readonly RestApiAccessTokenGenerator _restApiAccessTokenGenerator; diff --git a/src/Microsoft.Azure.SignalR.Management/ServiceManagerImpl.cs b/src/Microsoft.Azure.SignalR.Management/ServiceManagerImpl.cs index 7ee951843..ac4da6873 100644 --- a/src/Microsoft.Azure.SignalR.Management/ServiceManagerImpl.cs +++ b/src/Microsoft.Azure.SignalR.Management/ServiceManagerImpl.cs @@ -4,82 +4,100 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net.Http; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Microsoft.Azure.SignalR.Management +namespace Microsoft.Azure.SignalR.Management; + +#nullable enable + +internal class ServiceManagerImpl : ServiceManager, IServiceManager { - internal class ServiceManagerImpl : ServiceManager, IServiceManager + private readonly IServiceProvider _serviceProvider; + private readonly RestClient _restClient; + private readonly IReadOnlyCollection _services; + private readonly RestApiProvider _restApiEndpointProvider; + private readonly IServiceEndpointProvider _serviceEndpointProvider; + + public ServiceManagerImpl(IReadOnlyCollection services, IServiceProvider serviceProvider, RestClient restClient, IServiceEndpointManager endpointManager) { - private readonly RestClientFactory _restClientFactory; - private readonly IServiceProvider _serviceProvider; - private readonly IReadOnlyCollection _services; - private readonly ServiceEndpoint _endpoint; - private readonly IServiceEndpointProvider _endpointProvider; + _services = services; + _serviceProvider = serviceProvider; + _restClient = restClient; + var endpoint = endpointManager.Endpoints.Keys.First(); + _serviceEndpointProvider = endpointManager.GetEndpointProvider(endpoint); + _restApiEndpointProvider = new RestApiProvider(endpoint); + } - public ServiceManagerImpl(IReadOnlyCollection services, IServiceProvider serviceProvider, RestClientFactory restClientFactory, IServiceEndpointManager endpointManager) + public async Task CreateHubContextAsync(string hubName, ILoggerFactory loggerFactory, CancellationToken cancellationToken) + { + var builder = new ServiceHubContextBuilder(_services); + if (loggerFactory != null) { - _services = services; - _serviceProvider = serviceProvider; - _restClientFactory = restClientFactory; - _endpoint = endpointManager.Endpoints.Keys.First(); - _endpointProvider = endpointManager.GetEndpointProvider(_endpoint); + builder.ConfigureServices(services => services.AddSingleton(loggerFactory)); } + var serviceHubContext = await builder.CreateAsync(hubName, cancellationToken); + return serviceHubContext; + } - public async Task CreateHubContextAsync(string hubName, ILoggerFactory loggerFactory, CancellationToken cancellationToken) - { - var builder = new ServiceHubContextBuilder(_services); - if (loggerFactory != null) - { - builder.ConfigureServices(services => services.AddSingleton(loggerFactory)); - } - var serviceHubContext = await builder.CreateAsync(hubName, cancellationToken); - return serviceHubContext; - } + public override Task CreateHubContextAsync(string hubName, CancellationToken cancellationToken) + { + var builder = new ServiceHubContextBuilder(_services); + return builder.CreateAsync(hubName, cancellationToken); + } - public override Task CreateHubContextAsync(string hubName, CancellationToken cancellationToken) - { - var builder = new ServiceHubContextBuilder(_services); - return builder.CreateAsync(hubName, cancellationToken); - } + public override Task> CreateHubContextAsync(string hubName, CancellationToken cancellation) + { + var builder = new ServiceHubContextBuilder(_services); + return builder.CreateAsync(hubName, cancellation); + } - public override Task> CreateHubContextAsync(string hubName, CancellationToken cancellation) - { - var builder = new ServiceHubContextBuilder(_services); - return builder.CreateAsync(hubName, cancellation); - } + public override void Dispose() + { + (_serviceProvider as IDisposable)?.Dispose(); + } - public override void Dispose() + public string GenerateClientAccessToken(string hubName, string? userId = null, IList? claims = null, TimeSpan? lifeTime = null) + { + var claimsWithUserId = new List(); + if (userId != null) { - (_serviceProvider as IDisposable)?.Dispose(); + claimsWithUserId.Add(new Claim(ClaimTypes.NameIdentifier, userId)); + }; + if (claims != null) + { + claimsWithUserId.AddRange(claims); } + return _serviceEndpointProvider.GenerateClientAccessTokenAsync(hubName, claimsWithUserId, lifeTime).Result; + } - public string GenerateClientAccessToken(string hubName, string userId = null, IList claims = null, TimeSpan? lifeTime = null) + public string GetClientEndpoint(string hubName) + { + return _serviceEndpointProvider.GetClientEndpoint(hubName, null, null); + } + + public override async Task IsServiceHealthy(CancellationToken cancellationToken) + { + var api = await _restApiEndpointProvider.GetServiceHealthEndpointAsync(); + var isHealthy = false; + await _restClient.SendAsync(api, HttpMethod.Head, handleExpectedResponse: response => { - var claimsWithUserId = new List(); - if (userId != null) + if (response.IsSuccessStatusCode) { - claimsWithUserId.Add(new Claim(ClaimTypes.NameIdentifier, userId)); - }; - if (claims != null) + isHealthy = true; + return true; + } + else if (response.StatusCode >= System.Net.HttpStatusCode.InternalServerError) { - claimsWithUserId.AddRange(claims); + isHealthy = false; + return true; } - return _endpointProvider.GenerateClientAccessTokenAsync(hubName, claimsWithUserId, lifeTime).Result; - } - - public string GetClientEndpoint(string hubName) - { - return _endpointProvider.GetClientEndpoint(hubName, null, null); - } - - public override async Task IsServiceHealthy(CancellationToken cancellationToken) - { - using var restClient = _restClientFactory.Create(_endpoint); - return await restClient.IsServiceHealthy(cancellationToken); - } + return false; + }); + return isHealthy; } } \ No newline at end of file diff --git a/test/Microsoft.Azure.SignalR.Common.Tests/RestClients/HealthApiFacts.cs b/test/Microsoft.Azure.SignalR.Common.Tests/RestClients/HealthApiFacts.cs deleted file mode 100644 index d9e43cd00..000000000 --- a/test/Microsoft.Azure.SignalR.Common.Tests/RestClients/HealthApiFacts.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Net; -using Microsoft.Azure.SignalR.Tests.Common; -using Microsoft.Rest; -using Xunit; - -namespace Microsoft.Azure.SignalR.Common.Tests.RestClients -{ - public class HealthApiFacts - { - private const string UserAgent = "userAgent"; - private const string AccessKey = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - private const string Endpoint = "http://endpoint/"; - private static readonly string _connectionString = $"Endpoint={Endpoint};AccessKey={AccessKey};Version=1.0;"; - private static readonly ServiceEndpoint serviceEndpoint = new ServiceEndpoint(_connectionString); - [Fact] - public async void IsServiceHealthyReturnTrue() - { - using var signalRServiceRestClient = new TestRestClientFactory(UserAgent, HttpStatusCode.OK).Create(serviceEndpoint); - var healthApi = new HealthApi(signalRServiceRestClient); - - var operationResponse = await healthApi.GetHealthStatusWithHttpMessagesAsync(); - - Assert.Equal(HttpStatusCode.OK, operationResponse.Response.StatusCode); - } - - [Theory] - [InlineData(HttpStatusCode.ServiceUnavailable)] //will retry many times - [InlineData(HttpStatusCode.GatewayTimeout)] //will retry many times - [InlineData(HttpStatusCode.BadRequest)] //won't retry - [InlineData(HttpStatusCode.Conflict)] //won't retry - public async void IsServiceHealthyThrowException(HttpStatusCode statusCode) - //always throw exception when status code != 200 - { - string contentString = "response content"; - using var signalRServiceRestClient = new TestRestClientFactory(UserAgent, statusCode, contentString).Create(serviceEndpoint); - var healthApi = new HealthApi(signalRServiceRestClient); - - HttpOperationException exception = await Assert.ThrowsAsync(() => healthApi.GetHealthStatusWithHttpMessagesAsync()); - Assert.Equal(statusCode, exception.Response.StatusCode); - Assert.Equal(contentString, exception.Response.Content); - } - } -} diff --git a/test/Microsoft.Azure.SignalR.Common.Tests/RestClients/RestClientBuilderFacts.cs b/test/Microsoft.Azure.SignalR.Common.Tests/RestClients/RestClientBuilderFacts.cs deleted file mode 100644 index 69d22f660..000000000 --- a/test/Microsoft.Azure.SignalR.Common.Tests/RestClients/RestClientBuilderFacts.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Net.Http; -using System.Threading; -using Microsoft.Azure.SignalR.Tests.Common; -using Xunit; - -namespace Microsoft.Azure.SignalR.Common.Tests.RestClients -{ - public class RestClientBuilderFacts - { - private const string AccessKey = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - private const string Endpoint = "http://endpoint/"; - private const string productInfo = "productInfo"; - private static readonly string _connectionString = $"Endpoint={Endpoint};AccessKey={AccessKey};Version=1.0;"; - private static readonly ServiceEndpoint _serviceEndpoint = new ServiceEndpoint(_connectionString); - [Fact] - public void RequestContainsAsrsUAFact() - { - void assertion(HttpRequestMessage request, CancellationToken t) - { - Assert.True(request.Headers.Contains(Constants.AsrsUserAgent)); - Assert.NotNull(request.Headers.GetValues(Constants.AsrsUserAgent)); - } - - TestRestClientFactory(assertion); - } - - [Fact] - public void RequestContainsCredentials() - { - void assertion(HttpRequestMessage request, CancellationToken t) - { - var authHeader = request.Headers.Authorization; - string scheme = authHeader.Scheme; - string parameter = authHeader.Parameter; - - Assert.Equal("Bearer", scheme); - Assert.NotNull(parameter); - } - TestRestClientFactory(assertion); - } - - [Fact] - public void GetCustomiazeClient_BaseUriRightFact() - { - var restClientFactory = new TestRestClientFactory(productInfo, null); - using var restClient = restClientFactory.Create(_serviceEndpoint); - Assert.Equal(Endpoint, restClient.BaseUri.AbsoluteUri); - } - - private async void TestRestClientFactory(Action assertion) - { - var restClientFactory = new TestRestClientFactory(productInfo, assertion); - using var restClient = restClientFactory.Create(_serviceEndpoint); - await restClient.HealthApi.GetHealthStatusWithHttpMessagesAsync(); - } - } -} \ No newline at end of file diff --git a/test/Microsoft.Azure.SignalR.Common.Tests/RestClients/RestClientFacts.cs b/test/Microsoft.Azure.SignalR.Common.Tests/RestClients/RestClientFacts.cs index 14b478670..63d0922b4 100644 --- a/test/Microsoft.Azure.SignalR.Common.Tests/RestClients/RestClientFacts.cs +++ b/test/Microsoft.Azure.SignalR.Common.Tests/RestClients/RestClientFacts.cs @@ -18,7 +18,7 @@ public class RestClientFacts public async Task TestHttpRequestExceptionWithStatusCodeSetAsync() { var httpClientFactory = new ServiceCollection() - .AddHttpClient("").ConfigurePrimaryHttpMessageHandler(() => new TestRootHandler(HttpStatusCode.InsufficientStorage)).Services + .AddHttpClient(Constants.HttpClientNames.UserDefault).ConfigurePrimaryHttpMessageHandler(() => new TestRootHandler(HttpStatusCode.InsufficientStorage)).Services .BuildServiceProvider().GetRequiredService(); var client = new RestClient(httpClientFactory); var apiEndpoint = new RestApiEndpoint("https://localhost.test.com", "token"); @@ -29,7 +29,6 @@ public async Task TestHttpRequestExceptionWithStatusCodeSetAsync() var httpRequestException = Assert.IsType(exception.InnerException); Assert.Equal(HttpStatusCode.InsufficientStorage, httpRequestException.StatusCode); } - #endif } } diff --git a/test/Microsoft.Azure.SignalR.Management.Tests/DependencyInjectionExtensionFacts.cs b/test/Microsoft.Azure.SignalR.Management.Tests/DependencyInjectionExtensionFacts.cs index c97131e39..3ffa2df17 100644 --- a/test/Microsoft.Azure.SignalR.Management.Tests/DependencyInjectionExtensionFacts.cs +++ b/test/Microsoft.Azure.SignalR.Management.Tests/DependencyInjectionExtensionFacts.cs @@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.SignalR; using Microsoft.Azure.SignalR.Tests.Common; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -234,7 +235,7 @@ public async Task MultiServiceEndpoints_NotAppliedToTransientModeAsync() } [Fact] - public async Task ProxyApplyToTransientModeTestAsync() + public async Task ProxyApplyToUserRestRequestTestAsync() { var requestUrls = new Queue(); @@ -258,16 +259,60 @@ public async Task ProxyApplyToTransientModeTestAsync() o.Proxy = new WebProxy(app.Services.GetRequiredService().Features.Get().Addresses.First()); }).BuildServiceManager(); Assert.True(await serviceManager.IsServiceHealthy(default)); - Assert.Equal("/api/v1/health", requestUrls.Dequeue()); + Assert.Equal("/api/health", requestUrls.Dequeue()); using var hubContext = await serviceManager.CreateHubContextAsync("hub", default); Assert.True(await hubContext.ClientManager.UserExistsAsync("userId")); Assert.Equal("/api/hubs/hub/users/userId", requestUrls.Dequeue()); + await hubContext.Clients.All.SendAsync("method"); + Assert.Equal("/api/hubs/hub/:send", requestUrls.Dequeue()); await app.StopAsync(); } [Fact] - public async Task CustomizeHttpClientTimeoutTestAsync() + public async Task ProxyApplyToInternalHealthCheckTestAsync() + { + var requestUrls = new Queue(); + + //create a simple proxy server + var appBuilder = WebApplication.CreateBuilder(); + appBuilder.Services.AddLogging(b => b.AddXunit(_outputHelper)); + using var app = appBuilder.Build(); + //randomly choose a free port, listen to all interfaces + app.Urls.Add("http://[::1]:0"); + app.Run(context => + { + if (HttpMethods.IsHead(context.Request.Method)) + { + requestUrls.Enqueue(context.Request.Path); + } + return Task.CompletedTask; + }); + await app.StartAsync(); + + var serviceManager = new ServiceManagerBuilder().WithOptions(o => + { + // use http schema to avoid SSL handshake + o.ConnectionString = "Endpoint=http://abc;AccessKey=nOu3jXsHnsO5urMumc87M9skQbUWuQ+PE5IvSUEic8w=;Version=1.0;"; + o.Proxy = new WebProxy(app.Services.GetRequiredService().Features.Get().Addresses.First()); + o.ServiceTransportType = ServiceTransportType.Transient; + }).ConfigureServices(services => services.Configure(o => o.EnabledForSingleEndpoint = true)).BuildServiceManager(); + + using var hubContext = await serviceManager.CreateHubContextAsync("hub", default); + Assert.Equal("/api/health", requestUrls.Dequeue()); + await app.StopAsync(); + } + + public static IEnumerable CustomizeHttpClientTimeoutTestData => new object[][] + { + new object[]{ Constants.HttpClientNames.MessageResilient , (ServiceHubContext serviceHubContext)=> serviceHubContext.Clients.All.SendCoreAsync("method", null) }, + new object[]{ Constants.HttpClientNames.Resilient , (ServiceHubContext serviceHubContext)=> serviceHubContext.ClientManager.CloseConnectionAsync("connectionId") }, + new object[]{Constants.HttpClientNames.UserDefault, (ServiceHubContext serviceHubContext)=> (serviceHubContext as ServiceHubContextImpl).ServiceProvider.GetRequiredService().IsServiceHealthy(default)} + }; + + [Theory] + [MemberData(nameof(CustomizeHttpClientTimeoutTestData))] + public async Task CustomizeHttpClientTimeoutTestAsync(string httpClientName, Func testFunc) { for (int i = 0; i < 10; i++) { @@ -278,16 +323,11 @@ public async Task CustomizeHttpClientTimeoutTestAsync() o.ConnectionString = FakeEndpointUtils.GetFakeConnectionString(1).Single(); o.HttpClientTimeout = TimeSpan.FromSeconds(1); }) - .ConfigureServices(services => - { - services.AddHttpClient(Constants.HttpClientNames.MessageResilient).AddHttpMessageHandler(sp => new WaitInfinitelyHandler()); - services.AddHttpClient(Constants.HttpClientNames.Resilient).AddHttpMessageHandler(sp => new WaitInfinitelyHandler()); - }) + .ConfigureServices(services => services.AddHttpClient(httpClientName).AddHttpMessageHandler(sp => new WaitInfinitelyHandler())) .BuildServiceManager(); var requestStartTime = DateTime.UtcNow; var serviceHubContext = await serviceManager.CreateHubContextAsync("hub", default); - await TestCoreAsync(() => serviceHubContext.Clients.All.SendCoreAsync("method", null)); - await TestCoreAsync(() => serviceHubContext.ClientManager.CloseConnectionAsync("connectionId")); + await TestCoreAsync(() => testFunc(serviceHubContext)); } static async Task TestCoreAsync(Func testAction) @@ -302,7 +342,8 @@ static async Task TestCoreAsync(Func testAction) } [Theory] - [InlineData("")] + [InlineData(Constants.HttpClientNames.InternalDefault)] + [InlineData(Constants.HttpClientNames.UserDefault)] [InlineData(Constants.HttpClientNames.MessageResilient)] [InlineData(Constants.HttpClientNames.Resilient)] public async Task HttpClientProductInfoTestAsync(string httpClientName) diff --git a/test/Microsoft.Azure.SignalR.Management.Tests/RestHealthCheckServiceFacts.cs b/test/Microsoft.Azure.SignalR.Management.Tests/RestHealthCheckServiceFacts.cs index d4d03a016..73b476473 100644 --- a/test/Microsoft.Azure.SignalR.Management.Tests/RestHealthCheckServiceFacts.cs +++ b/test/Microsoft.Azure.SignalR.Management.Tests/RestHealthCheckServiceFacts.cs @@ -11,7 +11,6 @@ using Microsoft.Azure.SignalR.Tests.Common; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Testing; -using Microsoft.Extensions.Options; using Moq; using Moq.Protected; using Xunit; @@ -22,13 +21,13 @@ namespace Microsoft.Azure.SignalR.Management.Tests public class RestHealthCheckServiceFacts : LoggedTest { private const string HubName = "hub"; - public static IEnumerable TestRestClientFactoryData + public static IEnumerable HttpClientMockData { get { - yield return new object[] { new TestRestClientFactory("userAgent", HttpStatusCode.BadGateway) }; - yield return new object[] { new TestRestClientFactory("userAgent", HttpStatusCode.NotFound) }; - yield return new object[] { new TestRestClientFactory("userAgent", (req, token) => throw new HttpRequestException()) }; + yield return new object[] { new TestRootHandler(HttpStatusCode.BadGateway) }; + yield return new object[] { new TestRootHandler(HttpStatusCode.NotFound) }; + yield return new object[] { new TestRootHandler((request, token) => throw new HttpRequestException()) }; } } public RestHealthCheckServiceFacts(ITestOutputHelper output = null) : base(output) @@ -36,8 +35,8 @@ public RestHealthCheckServiceFacts(ITestOutputHelper output = null) : base(outpu } [Theory] - [MemberData(nameof(TestRestClientFactoryData))] - internal async Task TestRestHealthCheckServiceWithUnhealthyEndpoint(RestClientFactory implementationInstance) + [MemberData(nameof(HttpClientMockData))] + internal async Task TestRestHealthCheckServiceWithUnhealthyEndpoint(TestRootHandler testHandler) { using var _ = StartLog(out var loggerFactory); using var serviceHubContext = await new ServiceManagerBuilder() @@ -45,7 +44,8 @@ internal async Task TestRestHealthCheckServiceWithUnhealthyEndpoint(RestClientFa .WithLoggerFactory(loggerFactory) .ConfigureServices(services => { - services.AddSingleton(implementationInstance); + services.AddHttpClient(Constants.HttpClientNames.InternalDefault) + .ConfigurePrimaryHttpMessageHandler(() => testHandler); services.Configure(o => o.EnabledForSingleEndpoint = true); }) .BuildServiceManager() @@ -70,7 +70,7 @@ public async Task TestRestHealthCheckServiceWithEndpointFromHealthyToUnhealthy() using var _ = StartLog(out var loggerFactory); var services = new ServiceCollection() .AddSignalRServiceManager() - .AddHttpClient(Options.DefaultName).ConfigurePrimaryHttpMessageHandler(() => handlerMock.Object).Services + .AddHttpClient(Constants.HttpClientNames.InternalDefault).ConfigurePrimaryHttpMessageHandler(() => handlerMock.Object).Services .Configure(o => { o.CheckInterval = checkInterval; @@ -93,5 +93,42 @@ public async Task TestRestHealthCheckServiceWithEndpointFromHealthyToUnhealthy() await Task.Delay(checkInterval + retryTime + TimeSpan.FromSeconds(1)); Assert.False(endpoint.Online); } + + [Fact] + public async Task TestTimeoutAsync() + { + using var _ = StartLog(out var loggerFactory); + DateTime startTime = default, endTime = default; + var services = new ServiceCollection() + .AddSignalRServiceManager() + .AddHttpClient(Constants.HttpClientNames.InternalDefault).ConfigurePrimaryHttpMessageHandler(() => new TestRootHandler(async (message, token) => + { + try + { + startTime = DateTime.Now; + await Task.Delay(-1, token); + } + catch (OperationCanceledException) when (token.IsCancellationRequested) + { + endTime = DateTime.Now; + } + })).Services + .Configure(o => + { + // Never retry + o.RetryInterval = Timeout.InfiniteTimeSpan; + o.HttpTimeout = TimeSpan.FromMilliseconds(100); + o.EnabledForSingleEndpoint = true; + }); + using var serviceHubContext = await new ServiceManagerBuilder(services) + .WithOptions(o => o.ConnectionString = FakeEndpointUtils.GetFakeConnectionString(1).Single()) + .WithLoggerFactory(loggerFactory) + .BuildServiceManager() + .CreateHubContextAsync(HubName, default); + + + await Task.Delay(TimeSpan.FromMilliseconds(200)); + Assert.InRange(endTime - startTime, TimeSpan.FromMilliseconds(80), TimeSpan.FromMilliseconds(120)); + } } } diff --git a/test/Microsoft.Azure.SignalR.Management.Tests/ServiceManagerFacts.cs b/test/Microsoft.Azure.SignalR.Management.Tests/ServiceManagerFacts.cs index a76556755..781f9e11f 100644 --- a/test/Microsoft.Azure.SignalR.Management.Tests/ServiceManagerFacts.cs +++ b/test/Microsoft.Azure.SignalR.Management.Tests/ServiceManagerFacts.cs @@ -26,7 +26,6 @@ public class ServiceManagerFacts private const string AccessKey = "nOu3jXsHnsO5urMumc87M9skQbUWuQ+PE5IvSUEic8w="; private const string HubName = "signalrBench"; private const string UserId = "UserA"; - private const string UserAgent = "userAgent"; private static readonly string _testConnectionString = $"Endpoint={Endpoint};AccessKey={AccessKey};Version=1.0;"; private static readonly TimeSpan _tokenLifeTime = TimeSpan.FromSeconds(99); private static readonly Claim[] _defaultClaims = new Claim[] { new Claim("type1", "val1") }; @@ -123,8 +122,8 @@ internal async Task IsServiceHealthy_ReturnTrue_Test() { var services = new ServiceCollection() .AddSignalRServiceManager() - .Configure(o => o.ConnectionString = _testConnectionString) - .AddSingleton(new TestRestClientFactory(UserAgent, HttpStatusCode.OK)); + .Configure(o => o.ConnectionString = _testConnectionString); + ConfigureTestHttpClient(HttpStatusCode.OK)(services); var serviceManager = services.AddSingleton(services.ToList() as IReadOnlyCollection) .BuildServiceProvider() .GetRequiredService(); @@ -143,7 +142,7 @@ internal async Task IsServiceHealthy_ReturnFalse_Test(HttpStatusCode statusCode) var services = new ServiceCollection(); services.Configure(o => o.ConnectionString = _testConnectionString); services.AddSignalRServiceManager(); - services.AddSingleton(new TestRestClientFactory(UserAgent, statusCode)); + ConfigureTestHttpClient(statusCode)(services); services.AddSingleton(services.ToList() as IReadOnlyCollection); using var serviceManager = services.BuildServiceProvider().GetRequiredService(); @@ -162,7 +161,7 @@ internal async Task IsServiceHealthy_Throw_Test(HttpStatusCode statusCode, Type var services = new ServiceCollection(); services.AddSignalRServiceManager(); services.Configure(o => o.ConnectionString = _testConnectionString); - services.AddSingleton(new TestRestClientFactory(UserAgent, statusCode)); + ConfigureTestHttpClient(statusCode)(services); services.AddSingleton(services.ToList() as IReadOnlyCollection); using var serviceManager = services.BuildServiceProvider().GetRequiredService(); @@ -189,7 +188,7 @@ public async Task TestCreateServiceHubContext() using var serviceHubContext = await new ServiceManagerBuilder() .WithOptions(o => o.ConnectionString = _testConnectionString) // avoid waiting for health check result for long time - .ConfigureServices(services => services.AddSingleton(new TestRestClientFactory(UserAgent, HttpStatusCode.OK))) + .ConfigureServices(ConfigureTestHttpClient(HttpStatusCode.OK)) .BuildServiceManager() .CreateHubContextAsync(HubName, default); Assert.Equal(1, (serviceHubContext as ServiceHubContextImpl).ServiceProvider.GetRequiredService>().Value.ConnectionCount); @@ -205,10 +204,16 @@ public async Task TestConnectionCountCustomizable() o.ConnectionCount = 5; }) // avoid waiting for health check result for long time - .ConfigureServices(services => services.AddSingleton(new TestRestClientFactory(UserAgent, HttpStatusCode.OK))) + .ConfigureServices(ConfigureTestHttpClient(HttpStatusCode.OK)) .BuildServiceManager() .CreateHubContextAsync(HubName, default); Assert.Equal(5, (serviceHubContext as ServiceHubContextImpl).ServiceProvider.GetRequiredService>().Value.ConnectionCount); } + + private static Action ConfigureTestHttpClient(HttpStatusCode statusCode) + { + return services => services.AddHttpClient(Constants.HttpClientNames.UserDefault) + .ConfigurePrimaryHttpMessageHandler(() => new TestRootHandler(statusCode)); + } } } \ No newline at end of file diff --git a/test/Microsoft.Azure.SignalR.Tests.Common/TestRestClientFactory.cs b/test/Microsoft.Azure.SignalR.Tests.Common/TestRestClientFactory.cs deleted file mode 100644 index 0b5ca24a4..000000000 --- a/test/Microsoft.Azure.SignalR.Tests.Common/TestRestClientFactory.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Net; -using System.Net.Http; -using System.Threading; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.Azure.SignalR.Tests.Common -{ - internal class TestRestClientFactory : RestClientFactory - { - private const string HttpClientName = "TestRestClient"; - - public TestRestClientFactory(string userAgent, HttpStatusCode code) : base(userAgent, CreateTestFactory(new TestRootHandler(code))) - { } - - public TestRestClientFactory(string userAgent, HttpStatusCode code, string content) : base(userAgent, CreateTestFactory(new TestRootHandler(code, content))) - { } - - public TestRestClientFactory(string userAgent, Action callback) : base(userAgent, CreateTestFactory(new TestRootHandler(callback))) - { } - - protected override HttpClient CreateHttpClient() - { - return _httpClientFactory.CreateClient(HttpClientName); - } - - private static IHttpClientFactory CreateTestFactory(TestRootHandler rootHandler) - { - var builder = new ServiceCollection().AddHttpClient(HttpClientName); - builder.ConfigurePrimaryHttpMessageHandler(() => rootHandler); - return builder.Services.BuildServiceProvider().GetRequiredService(); - } - } -} \ No newline at end of file diff --git a/test/Microsoft.Azure.SignalR.Tests.Common/TestRootHandler.cs b/test/Microsoft.Azure.SignalR.Tests.Common/TestRootHandler.cs index c4765eb89..c867f58ed 100644 --- a/test/Microsoft.Azure.SignalR.Tests.Common/TestRootHandler.cs +++ b/test/Microsoft.Azure.SignalR.Tests.Common/TestRootHandler.cs @@ -12,7 +12,7 @@ namespace Microsoft.Azure.SignalR.Tests.Common { internal class TestRootHandler : DelegatingHandler { - private readonly Action _callback; + private readonly Func _callback; private readonly HttpStatusCode _code; private readonly string _content; @@ -27,6 +27,15 @@ public TestRootHandler(HttpStatusCode code, string content) : this(code) } public TestRootHandler(Action callback) + : this((message, token) => + { + callback(message, token); + return Task.CompletedTask; + }) + { + } + + public TestRootHandler(Func callback) : this(HttpStatusCode.OK) { _callback = callback; @@ -37,7 +46,7 @@ protected override void Dispose(bool disposing) base.Dispose(false); } - protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { //to avoid possible retry policy which dispose content, create new content each time var response = new HttpResponseMessage(_code) @@ -48,8 +57,8 @@ protected override Task SendAsync(HttpRequestMessage reques { response.Content = new ByteArrayContent(Encoding.UTF8.GetBytes(_content)); } - _callback?.Invoke(request, cancellationToken); - return Task.FromResult(response); + await _callback?.Invoke(request, cancellationToken); + return response; } } }