From 5e2e1b26e82e7a90df544782babb9c83bf493f82 Mon Sep 17 00:00:00 2001 From: "Bjarte K. Helland" Date: Mon, 22 Feb 2021 10:00:34 +0100 Subject: [PATCH 1/4] Add possibility to set Connection to Close ( request.Headers.ConnectionClose = ConnectionClose ) in order to prevent persistent connections. --- .../Creator/ConnectionCloseCreator.cs | 14 ++ .../Creator/IConnectionCloseCreator.cs | 9 ++ .../Configuration/Creator/RoutesCreator.cs | 8 +- src/Ocelot/Configuration/DownstreamRoute.cs | 6 +- .../File/FileGlobalConfiguration.cs | 3 + src/Ocelot/Configuration/File/FileRoute.cs | 7 +- .../DependencyInjection/OcelotBuilder.cs | 1 + src/Ocelot/Requester/HttpClientBuilder.cs | 129 ++++++++++++++++++ src/Ocelot/Requester/HttpClientWrapper.cs | 28 ++++ 9 files changed, 201 insertions(+), 4 deletions(-) create mode 100644 src/Ocelot/Configuration/Creator/ConnectionCloseCreator.cs create mode 100644 src/Ocelot/Configuration/Creator/IConnectionCloseCreator.cs create mode 100644 src/Ocelot/Requester/HttpClientBuilder.cs create mode 100644 src/Ocelot/Requester/HttpClientWrapper.cs diff --git a/src/Ocelot/Configuration/Creator/ConnectionCloseCreator.cs b/src/Ocelot/Configuration/Creator/ConnectionCloseCreator.cs new file mode 100644 index 000000000..fd0d330cd --- /dev/null +++ b/src/Ocelot/Configuration/Creator/ConnectionCloseCreator.cs @@ -0,0 +1,14 @@ +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public class ConnectionCloseCreator : IConnectionCloseCreator + { + public bool Create(bool fileRouteConnectionClose, FileGlobalConfiguration globalConfiguration) + { + var globalConnectionClose = globalConfiguration.ConnectionClose; + + return fileRouteConnectionClose || globalConnectionClose; + } + } +} diff --git a/src/Ocelot/Configuration/Creator/IConnectionCloseCreator.cs b/src/Ocelot/Configuration/Creator/IConnectionCloseCreator.cs new file mode 100644 index 000000000..8fea30c92 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/IConnectionCloseCreator.cs @@ -0,0 +1,9 @@ +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IConnectionCloseCreator + { + bool Create(bool fileRouteConnectionClose, FileGlobalConfiguration globalConfiguration); + } +} diff --git a/src/Ocelot/Configuration/Creator/RoutesCreator.cs b/src/Ocelot/Configuration/Creator/RoutesCreator.cs index 015944014..a3b65ce38 100644 --- a/src/Ocelot/Configuration/Creator/RoutesCreator.cs +++ b/src/Ocelot/Configuration/Creator/RoutesCreator.cs @@ -23,6 +23,7 @@ public class RoutesCreator : IRoutesCreator private readonly IVersionCreator _versionCreator; private readonly IVersionPolicyCreator _versionPolicyCreator; private readonly IMetadataCreator _metadataCreator; + private readonly IConnectionCloseCreator _connectionCloseCreator; public RoutesCreator( IClaimsToThingCreator claimsToThingCreator, @@ -42,7 +43,8 @@ public RoutesCreator( IVersionCreator versionCreator, IVersionPolicyCreator versionPolicyCreator, IUpstreamHeaderTemplatePatternCreator upstreamHeaderTemplatePatternCreator, - IMetadataCreator metadataCreator) + IMetadataCreator metadataCreator, + IConnectionCloseCreator connectionCloseCreator) { _routeKeyCreator = routeKeyCreator; _loadBalancerOptionsCreator = loadBalancerOptionsCreator; @@ -63,6 +65,7 @@ public RoutesCreator( _versionPolicyCreator = versionPolicyCreator; _upstreamHeaderTemplatePatternCreator = upstreamHeaderTemplatePatternCreator; _metadataCreator = metadataCreator; + _connectionCloseCreator = connectionCloseCreator; } public List Create(FileConfiguration fileConfiguration) @@ -118,6 +121,8 @@ private DownstreamRoute SetUpDownstreamRoute(FileRoute fileRoute, FileGlobalConf var metadata = _metadataCreator.Create(fileRoute.Metadata, globalConfiguration); + var connectionClose = _connectionCloseCreator.Create(fileRoute.ConnectionClose, globalConfiguration); + var route = new DownstreamRouteBuilder() .WithKey(fileRoute.Key) .WithDownstreamPathTemplate(fileRoute.DownstreamPathTemplate) @@ -156,6 +161,7 @@ private DownstreamRoute SetUpDownstreamRoute(FileRoute fileRoute, FileGlobalConf .WithDownstreamHttpVersionPolicy(downstreamHttpVersionPolicy) .WithDownStreamHttpMethod(fileRoute.DownstreamHttpMethod) .WithMetadata(metadata) + .WithConnectionClose(connectionClose) .Build(); return route; diff --git a/src/Ocelot/Configuration/DownstreamRoute.cs b/src/Ocelot/Configuration/DownstreamRoute.cs index 818390a02..8fb806540 100644 --- a/src/Ocelot/Configuration/DownstreamRoute.cs +++ b/src/Ocelot/Configuration/DownstreamRoute.cs @@ -42,7 +42,8 @@ public DownstreamRoute( Version downstreamHttpVersion, HttpVersionPolicy downstreamHttpVersionPolicy, Dictionary upstreamHeaders, - MetadataOptions metadataOptions) + MetadataOptions metadataOptions, + bool connectionClose) { DangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator; AddHeadersToDownstream = addHeadersToDownstream; @@ -81,6 +82,7 @@ public DownstreamRoute( DownstreamHttpVersionPolicy = downstreamHttpVersionPolicy; UpstreamHeaders = upstreamHeaders ?? new(); MetadataOptions = metadataOptions; + ConnectionClose = connectionClose; } public string Key { get; } @@ -137,5 +139,7 @@ public DownstreamRoute( public string Name() => string.IsNullOrEmpty(ServiceName) && !UseServiceDiscovery ? UpstreamPathTemplate?.Template ?? DownstreamPathTemplate?.Value ?? "?" : string.Join(':', ServiceNamespace, ServiceName, UpstreamPathTemplate?.Template); + + public bool ConnectionClose { get; } } } diff --git a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs index 7ce35f99e..63cdd7ffb 100644 --- a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs +++ b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs @@ -13,6 +13,7 @@ public FileGlobalConfiguration() HttpHandlerOptions = new FileHttpHandlerOptions(); CacheOptions = new FileCacheOptions(); MetadataOptions = new FileMetadataOptions(); + ConnectionClose = false; } public string RequestIdKey { get; set; } @@ -48,5 +49,7 @@ public FileGlobalConfiguration() public FileCacheOptions CacheOptions { get; set; } public FileMetadataOptions MetadataOptions { get; set; } + + public bool ConnectionClose { get; set; } } } diff --git a/src/Ocelot/Configuration/File/FileRoute.cs b/src/Ocelot/Configuration/File/FileRoute.cs index b9a3c4eda..6735ac588 100644 --- a/src/Ocelot/Configuration/File/FileRoute.cs +++ b/src/Ocelot/Configuration/File/FileRoute.cs @@ -26,6 +26,7 @@ public FileRoute() UpstreamHeaderTemplates = new Dictionary(); UpstreamHeaderTransform = new Dictionary(); UpstreamHttpMethod = new List(); + ConnectionClose = false; } public FileRoute(FileRoute from) @@ -38,6 +39,7 @@ public FileRoute(FileRoute from) public Dictionary AddQueriesToRequest { get; set; } public FileAuthenticationOptions AuthenticationOptions { get; set; } public Dictionary ChangeDownstreamPathTemplate { get; set; } + public bool ConnectionClose { get; set; } public bool DangerousAcceptAnyServerCertificateValidator { get; set; } public List DelegatingHandlers { get; set; } public Dictionary DownstreamHeaderTransform { get; set; } @@ -57,7 +59,7 @@ public FileRoute(FileRoute from) /// public string DownstreamHttpVersionPolicy { get; set; } public string DownstreamPathTemplate { get; set; } - public string DownstreamScheme { get; set; } + public string DownstreamScheme { get; set; } public FileCacheOptions FileCacheOptions { get; set; } public FileHttpHandlerOptions HttpHandlerOptions { get; set; } public string Key { get; set; } @@ -97,6 +99,7 @@ public static void DeepCopy(FileRoute from, FileRoute to) to.AddQueriesToRequest = new(from.AddQueriesToRequest); to.AuthenticationOptions = new(from.AuthenticationOptions); to.ChangeDownstreamPathTemplate = new(from.ChangeDownstreamPathTemplate); + to.ConnectionClose = from.ConnectionClose; to.DangerousAcceptAnyServerCertificateValidator = from.DangerousAcceptAnyServerCertificateValidator; to.DelegatingHandlers = new(from.DelegatingHandlers); to.DownstreamHeaderTransform = new(from.DownstreamHeaderTransform); @@ -105,7 +108,7 @@ public static void DeepCopy(FileRoute from, FileRoute to) to.DownstreamHttpVersion = from.DownstreamHttpVersion; to.DownstreamHttpVersionPolicy = from.DownstreamHttpVersionPolicy; to.DownstreamPathTemplate = from.DownstreamPathTemplate; - to.DownstreamScheme = from.DownstreamScheme; + to.DownstreamScheme = from.DownstreamScheme; to.FileCacheOptions = new(from.FileCacheOptions); to.HttpHandlerOptions = new(from.HttpHandlerOptions); to.Key = from.Key; diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 642697744..c1d5b5707 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -71,6 +71,7 @@ public OcelotBuilder(IServiceCollection services, IConfiguration configurationRo Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); + Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs new file mode 100644 index 000000000..bbb93d2cc --- /dev/null +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -0,0 +1,129 @@ +using System; +using System.Linq; +using System.Net; +using System.Net.Http; + +using Ocelot.Configuration; + +using Ocelot.Logging; + +namespace Ocelot.Requester +{ + public class HttpClientBuilder : IHttpClientBuilder + { + private readonly IDelegatingHandlerHandlerFactory _factory; + private readonly IHttpClientCache _cacheHandlers; + private readonly IOcelotLogger _logger; + private DownstreamRoute _cacheKey; + private HttpClient _httpClient; + private IHttpClient _client; + private readonly TimeSpan _defaultTimeout; + + public HttpClientBuilder( + IDelegatingHandlerHandlerFactory factory, + IHttpClientCache cacheHandlers, + IOcelotLogger logger) + { + _factory = factory; + _cacheHandlers = cacheHandlers; + _logger = logger; + + // This is hardcoded at the moment but can easily be added to configuration + // if required by a user request. + _defaultTimeout = TimeSpan.FromSeconds(90); + } + + public IHttpClient Create(DownstreamRoute downstreamRoute) + { + _cacheKey = downstreamRoute; + + var httpClient = _cacheHandlers.Get(_cacheKey); + + if (httpClient != null) + { + _client = httpClient; + return httpClient; + } + + var handler = CreateHandler(downstreamRoute); + + if (downstreamRoute.DangerousAcceptAnyServerCertificateValidator) + { + handler.ServerCertificateCustomValidationCallback = + HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; + + _logger + .LogWarning($"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamRoute, UpstreamPathTemplate: {downstreamRoute.UpstreamPathTemplate}, DownstreamPathTemplate: {downstreamRoute.DownstreamPathTemplate}"); + } + + var timeout = downstreamRoute.QosOptions.TimeoutValue == 0 + ? _defaultTimeout + : TimeSpan.FromMilliseconds(downstreamRoute.QosOptions.TimeoutValue); + + _httpClient = new HttpClient(CreateHttpMessageHandler(handler, downstreamRoute)) + { + Timeout = timeout, + }; + + _client = new HttpClientWrapper(_httpClient, downstreamRoute.ConnectionClose); + + return _client; + } + + private static HttpClientHandler CreateHandler(DownstreamRoute downstreamRoute) + { + // Dont' create the CookieContainer if UseCookies is not set or the HttpClient will complain + // under .Net Full Framework + var useCookies = downstreamRoute.HttpHandlerOptions.UseCookieContainer; + + return useCookies ? UseCookiesHandler(downstreamRoute) : UseNonCookiesHandler(downstreamRoute); + } + + private static HttpClientHandler UseNonCookiesHandler(DownstreamRoute downstreamRoute) + { + return new HttpClientHandler + { + AllowAutoRedirect = downstreamRoute.HttpHandlerOptions.AllowAutoRedirect, + UseCookies = downstreamRoute.HttpHandlerOptions.UseCookieContainer, + UseProxy = downstreamRoute.HttpHandlerOptions.UseProxy, + MaxConnectionsPerServer = downstreamRoute.HttpHandlerOptions.MaxConnectionsPerServer, + + }; + } + + private static HttpClientHandler UseCookiesHandler(DownstreamRoute downstreamRoute) + { + return new HttpClientHandler + { + AllowAutoRedirect = downstreamRoute.HttpHandlerOptions.AllowAutoRedirect, + UseCookies = downstreamRoute.HttpHandlerOptions.UseCookieContainer, + UseProxy = downstreamRoute.HttpHandlerOptions.UseProxy, + MaxConnectionsPerServer = downstreamRoute.HttpHandlerOptions.MaxConnectionsPerServer, + CookieContainer = new CookieContainer(), + }; + } + + public void Save() + { + _cacheHandlers.Set(_cacheKey, _client, TimeSpan.FromHours(24)); + } + + private HttpMessageHandler CreateHttpMessageHandler(HttpMessageHandler httpMessageHandler, DownstreamRoute request) + { + //todo handle error + var handlers = _factory.Get(request).Data; + + handlers + .Select(handler => handler) + .Reverse() + .ToList() + .ForEach(handler => + { + var delegatingHandler = handler(); + delegatingHandler.InnerHandler = httpMessageHandler; + httpMessageHandler = delegatingHandler; + }); + return httpMessageHandler; + } + } +} diff --git a/src/Ocelot/Requester/HttpClientWrapper.cs b/src/Ocelot/Requester/HttpClientWrapper.cs new file mode 100644 index 000000000..33e8df80a --- /dev/null +++ b/src/Ocelot/Requester/HttpClientWrapper.cs @@ -0,0 +1,28 @@ +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Ocelot.Requester +{ + /// + /// This class was made to make unit testing easier when HttpClient is used. + /// + public class HttpClientWrapper : IHttpClient + { + public HttpClient Client { get; } + + public bool ConnectionClose { get; } + + public HttpClientWrapper(HttpClient client, bool connectionClose = false) + { + Client = client; + ConnectionClose = connectionClose; + } + + public Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default) + { + request.Headers.ConnectionClose = ConnectionClose; + return Client.SendAsync(request, cancellationToken); + } + } +} From 85f8020b655a4cef977f07743f2d555332c9d07e Mon Sep 17 00:00:00 2001 From: raman-m Date: Wed, 23 Aug 2023 20:37:13 +0300 Subject: [PATCH 2/4] Add missing code after rebasing and file renaming --- .../Configuration/Builder/DownstreamRouteBuilder.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs b/src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs index 2f01cd429..eb927b1b2 100644 --- a/src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs @@ -43,6 +43,7 @@ public class DownstreamRouteBuilder private HttpVersionPolicy _downstreamHttpVersionPolicy; private Dictionary _upstreamHeaders; private MetadataOptions _metadataOptions; + private bool _connectionClose; public DownstreamRouteBuilder() { @@ -282,6 +283,12 @@ public DownstreamRouteBuilder WithMetadata(MetadataOptions metadataOptions) return this; } + public DownstreamRouteBuilder WithConnectionClose(bool connectionClose) + { + _connectionClose = connectionClose; + return this; + } + public DownstreamRoute Build() { return new DownstreamRoute( @@ -321,6 +328,7 @@ public DownstreamRoute Build() _downstreamHttpVersion, _downstreamHttpVersionPolicy, _upstreamHeaders, - _metadataOptions); + _metadataOptions, + _connectionClose); } } From 04fc6b135068f9c161dee839f9585ae380323c3f Mon Sep 17 00:00:00 2001 From: raman-m Date: Wed, 23 Aug 2023 20:41:10 +0300 Subject: [PATCH 3/4] Fix build error --- .../Configuration/RoutesCreatorTests.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs index dbb9ef25a..d07940763 100644 --- a/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs @@ -27,6 +27,7 @@ public class RoutesCreatorTests : UnitTest private readonly Mock _versionCreator; private readonly Mock _versionPolicyCreator; private readonly Mock _metadataCreator; + private readonly Mock _connectionCloseCreator; private FileConfiguration _fileConfig; private RouteOptions _rro; private string _requestId; @@ -42,7 +43,7 @@ public class RoutesCreatorTests : UnitTest private List _dhp; private LoadBalancerOptions _lbo; private List _result; - private Version _expectedVersion; + private Version _expectedVersion; private HttpVersionPolicy _expectedVersionPolicy; private Dictionary _uht; private Dictionary _expectedMetadata; @@ -67,6 +68,7 @@ public RoutesCreatorTests() _versionPolicyCreator = new Mock(); _uhtpCreator = new Mock(); _metadataCreator = new Mock(); + _connectionCloseCreator = new Mock(); _creator = new RoutesCreator( _cthCreator.Object, @@ -86,7 +88,8 @@ public RoutesCreatorTests() _versionCreator.Object, _versionPolicyCreator.Object, _uhtpCreator.Object, - _metadataCreator.Object); + _metadataCreator.Object, + _connectionCloseCreator.Object); } [Fact] @@ -123,7 +126,7 @@ public void Should_return_routes() { { "e","f" }, }, - UpstreamHttpMethod = new List { "GET", "POST" }, + UpstreamHttpMethod = new List { "GET", "POST" }, Metadata = new Dictionary { ["foo"] = "bar", @@ -303,7 +306,7 @@ private void ThenTheDepsAreCalledFor(FileRoute fileRoute, FileGlobalConfiguratio _hfarCreator.Verify(x => x.Create(fileRoute), Times.Once); _daCreator.Verify(x => x.Create(fileRoute), Times.Once); _lboCreator.Verify(x => x.Create(fileRoute.LoadBalancerOptions), Times.Once); - _soCreator.Verify(x => x.Create(fileRoute.SecurityOptions), Times.Once); + _soCreator.Verify(x => x.Create(fileRoute.SecurityOptions), Times.Once); _metadataCreator.Verify(x => x.Create(fileRoute.Metadata, globalConfig), Times.Once); } } From b0879eaa82d223feb8b8f578e7b95fdacc6185ed Mon Sep 17 00:00:00 2001 From: raman-m Date: Wed, 23 Aug 2023 20:43:09 +0300 Subject: [PATCH 4/4] Keep old changes --- src/Ocelot/Requester/HttpClientBuilder.cs | 9 ++++++++- src/Ocelot/Requester/HttpClientWrapper.cs | 8 +++++--- .../Configuration/DownstreamRouteExtensionsTests.cs | 3 ++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index bbb93d2cc..2b0cff47a 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -9,6 +9,13 @@ namespace Ocelot.Requester { + public interface IHttpClientBuilder { } + public interface IHttpClientCache + { + IHttpClient Get(DownstreamRoute cacheKey); + void Set(DownstreamRoute cacheKey, IHttpClient client, TimeSpan span); + } + public class HttpClientBuilder : IHttpClientBuilder { private readonly IDelegatingHandlerHandlerFactory _factory; @@ -65,7 +72,7 @@ public IHttpClient Create(DownstreamRoute downstreamRoute) Timeout = timeout, }; - _client = new HttpClientWrapper(_httpClient, downstreamRoute.ConnectionClose); + _client = new HttpClientWrapper(_httpClient, downstreamRoute.ConnectionClose); // TODO return _client; } diff --git a/src/Ocelot/Requester/HttpClientWrapper.cs b/src/Ocelot/Requester/HttpClientWrapper.cs index 33e8df80a..8c9ac82d5 100644 --- a/src/Ocelot/Requester/HttpClientWrapper.cs +++ b/src/Ocelot/Requester/HttpClientWrapper.cs @@ -4,6 +4,8 @@ namespace Ocelot.Requester { + public interface IHttpClient { } + /// /// This class was made to make unit testing easier when HttpClient is used. /// @@ -11,9 +13,9 @@ public class HttpClientWrapper : IHttpClient { public HttpClient Client { get; } - public bool ConnectionClose { get; } + public bool ConnectionClose { get; } // TODO - public HttpClientWrapper(HttpClient client, bool connectionClose = false) + public HttpClientWrapper(HttpClient client, bool connectionClose = false) // TODO { Client = client; ConnectionClose = connectionClose; @@ -21,7 +23,7 @@ public HttpClientWrapper(HttpClient client, bool connectionClose = false) public Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default) { - request.Headers.ConnectionClose = ConnectionClose; + request.Headers.ConnectionClose = ConnectionClose; // TODO return Client.SendAsync(request, cancellationToken); } } diff --git a/test/Ocelot.UnitTests/Configuration/DownstreamRouteExtensionsTests.cs b/test/Ocelot.UnitTests/Configuration/DownstreamRouteExtensionsTests.cs index 0c3b3bcc3..09ccb707c 100644 --- a/test/Ocelot.UnitTests/Configuration/DownstreamRouteExtensionsTests.cs +++ b/test/Ocelot.UnitTests/Configuration/DownstreamRouteExtensionsTests.cs @@ -51,7 +51,8 @@ public DownstreamRouteExtensionsTests() new Version(), HttpVersionPolicy.RequestVersionExact, new(), - new MetadataOptions(new FileMetadataOptions())); + new MetadataOptions(new FileMetadataOptions()), + false); } [Theory]