Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Opt-in to set Connection:Close on downstream requests #1441

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public class DownstreamRouteBuilder
private HttpVersionPolicy _downstreamHttpVersionPolicy;
private Dictionary<string, UpstreamHeaderTemplate> _upstreamHeaders;
private MetadataOptions _metadataOptions;
private bool _connectionClose;

public DownstreamRouteBuilder()
{
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -321,6 +328,7 @@ public DownstreamRoute Build()
_downstreamHttpVersion,
_downstreamHttpVersionPolicy,
_upstreamHeaders,
_metadataOptions);
_metadataOptions,
_connectionClose);
}
}
14 changes: 14 additions & 0 deletions src/Ocelot/Configuration/Creator/ConnectionCloseCreator.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
9 changes: 9 additions & 0 deletions src/Ocelot/Configuration/Creator/IConnectionCloseCreator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Ocelot.Configuration.File;

namespace Ocelot.Configuration.Creator
{
public interface IConnectionCloseCreator
{
bool Create(bool fileRouteConnectionClose, FileGlobalConfiguration globalConfiguration);
}
}
8 changes: 7 additions & 1 deletion src/Ocelot/Configuration/Creator/RoutesCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -42,7 +43,8 @@ public RoutesCreator(
IVersionCreator versionCreator,
IVersionPolicyCreator versionPolicyCreator,
IUpstreamHeaderTemplatePatternCreator upstreamHeaderTemplatePatternCreator,
IMetadataCreator metadataCreator)
IMetadataCreator metadataCreator,
IConnectionCloseCreator connectionCloseCreator)
{
_routeKeyCreator = routeKeyCreator;
_loadBalancerOptionsCreator = loadBalancerOptionsCreator;
Expand All @@ -63,6 +65,7 @@ public RoutesCreator(
_versionPolicyCreator = versionPolicyCreator;
_upstreamHeaderTemplatePatternCreator = upstreamHeaderTemplatePatternCreator;
_metadataCreator = metadataCreator;
_connectionCloseCreator = connectionCloseCreator;
}

public List<Route> Create(FileConfiguration fileConfiguration)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -156,6 +161,7 @@ private DownstreamRoute SetUpDownstreamRoute(FileRoute fileRoute, FileGlobalConf
.WithDownstreamHttpVersionPolicy(downstreamHttpVersionPolicy)
.WithDownStreamHttpMethod(fileRoute.DownstreamHttpMethod)
.WithMetadata(metadata)
.WithConnectionClose(connectionClose)
.Build();

return route;
Expand Down
6 changes: 5 additions & 1 deletion src/Ocelot/Configuration/DownstreamRoute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ public DownstreamRoute(
Version downstreamHttpVersion,
HttpVersionPolicy downstreamHttpVersionPolicy,
Dictionary<string, UpstreamHeaderTemplate> upstreamHeaders,
MetadataOptions metadataOptions)
MetadataOptions metadataOptions,
bool connectionClose)
{
DangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator;
AddHeadersToDownstream = addHeadersToDownstream;
Expand Down Expand Up @@ -81,6 +82,7 @@ public DownstreamRoute(
DownstreamHttpVersionPolicy = downstreamHttpVersionPolicy;
UpstreamHeaders = upstreamHeaders ?? new();
MetadataOptions = metadataOptions;
ConnectionClose = connectionClose;
}

public string Key { get; }
Expand Down Expand Up @@ -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; }
}
}
3 changes: 3 additions & 0 deletions src/Ocelot/Configuration/File/FileGlobalConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public FileGlobalConfiguration()
HttpHandlerOptions = new FileHttpHandlerOptions();
CacheOptions = new FileCacheOptions();
MetadataOptions = new FileMetadataOptions();
ConnectionClose = false;
}

public string RequestIdKey { get; set; }
Expand Down Expand Up @@ -48,5 +49,7 @@ public FileGlobalConfiguration()
public FileCacheOptions CacheOptions { get; set; }

public FileMetadataOptions MetadataOptions { get; set; }

