-
Notifications
You must be signed in to change notification settings - Fork 100
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2dff80e
commit c7498d1
Showing
8 changed files
with
268 additions
and
267 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
23 changes: 23 additions & 0 deletions
23
src/Microsoft.Azure.SignalR.Common/Utilities/Rest/RestApiEndpoint.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using System.Collections.Generic; | ||
|
||
using Microsoft.Extensions.Primitives; | ||
|
||
namespace Microsoft.Azure.SignalR; | ||
|
||
internal class RestApiEndpoint | ||
{ | ||
public string Audience { get; } | ||
|
||
public string Token { get; } | ||
|
||
public IDictionary<string, StringValues> Query { get; set; } | ||
|
||
public RestApiEndpoint(string endpoint, string token) | ||
{ | ||
Audience = endpoint; | ||
Token = token; | ||
} | ||
} |
192 changes: 192 additions & 0 deletions
192
src/Microsoft.Azure.SignalR.Common/Utilities/Rest/RestClient.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
// 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.Collections.Generic; | ||
using System.Net; | ||
using System.Net.Http; | ||
using System.Net.Http.Headers; | ||
using System.Text; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.Azure.SignalR.Common; | ||
using Microsoft.Extensions.Primitives; | ||
|
||
#nullable enable | ||
|
||
namespace Microsoft.Azure.SignalR; | ||
|
||
internal class RestClient | ||
{ | ||
private readonly IHttpClientFactory _httpClientFactory; | ||
|
||
private readonly IPayloadContentBuilder _payloadContentBuilder; | ||
|
||
public RestClient(IHttpClientFactory httpClientFactory, IPayloadContentBuilder contentBuilder) | ||
{ | ||
_httpClientFactory = httpClientFactory; | ||
_payloadContentBuilder = contentBuilder; | ||
} | ||
|
||
public Task SendAsync(RestApiEndpoint api, | ||
HttpMethod httpMethod, | ||
string? methodName = null, | ||
object[]? args = null, | ||
Func<HttpResponseMessage, bool>? handleExpectedResponse = null, | ||
CancellationToken cancellationToken = default) | ||
{ | ||
return handleExpectedResponse == null | ||
? SendAsync(api, httpMethod, methodName, args, handleExpectedResponseAsync: null, cancellationToken) | ||
: SendAsync(api, httpMethod, methodName, args, response => Task.FromResult(handleExpectedResponse(response)), cancellationToken); | ||
} | ||
|
||
public Task SendAsync(RestApiEndpoint api, | ||
HttpMethod httpMethod, | ||
string? methodName = null, | ||
object[]? args = null, | ||
Func<HttpResponseMessage, Task<bool>>? handleExpectedResponseAsync = null, | ||
CancellationToken cancellationToken = default) | ||
{ | ||
return SendAsyncCore(Constants.HttpClientNames.UserDefault, | ||
api, | ||
httpMethod, | ||
methodName, | ||
args, | ||
handleExpectedResponseAsync, | ||
cancellationToken); | ||
} | ||
|
||
public Task SendWithRetryAsync(RestApiEndpoint api, | ||
HttpMethod httpMethod, | ||
string? methodName = null, | ||
object[]? args = null, | ||
Func<HttpResponseMessage, bool>? handleExpectedResponse = null, | ||
CancellationToken cancellationToken = default) | ||
{ | ||
return SendAsyncCore(Constants.HttpClientNames.Resilient, | ||
api, | ||
httpMethod, | ||
methodName, | ||
args, | ||
handleExpectedResponse == null ? null : response => Task.FromResult(handleExpectedResponse(response)), | ||
cancellationToken); | ||
} | ||
|
||
public Task SendMessageWithRetryAsync(RestApiEndpoint api, | ||
HttpMethod httpMethod, | ||
string? methodName = null, | ||
object[]? args = null, | ||
Func<HttpResponseMessage, bool>? handleExpectedResponse = null, | ||
CancellationToken cancellationToken = default) | ||
{ | ||
return SendAsyncCore(Constants.HttpClientNames.MessageResilient, | ||
api, | ||
httpMethod, | ||
methodName, | ||
args, | ||
handleExpectedResponse == null ? null : response => Task.FromResult(handleExpectedResponse(response)), | ||
cancellationToken); | ||
} | ||
|
||
private static Uri GetUri(string url, IDictionary<string, StringValues>? query) | ||
{ | ||
if (query == null || query.Count == 0) | ||
{ | ||
return new Uri(url); | ||
} | ||
var builder = new UriBuilder(url); | ||
var sb = new StringBuilder(builder.Query); | ||
if (sb.Length == 1 && sb[0] == '?') | ||
{ | ||
sb.Clear(); | ||
} | ||
else if (sb.Length > 0 && sb[0] != '?') | ||
{ | ||
sb.Insert(0, '?'); | ||
} | ||
foreach (var item in query) | ||
{ | ||
foreach (var value in item.Value) | ||
{ | ||
sb.Append(sb.Length > 0 ? '&' : '?'); | ||
sb.Append(Uri.EscapeDataString(item.Key)); | ||
sb.Append('='); | ||
sb.Append(Uri.EscapeDataString(value!)); | ||
} | ||
} | ||
builder.Query = sb.ToString(); | ||
return builder.Uri; | ||
} | ||
|
||
private async Task ThrowExceptionOnResponseFailureAsync(HttpResponseMessage response) | ||
{ | ||
if (response.IsSuccessStatusCode) | ||
{ | ||
return; | ||
} | ||
|
||
var detail = await response.Content.ReadAsStringAsync(); | ||
|
||
#if NET5_0_OR_GREATER | ||
var innerException = new HttpRequestException( | ||
$"Response status code does not indicate success: {(int)response.StatusCode} ({response.ReasonPhrase})", null, response.StatusCode); | ||
#else | ||
var innerException = new HttpRequestException( | ||
$"Response status code does not indicate success: {(int)response.StatusCode} ({response.ReasonPhrase})"); | ||
#endif | ||
throw response.StatusCode switch | ||
{ | ||
HttpStatusCode.BadRequest => new AzureSignalRInvalidArgumentException(response.RequestMessage?.RequestUri?.ToString(), innerException, detail), | ||
HttpStatusCode.Unauthorized => new AzureSignalRUnauthorizedException(response.RequestMessage?.RequestUri?.ToString(), innerException), | ||
HttpStatusCode.NotFound => new AzureSignalRInaccessibleEndpointException(response.RequestMessage?.RequestUri?.ToString(), innerException), | ||
_ => new AzureSignalRRuntimeException(response.RequestMessage?.RequestUri?.ToString(), innerException), | ||
}; | ||
} | ||
|
||
private async Task SendAsyncCore( | ||
string httpClientName, | ||
RestApiEndpoint api, | ||
HttpMethod httpMethod, | ||
string? methodName = null, | ||
object[]? args = null, | ||
Func<HttpResponseMessage, Task<bool>>? handleExpectedResponseAsync = null, | ||
CancellationToken cancellationToken = default) | ||
{ | ||
using var httpClient = _httpClientFactory.CreateClient(httpClientName); | ||
using var request = BuildRequest(api, httpMethod, methodName, args); | ||
|
||
try | ||
{ | ||
using var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); | ||
if (handleExpectedResponseAsync == null) | ||
{ | ||
await ThrowExceptionOnResponseFailureAsync(response); | ||
} | ||
else | ||
{ | ||
if (!await handleExpectedResponseAsync(response)) | ||
{ | ||
await ThrowExceptionOnResponseFailureAsync(response); | ||
} | ||
} | ||
} | ||
catch (HttpRequestException ex) | ||
{ | ||
throw new AzureSignalRException($"An error happened when making request to {request.RequestUri}", ex); | ||
} | ||
} | ||
|
||
private HttpRequestMessage BuildRequest(RestApiEndpoint api, HttpMethod httpMethod, string? methodName = null, object[]? args = null) | ||
{ | ||
var payload = httpMethod == HttpMethod.Post ? new PayloadMessage { Target = methodName, Arguments = args } : null; | ||
return GenerateHttpRequest(api.Audience, api.Query, httpMethod, payload, api.Token); | ||
} | ||
|
||
private HttpRequestMessage GenerateHttpRequest(string url, IDictionary<string, StringValues> query, HttpMethod httpMethod, PayloadMessage? payload, string tokenString) | ||
{ | ||
var request = new HttpRequestMessage(httpMethod, GetUri(url, query)); | ||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokenString); | ||
request.Content = _payloadContentBuilder.Build(payload); | ||
return request; | ||
} | ||
} |
47 changes: 0 additions & 47 deletions
47
src/Microsoft.Azure.SignalR.Common/Utilities/RestApiAccessTokenGenerator.cs
This file was deleted.
Oops, something went wrong.
24 changes: 0 additions & 24 deletions
24
src/Microsoft.Azure.SignalR.Common/Utilities/RestApiEndpoint.cs
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.