From ca4abc68ef1d00a062bac5a3a8d1709e4c985f8e Mon Sep 17 00:00:00 2001 From: Daniel Murrmann Date: Tue, 26 Sep 2023 11:21:36 +0200 Subject: [PATCH] Various improvements and added Put and Post Verbs to router --- ...eLinker.Gateway.EntityFrameworkCore.csproj | 2 +- .../Fancy.ResourceLinker.Gateway.csproj | 2 +- .../Routing/GatewayRouter.cs | 193 +++++++++++++----- .../Routing/IResourceCache.cs | 4 +- .../Routing/InMemoryResourceCache.cs | 6 +- .../Fancy.ResourceLinker.Hateoas.csproj | 2 +- .../Fancy.ResourceLinker.Models.csproj | 2 +- 7 files changed, 150 insertions(+), 61 deletions(-) diff --git a/src/Fancy.ResourceLinker.Gateway.EntityFrameworkCore/Fancy.ResourceLinker.Gateway.EntityFrameworkCore.csproj b/src/Fancy.ResourceLinker.Gateway.EntityFrameworkCore/Fancy.ResourceLinker.Gateway.EntityFrameworkCore.csproj index 35b8d3e..a0e3c5e 100644 --- a/src/Fancy.ResourceLinker.Gateway.EntityFrameworkCore/Fancy.ResourceLinker.Gateway.EntityFrameworkCore.csproj +++ b/src/Fancy.ResourceLinker.Gateway.EntityFrameworkCore/Fancy.ResourceLinker.Gateway.EntityFrameworkCore.csproj @@ -1,7 +1,7 @@  - 0.0.4 + 0.0.5 net6.0 enable enable diff --git a/src/Fancy.ResourceLinker.Gateway/Fancy.ResourceLinker.Gateway.csproj b/src/Fancy.ResourceLinker.Gateway/Fancy.ResourceLinker.Gateway.csproj index 9e9415b..31cfd15 100644 --- a/src/Fancy.ResourceLinker.Gateway/Fancy.ResourceLinker.Gateway.csproj +++ b/src/Fancy.ResourceLinker.Gateway/Fancy.ResourceLinker.Gateway.csproj @@ -1,7 +1,7 @@  - 0.0.4 + 0.0.5 net6.0 enable enable diff --git a/src/Fancy.ResourceLinker.Gateway/Routing/GatewayRouter.cs b/src/Fancy.ResourceLinker.Gateway/Routing/GatewayRouter.cs index 6b575a8..883b1f6 100644 --- a/src/Fancy.ResourceLinker.Gateway/Routing/GatewayRouter.cs +++ b/src/Fancy.ResourceLinker.Gateway/Routing/GatewayRouter.cs @@ -94,21 +94,38 @@ public GatewayRouter(GatewayRoutingSettings settings, IHttpForwarder forwarder, _serializerOptions.AddResourceConverter(); } + private async Task SetTokenToRequest(HttpRequestMessage request) + { + string accessToken; + if (_tokenService != null) + { + // A user session exists, get token from token service + accessToken = await _tokenService.GetAccessTokenAsync(); + } + else if (_tokenClient != null) + { + // Fall back to client credentials token directly + ClientCredentialsTokenResponse? tokenResponse = await _tokenClient.GetTokenViaClientCredentialsAsync(); + if (tokenResponse == null) throw new InvalidOperationException("Could not retrieve token via client credentials."); + accessToken = tokenResponse.AccessToken; + } + else + { + throw new InvalidOperationException($"If you want to send access tokens, gateway authentication must be configured."); + } + + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + } + /// - /// Gets data from a url and deserializes it into a given type. + /// Sends a request and deserializes the response into a given type. /// /// The type of the resource. - /// The uri of the data to get. + /// The request to send. + /// I true, the request will be enriched with an access token. /// The result deserialized into the specified resource type. - private async Task GetAsync(Uri requestUri, bool sendAccessToken) where TResource : class + private async Task SendAsync(HttpRequestMessage request, bool sendAccessToken) where TResource : class { - // Set up request - HttpRequestMessage request = new HttpRequestMessage() - { - RequestUri = requestUri, - Method = HttpMethod.Get - }; - if (_settings.ResourceProxy != null) { request.Headers.Add("X-Forwarded-Host", _settings.ResourceProxy); @@ -116,42 +133,69 @@ private async Task GetAsync(Uri requestUri, bool sendAcces if(sendAccessToken) { - string accessToken; - if (_tokenService != null) - { - // A user session exists, get token from token service - accessToken = await _tokenService.GetAccessTokenAsync(); - } - else if(_tokenClient != null) - { - // Fall back to client credentials token directly - ClientCredentialsTokenResponse? tokenResponse = await _tokenClient.GetTokenViaClientCredentialsAsync(); - if (tokenResponse == null) throw new InvalidOperationException("Could not retrieve token via client credentials."); - accessToken = tokenResponse.AccessToken; - } - else - { - throw new InvalidOperationException($"If '{nameof(sendAccessToken)}' is 'true', gateway authentication must be configured."); - } - - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + await SetTokenToRequest(request); } // Get data from microservice HttpResponseMessage responseMessage = await _httpClient.SendAsync(request); responseMessage.EnsureSuccessStatusCode(); - if (responseMessage.StatusCode == System.Net.HttpStatusCode.OK) + if (responseMessage.Content.Headers.ContentLength > 0)// responseMessage.StatusCode == System.Net.HttpStatusCode.OK) { string jsonResponse = await responseMessage.Content.ReadAsStringAsync(); return JsonSerializer.Deserialize(jsonResponse, _serializerOptions) ?? throw new Exception("Error on deserialization of result"); } else { - throw new Exception("No Content was provided by the server"); ; + return default;// throw new Exception("No Content was provided by the server"); } } + /// + /// Sends a request. + /// + /// The request to send. + /// I true, the request will be enriched with an access token. + private async Task SendAsync(HttpRequestMessage request, bool sendAccessToken) + { + if (_settings.ResourceProxy != null) + { + request.Headers.Add("X-Forwarded-Host", _settings.ResourceProxy); + } + + if (sendAccessToken) + { + await SetTokenToRequest(request); + } + + // Get data from microservice + HttpResponseMessage responseMessage = await _httpClient.SendAsync(request); + responseMessage.EnsureSuccessStatusCode(); + } + + /// + /// Gets data from a url and deserializes it into a given type. + /// + /// The type of the resource. + /// The uri of the data to get. + /// I true, the request will be enriched with an access token. + /// The result deserialized into the specified resource type. + private async Task GetAsync(Uri requestUri, bool sendAccessToken) where TResource : class + { + // Set up request + HttpRequestMessage request = new HttpRequestMessage() + { + RequestUri = requestUri, + Method = HttpMethod.Get, + }; + + var result = await SendAsync(request, sendAccessToken); + + if(result == null) throw new ApplicationException("No Content was provided by the server"); + + return result; + } + /// /// Get data from a microservice specified by its key of a provided route and deserializes it into a given type. /// @@ -165,6 +209,68 @@ public Task GetAsync(string routeKey, string relativeUrl) return GetAsync(requestUri, _settings.Routes[routeKey].EnforceAuthentication); } + /// + /// Puts data to a specific uri. + /// + /// The uri to send to. + /// The content to send - will be serialized as json. + /// I true, the request will be enriched with an access token. + private Task PutAsync(Uri requestUri, object content, bool sendAccessToken) + { + // Set up request + HttpRequestMessage request = new HttpRequestMessage() + { + RequestUri = requestUri, + Method = HttpMethod.Put, + Content = new StringContent(JsonSerializer.Serialize(content), Encoding.UTF8, "application/json") + }; + + return SendAsync(request, sendAccessToken); + } + + /// + /// Puts data to a specific uri. + /// + /// The key of the route to use. + /// The relative url to the endpoint. + /// The content to send - will be serialized as json. + public Task PutAsync(string routeKey, string relativeUrl, object content) + { + Uri requestUri = CombineUris(GetBaseUrl(routeKey), relativeUrl); + return PutAsync(requestUri, content, _settings.Routes[routeKey].EnforceAuthentication); + } + + /// + /// Post data to a specific uri. + /// + /// The uri to send to. + /// The content to send - will be serialized as json. + /// I true, the request will be enriched with an access token. + private Task PostAsync(Uri requestUri, object content, bool sendAccessToken) + { + // Set up request + HttpRequestMessage request = new HttpRequestMessage() + { + RequestUri = requestUri, + Method = HttpMethod.Post, + Content = new StringContent(JsonSerializer.Serialize(content), Encoding.UTF8, "application/json") + }; + + return SendAsync(request, sendAccessToken); + } + + /// + /// Post data to a specific uri. + /// + /// The key of the route to use. + /// The relative url to the endpoint. + /// The content to send - will be serialized as json. + public Task PostAsync(string routeKey, string relativeUrl, object content) + { + Uri requestUri = CombineUris(GetBaseUrl(routeKey), relativeUrl); + return PostAsync(requestUri, content, _settings.Routes[routeKey].EnforceAuthentication); + } + /// /// Gets data from a url and deserializes it into a given type. If data is available in the cache and not older /// as the age specified the data is returned from the chache, if not data is retrieved from the origin and written to the cache. @@ -175,7 +281,7 @@ public Task GetAsync(string routeKey, string relativeUrl) /// /// The result deserialized into the specified resource type. /// - public async Task GetCachedAsync(Uri requestUri, TimeSpan maxResourceAge, bool sendAccessToken) where TResource : ResourceBase + public async Task GetCachedAsync(Uri requestUri, TimeSpan maxResourceAge, bool sendAccessToken) where TResource : class { string cacheKey = requestUri.ToString(); @@ -183,7 +289,7 @@ public async Task GetCachedAsync(Uri requestUri, TimeSpan TResource? data; if (_resourceCache.TryRead(cacheKey, maxResourceAge, out data)) { - return data ?? throw new Exception("Error on reading item from cache"); + return data ?? throw new ApplicationException("Error on reading item from cache"); } else { @@ -206,7 +312,7 @@ public async Task GetCachedAsync(Uri requestUri, TimeSpan /// /// The result deserialized into the specified resource type. /// - public Task GetCachedAsync(string routeKey, string relativeUrl, TimeSpan maxResourceAge) where TResource : ResourceBase + public Task GetCachedAsync(string routeKey, string relativeUrl, TimeSpan maxResourceAge) where TResource : class { Uri requestUri = CombineUris(GetBaseUrl(routeKey), relativeUrl); return GetCachedAsync(requestUri, maxResourceAge, _settings.Routes[routeKey].EnforceAuthentication); @@ -246,24 +352,7 @@ public async Task ProxyAsync(HttpContext httpContext, string rout if (_settings.Routes[routeKey].EnforceAuthentication) { - string accessToken; - if (_tokenService != null) - { - // A user session exists, get token from token service - accessToken = await _tokenService.GetAccessTokenAsync(); - } - else if (_tokenClient != null) - { - // Fall back to client credentials token directly - ClientCredentialsTokenResponse? tokenResponse = await _tokenClient.GetTokenViaClientCredentialsAsync(); - if (tokenResponse == null) throw new InvalidOperationException("Could not retrieve token via client credentials."); - accessToken = tokenResponse.AccessToken; - } - else - { - throw new InvalidOperationException($"If 'EnforceAuthentication' is 'true', gateway authentication must be configured."); - } - proxyRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + await SetTokenToRequest(proxyRequest); } HttpResponseMessage proxyResponse = await _httpClient.SendAsync(proxyRequest); diff --git a/src/Fancy.ResourceLinker.Gateway/Routing/IResourceCache.cs b/src/Fancy.ResourceLinker.Gateway/Routing/IResourceCache.cs index a15ac3f..21e26e5 100644 --- a/src/Fancy.ResourceLinker.Gateway/Routing/IResourceCache.cs +++ b/src/Fancy.ResourceLinker.Gateway/Routing/IResourceCache.cs @@ -13,7 +13,7 @@ public interface IResourceCache /// The type of the resource. /// The key to save the resource under. /// The resource instance to save. - void Write(string key, TResource resource) where TResource : ResourceBase; + void Write(string key, TResource resource) where TResource : class; /// /// Tries to read a resource from the cache. @@ -23,5 +23,5 @@ public interface IResourceCache /// The maximum age of the resource. /// The resource. /// True if the cache was able to read and provide a valid resource instance; otherwise, false. - bool TryRead(string key, TimeSpan maxResourceAge, out TResource? resource) where TResource : ResourceBase; + bool TryRead(string key, TimeSpan maxResourceAge, out TResource? resource) where TResource : class; } diff --git a/src/Fancy.ResourceLinker.Gateway/Routing/InMemoryResourceCache.cs b/src/Fancy.ResourceLinker.Gateway/Routing/InMemoryResourceCache.cs index 1163564..d067c76 100644 --- a/src/Fancy.ResourceLinker.Gateway/Routing/InMemoryResourceCache.cs +++ b/src/Fancy.ResourceLinker.Gateway/Routing/InMemoryResourceCache.cs @@ -20,7 +20,7 @@ public class InMemoryResourceCache : IResourceCache /// The type of the resource. /// The key to save the resource under. /// The resource instance to save. - public void Write(string key, TResource resource) where TResource : ResourceBase + public void Write(string key, TResource resource) where TResource : class { _cache[key] = new Tuple(DateTime.Now, resource); } @@ -35,9 +35,9 @@ public void Write(string key, TResource resource) where TResource : R /// /// True if the cache was able to read and provide a valid resource instance; otherwise, false. /// - public bool TryRead(string key, TimeSpan maxResourceAge, out TResource? resource) where TResource : ResourceBase + public bool TryRead(string key, TimeSpan maxResourceAge, out TResource? resource) where TResource : class { - resource = null; + resource = default; // Check if the key exists within the cache if (_cache.ContainsKey(key)) diff --git a/src/Fancy.ResourceLinker.Hateoas/Fancy.ResourceLinker.Hateoas.csproj b/src/Fancy.ResourceLinker.Hateoas/Fancy.ResourceLinker.Hateoas.csproj index 6c95472..329e4d0 100644 --- a/src/Fancy.ResourceLinker.Hateoas/Fancy.ResourceLinker.Hateoas.csproj +++ b/src/Fancy.ResourceLinker.Hateoas/Fancy.ResourceLinker.Hateoas.csproj @@ -1,7 +1,7 @@ - 0.0.4 + 0.0.5 net6.0 enable enable diff --git a/src/Fancy.ResourceLinker.Models/Fancy.ResourceLinker.Models.csproj b/src/Fancy.ResourceLinker.Models/Fancy.ResourceLinker.Models.csproj index 000a53e..793b29f 100644 --- a/src/Fancy.ResourceLinker.Models/Fancy.ResourceLinker.Models.csproj +++ b/src/Fancy.ResourceLinker.Models/Fancy.ResourceLinker.Models.csproj @@ -1,7 +1,7 @@  - 0.0.4 + 0.0.5 net6.0 enable enable