public bool ConnectionClose { get; set; }
}
}
7 changes: 5 additions & 2 deletions src/Ocelot/Configuration/File/FileRoute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public FileRoute()
UpstreamHeaderTemplates = new Dictionary<string, string>();
UpstreamHeaderTransform = new Dictionary<string, string>();
UpstreamHttpMethod = new List<string>();
ConnectionClose = false;
}

public FileRoute(FileRoute from)
Expand All @@ -38,6 +39,7 @@ public FileRoute(FileRoute from)
public Dictionary<string, string> AddQueriesToRequest { get; set; }
public FileAuthenticationOptions AuthenticationOptions { get; set; }
public Dictionary<string, string> ChangeDownstreamPathTemplate { get; set; }
public bool ConnectionClose { get; set; }
public bool DangerousAcceptAnyServerCertificateValidator { get; set; }
public List<string> DelegatingHandlers { get; set; }
public Dictionary<string, string> DownstreamHeaderTransform { get; set; }
Expand All @@ -57,7 +59,7 @@ public FileRoute(FileRoute from)
/// </remarks>
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; }
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/Ocelot/DependencyInjection/OcelotBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public OcelotBuilder(IServiceCollection services, IConfiguration configurationRo
Services.TryAddSingleton<IAuthenticationOptionsCreator, AuthenticationOptionsCreator>();
Services.TryAddSingleton<IUpstreamTemplatePatternCreator, UpstreamTemplatePatternCreator>();
Services.TryAddSingleton<IRequestIdKeyCreator, RequestIdKeyCreator>();
Services.TryAddSingleton<IConnectionCloseCreator, ConnectionCloseCreator>();
Services.TryAddSingleton<IServiceProviderConfigurationCreator, ServiceProviderConfigurationCreator>();
Services.TryAddSingleton<IQoSOptionsCreator, QoSOptionsCreator>();
Services.TryAddSingleton<IRouteOptionsCreator, RouteOptionsCreator>();
Expand Down
136 changes: 136 additions & 0 deletions src/Ocelot/Requester/HttpClientBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
using System;
using System.Linq;
using System.Net;
using System.Net.Http;

using Ocelot.Configuration;

using Ocelot.Logging;

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;
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); // TODO

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;
}
Comment on lines +123 to +134
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
handlers
.Select(handler => handler)
.Reverse()
.ToList()
.ForEach(handler =>
{
var delegatingHandler = handler();
delegatingHandler.InnerHandler = httpMessageHandler;
httpMessageHandler = delegatingHandler;
});
return httpMessageHandler;
}
return handlers
.Reverse() // Reverse the sequence
.Aggregate(httpMessageHandler, (currentHandler, handlerFunc) =>
{
var delegatingHandler = handlerFunc();
delegatingHandler.InnerHandler = currentHandler;
return delegatingHandler;
});
return httpMessageHandler;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeap! ToList was a bit overhead to get access to ForEach helper.
Aggregate helper is good, for sure because it is applicable for this case.
There is another coding life hack: calling All to process all elements of collection. 😃

But!... Ray, this file is redundant one! I left this file during rebasing just to keep the author's changes: see // TODO 😉
Both files are just container of new changes

  • src/Ocelot/Requester/HttpClientBuilder.cs
  • src/Ocelot/Requester/HttpClientWrapper.cs

You may check, there are no these files in develop! 🙃
This PR is not ready for reviews: it must be developed more ❗

}
}
30 changes: 30 additions & 0 deletions src/Ocelot/Requester/HttpClientWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace Ocelot.Requester
{
public interface IHttpClient { }

/// <summary>
/// This class was made to make unit testing easier when HttpClient is used.
/// </summary>
public class HttpClientWrapper : IHttpClient
{
public HttpClient Client { get; }

public bool ConnectionClose { get; } // TODO

public HttpClientWrapper(HttpClient client, bool connectionClose = false) // TODO
{
Client = client;
ConnectionClose = connectionClose;
}

public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default)
{
request.Headers.ConnectionClose = ConnectionClose; // TODO
return Client.SendAsync(request, cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ public DownstreamRouteExtensionsTests()
new Version(),
HttpVersionPolicy.RequestVersionExact,
new(),
new MetadataOptions(new FileMetadataOptions()));
new MetadataOptions(new FileMetadataOptions()),
false);
}

[Theory]
Expand Down
Loading