From 35dd9473feeceaa93cf137ffdcaa60a9739c60e9 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Fri, 27 Oct 2017 13:37:59 -0700 Subject: [PATCH] Updated shared components used for proxy communication by the gateway --- .../Authentication/Http/HttpClientLookup.cs | 35 ++++++++ .../Exceptions/ConvertStatusToException.cs | 2 +- .../EndpointNotConfiguredException.cs | 14 ++++ .../Mediatr/AuthenticatedRequest.cs | 18 ++-- .../Extensions/Mediatr/ProxyHandler.cs | 83 +++++++++++++++++++ Domain/Sia.Shared/Protocol/IResponse.cs | 9 +- Domain/Sia.Shared/Protocol/Response.cs | 22 +++-- 7 files changed, 168 insertions(+), 15 deletions(-) create mode 100644 Domain/Sia.Shared/Authentication/Http/HttpClientLookup.cs create mode 100644 Domain/Sia.Shared/Exceptions/EndpointNotConfiguredException.cs create mode 100644 Domain/Sia.Shared/Extensions/Mediatr/ProxyHandler.cs diff --git a/Domain/Sia.Shared/Authentication/Http/HttpClientLookup.cs b/Domain/Sia.Shared/Authentication/Http/HttpClientLookup.cs new file mode 100644 index 0000000..b7635f7 --- /dev/null +++ b/Domain/Sia.Shared/Authentication/Http/HttpClientLookup.cs @@ -0,0 +1,35 @@ +using Sia.Shared.Exceptions; +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Text; + +namespace Sia.Shared.Authentication.Http +{ + public class HttpClientLookup + { + private IDictionary _endpointToBaseUrl; + private IDictionary _endpointToHttpClient { get; } + + public HttpClientLookup() + { + _endpointToBaseUrl = new Dictionary(); + _endpointToHttpClient = new Dictionary(); + } + + public void RegisterEndpoint(string endpointName, string baseUrl) + => _endpointToBaseUrl.Add(endpointName, baseUrl); + + public HttpClient GetClientForEndpoint(string endpointName) + { + if (_endpointToHttpClient.TryGetValue(endpointName, out HttpClient storedHttpClient)) return storedHttpClient; + if (!_endpointToBaseUrl.TryGetValue(endpointName, out string baseUrl)) throw new EndpointNotConfiguredException(endpointName); + + var client = HttpClientExtensions.CreateHttpClient(baseUrl); + + _endpointToHttpClient.Add(endpointName, client); + + return client; + } + } +} diff --git a/Domain/Sia.Shared/Exceptions/ConvertStatusToException.cs b/Domain/Sia.Shared/Exceptions/ConvertStatusToException.cs index 753e0e8..e150d91 100644 --- a/Domain/Sia.Shared/Exceptions/ConvertStatusToException.cs +++ b/Domain/Sia.Shared/Exceptions/ConvertStatusToException.cs @@ -5,7 +5,7 @@ namespace Sia.Shared.Exceptions { public static class ConvertStatusToException { - public static void ThrowExceptionOnUnsuccessfulStatus(this IResponse response) + public static void ThrowExceptionOnUnsuccessfulStatus(this IResponse response) { if (response.IsSuccessStatusCode) { diff --git a/Domain/Sia.Shared/Exceptions/EndpointNotConfiguredException.cs b/Domain/Sia.Shared/Exceptions/EndpointNotConfiguredException.cs new file mode 100644 index 0000000..711cbc9 --- /dev/null +++ b/Domain/Sia.Shared/Exceptions/EndpointNotConfiguredException.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sia.Shared.Exceptions +{ + public class EndpointNotConfiguredException : Exception + { + public EndpointNotConfiguredException(string endpointName) + : base($"No base URI was configured for ${endpointName}") + { + } + } +} diff --git a/Domain/Sia.Shared/Extensions/Mediatr/AuthenticatedRequest.cs b/Domain/Sia.Shared/Extensions/Mediatr/AuthenticatedRequest.cs index 1168ab8..b4a5723 100644 --- a/Domain/Sia.Shared/Extensions/Mediatr/AuthenticatedRequest.cs +++ b/Domain/Sia.Shared/Extensions/Mediatr/AuthenticatedRequest.cs @@ -3,19 +3,23 @@ using Sia.Shared.Authentication; namespace Sia.Shared.Requests { - public abstract class AuthenticatedRequest : IRequest + public abstract class AuthenticatedRequest : AuthenticatedRequestBase, IRequest { - protected AuthenticatedRequest(AuthenticatedUserContext userContext) + protected AuthenticatedRequest(AuthenticatedUserContext userContext) : base(userContext) { - UserContext = userContext; } - - public AuthenticatedUserContext UserContext { get; private set; } } - public abstract class AuthenticatedRequest : IRequest + public abstract class AuthenticatedRequest : AuthenticatedRequestBase, IRequest { - protected AuthenticatedRequest(AuthenticatedUserContext userContext) + protected AuthenticatedRequest(AuthenticatedUserContext userContext) : base(userContext) + { + } + } + + public abstract class AuthenticatedRequestBase + { + protected AuthenticatedRequestBase(AuthenticatedUserContext userContext) { UserContext = userContext; } diff --git a/Domain/Sia.Shared/Extensions/Mediatr/ProxyHandler.cs b/Domain/Sia.Shared/Extensions/Mediatr/ProxyHandler.cs new file mode 100644 index 0000000..721be8d --- /dev/null +++ b/Domain/Sia.Shared/Extensions/Mediatr/ProxyHandler.cs @@ -0,0 +1,83 @@ +using MediatR; +using Sia.Shared.Authentication.Http; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using System.Net.Http; +using Sia.Shared.Protocol; +using Sia.Shared.Exceptions; +using Newtonsoft.Json; +using Sia.Shared.Requests; +using System.Net.Http.Headers; + +namespace Sia.Shared.Extensions.Mediatr +{ + public abstract class ProxyHandler : ProxyHandlerBase, IAsyncRequestHandler + where TRequest : AuthenticatedRequest + { + protected ProxyHandler(HttpClientLookup clientFactory, string endpointName) + : base(clientFactory, endpointName) + { + } + + public virtual async Task Handle(TRequest request) + { + var httpResponse = await SendRequest(request); + var logicalResponse = await Response.Create(httpResponse); + logicalResponse.ThrowExceptionOnUnsuccessfulStatus(); + return logicalResponse.Value; + } + } + + public abstract class ProxyHandler : ProxyHandlerBase, IAsyncRequestHandler + where TRequest : AuthenticatedRequest + { + protected ProxyHandler(HttpClientLookup clientFactory, string endpointName) + : base(clientFactory, endpointName) + { + } + + public virtual async Task Handle(TRequest request) + { + var httpResponse = await SendRequest(request); + var logicalResponse = await Response.Create(httpResponse); + logicalResponse.ThrowExceptionOnUnsuccessfulStatus(); + } + } + + public abstract class ProxyHandlerBase + where TRequest: AuthenticatedRequestBase + { + protected readonly HttpClient _client; + protected abstract HttpMethod Method(); + protected abstract string RelativeUri(TRequest request); + protected abstract object MessageContent(TRequest request); + + protected virtual async Task SendRequest(TRequest request) + { + var message = new HttpRequestMessage(Method(), RelativeUri(request)); + AddContentToMessage(request, message); + await AddAuthorizationToMessage(request, message); + return await _client.SendAsync(message); + } + protected virtual void AddContentToMessage(TRequest request, HttpRequestMessage message) + { + var content = MessageContent(request); + if (content is null) return; + var encodedContent = JsonConvert.SerializeObject(content); + var httpContent = new StringContent(encodedContent, Encoding.UTF8, HttpClientExtensions.JsonMediaType); + message.Content = httpContent; + } + protected virtual async Task AddAuthorizationToMessage(TRequest request, HttpRequestMessage message) + { + var tokenResult = await HttpClientExtensions.AcquireTokenAsync(request.UserContext); + message.Headers.Authorization = new AuthenticationHeaderValue(request.UserContext.AuthConfig.Scheme, tokenResult); + } + + protected ProxyHandlerBase(HttpClientLookup clientFactory, string endpointName) + { + _client = clientFactory.GetClientForEndpoint(endpointName); + } + } +} diff --git a/Domain/Sia.Shared/Protocol/IResponse.cs b/Domain/Sia.Shared/Protocol/IResponse.cs index 237196e..c90239d 100644 --- a/Domain/Sia.Shared/Protocol/IResponse.cs +++ b/Domain/Sia.Shared/Protocol/IResponse.cs @@ -2,11 +2,16 @@ namespace Sia.Shared.Protocol { - public interface IResponse + public interface IResponse : IResponse + { + T Value { get; } + + } + + public interface IResponse { HttpStatusCode StatusCode { get; } bool IsSuccessStatusCode { get; } string Content { get; } - T Value { get; } } } diff --git a/Domain/Sia.Shared/Protocol/Response.cs b/Domain/Sia.Shared/Protocol/Response.cs index a330fed..07c5d31 100644 --- a/Domain/Sia.Shared/Protocol/Response.cs +++ b/Domain/Sia.Shared/Protocol/Response.cs @@ -5,13 +5,11 @@ using System.Threading.Tasks; namespace Sia.Shared.Protocol { - public class Response : IResponse + public class Response : Response, IResponse { - public HttpStatusCode StatusCode { get; private set; } - public bool IsSuccessStatusCode { get; private set; } - public string Content { get; private set; } public T Value { get; private set; } - public static async Task> Create(HttpResponseMessage message) + + public static new async Task> Create(HttpResponseMessage message) { var response = new Response(); response.IsSuccessStatusCode = message.IsSuccessStatusCode; @@ -28,4 +26,18 @@ namespace Sia.Shared.Protocol return response; } } + + public class Response : IResponse + { + public HttpStatusCode StatusCode { get; protected set; } + public bool IsSuccessStatusCode { get; protected set; } + public string Content { get; protected set; } + public static async Task Create(HttpResponseMessage message) + => new Response() + { + IsSuccessStatusCode = message.IsSuccessStatusCode, + StatusCode = message.StatusCode, + Content = await message.Content.ReadAsStringAsync() + }; + } }