Skip to content

Commit a6f361c

Browse files
Bjarte K.  Hellandraman-m
authored andcommitted
Add possibility to set Connection to Close ( request.Headers.ConnectionClose = ConnectionClose ) in order to prevent persistent connections.
1 parent 59b63ea commit a6f361c

File tree

9 files changed

+211
-15
lines changed

9 files changed

+211
-15
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Ocelot.Configuration.File;
2+
3+
namespace Ocelot.Configuration.Creator
4+
{
5+
public class ConnectionCloseCreator : IConnectionCloseCreator
6+
{
7+
public bool Create(bool fileRouteConnectionClose, FileGlobalConfiguration globalConfiguration)
8+
{
9+
var globalConnectionClose = globalConfiguration.ConnectionClose;
10+
11+
return fileRouteConnectionClose || globalConnectionClose;
12+
}
13+
}
14+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Ocelot.Configuration.File;
2+
3+
namespace Ocelot.Configuration.Creator
4+
{
5+
public interface IConnectionCloseCreator
6+
{
7+
bool Create(bool fileRouteConnectionClose, FileGlobalConfiguration globalConfiguration);
8+
}
9+
}

src/Ocelot/Configuration/Creator/RoutesCreator.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public class RoutesCreator : IRoutesCreator
2121
private readonly IRouteKeyCreator _routeKeyCreator;
2222
private readonly ISecurityOptionsCreator _securityOptionsCreator;
2323
private readonly IVersionCreator _versionCreator;
24+
private readonly IConnectionCloseCreator _connectionCloseCreator;
2425

2526
public RoutesCreator(
2627
IClaimsToThingCreator claimsToThingCreator,
@@ -37,7 +38,8 @@ public RoutesCreator(
3738
ILoadBalancerOptionsCreator loadBalancerOptionsCreator,
3839
IRouteKeyCreator routeKeyCreator,
3940
ISecurityOptionsCreator securityOptionsCreator,
40-
IVersionCreator versionCreator
41+
IVersionCreator versionCreator,
42+
IConnectionCloseCreator connectionCloseCreator
4143
)
4244
{
4345
_routeKeyCreator = routeKeyCreator;
@@ -56,6 +58,7 @@ IVersionCreator versionCreator
5658
_loadBalancerOptionsCreator = loadBalancerOptionsCreator;
5759
_securityOptionsCreator = securityOptionsCreator;
5860
_versionCreator = versionCreator;
61+
_connectionCloseCreator = connectionCloseCreator;
5962
}
6063

6164
public List<Route> Create(FileConfiguration fileConfiguration)
@@ -107,6 +110,8 @@ private DownstreamRoute SetUpDownstreamRoute(FileRoute fileRoute, FileGlobalConf
107110

108111
var downstreamHttpVersion = _versionCreator.Create(fileRoute.DownstreamHttpVersion);
109112

113+
var connectionClose = _connectionCloseCreator.Create(fileRoute.ConnectionClose, globalConfiguration);
114+
110115
var route = new DownstreamRouteBuilder()
111116
.WithKey(fileRoute.Key)
112117
.WithDownstreamPathTemplate(fileRoute.DownstreamPathTemplate)
@@ -143,6 +148,7 @@ private DownstreamRoute SetUpDownstreamRoute(FileRoute fileRoute, FileGlobalConf
143148
.WithSecurityOptions(securityOptions)
144149
.WithDownstreamHttpVersion(downstreamHttpVersion)
145150
.WithDownStreamHttpMethod(fileRoute.DownstreamHttpMethod)
151+
.WithConnectionClose(connectionClose)
146152
.Build();
147153

148154
return route;

src/Ocelot/Configuration/DownstreamRoute.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ public DownstreamRoute(
3939
bool dangerousAcceptAnyServerCertificateValidator,
4040
SecurityOptions securityOptions,
4141
string downstreamHttpMethod,
42-
Version downstreamHttpVersion)
42+
Version downstreamHttpVersion,
43+
bool connectionClose)
4344
{
4445
DangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator;
4546
AddHeadersToDownstream = addHeadersToDownstream;
@@ -75,6 +76,7 @@ public DownstreamRoute(
7576
SecurityOptions = securityOptions;
7677
DownstreamHttpMethod = downstreamHttpMethod;
7778
DownstreamHttpVersion = downstreamHttpVersion;
79+
ConnectionClose = connectionClose;
7880
}
7981

8082
public string Key { get; }
@@ -110,6 +112,7 @@ public DownstreamRoute(
110112
public bool DangerousAcceptAnyServerCertificateValidator { get; }
111113
public SecurityOptions SecurityOptions { get; }
112114
public string DownstreamHttpMethod { get; }
113-
public Version DownstreamHttpVersion { get; }
115+
public Version DownstreamHttpVersion { get; }
116+
public bool ConnectionClose { get; }
114117
}
115118
}

src/Ocelot/Configuration/File/FileGlobalConfiguration.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ public FileGlobalConfiguration()
99
LoadBalancerOptions = new FileLoadBalancerOptions();
1010
QoSOptions = new FileQoSOptions();
1111
HttpHandlerOptions = new FileHttpHandlerOptions();
12+
ConnectionClose = false;
1213
}
1314

1415
public string RequestIdKey { get; set; }
@@ -28,5 +29,7 @@ public FileGlobalConfiguration()
2829
public FileHttpHandlerOptions HttpHandlerOptions { get; set; }
2930

3031
public string DownstreamHttpVersion { get; set; }
32+
33+
public bool ConnectionClose { get; set; }
3134
}
3235
}

src/Ocelot/Configuration/File/FileRoute.cs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,19 @@ public FileRoute()
2222
SecurityOptions = new FileSecurityOptions();
2323
UpstreamHeaderTransform = new Dictionary<string, string>();
2424
UpstreamHttpMethod = new List<string>();
25-
}
26-
25+
ConnectionClose = false;
26+
}
27+
2728
public FileRoute(FileRoute from)
2829
{
2930
DeepCopy(from, this);
3031
}
3132

32-
public Dictionary<string, string> AddClaimsToRequest { get; set; }
33+
public Dictionary<string, string> AddClaimsToRequest { get; set; }
3334
public Dictionary<string, string> AddHeadersToRequest { get; set; }
34-
public Dictionary<string, string> AddQueriesToRequest { get; set; }
35+
public Dictionary<string, string> AddQueriesToRequest { get; set; }
3536
public FileAuthenticationOptions AuthenticationOptions { get; set; }
36-
public Dictionary<string, string> ChangeDownstreamPathTemplate { get; set; }
37+
public Dictionary<string, string> ChangeDownstreamPathTemplate { get; set; }
3738
public bool DangerousAcceptAnyServerCertificateValidator { get; set; }
3839
public List<string> DelegatingHandlers { get; set; }
3940
public Dictionary<string, string> DownstreamHeaderTransform { get; set; }
@@ -42,7 +43,7 @@ public FileRoute(FileRoute from)
4243
public string DownstreamHttpVersion { get; set; }
4344
public string DownstreamPathTemplate { get; set; }
4445
public string DownstreamScheme { get; set; }
45-
public FileCacheOptions FileCacheOptions { get; set; }
46+
public FileCacheOptions FileCacheOptions { get; set; }
4647
public FileHttpHandlerOptions HttpHandlerOptions { get; set; }
4748
public string Key { get; set; }
4849
public FileLoadBalancerOptions LoadBalancerOptions { get; set; }
@@ -51,15 +52,16 @@ public FileRoute(FileRoute from)
5152
public FileRateLimitRule RateLimitOptions { get; set; }
5253
public string RequestIdKey { get; set; }
5354
public Dictionary<string, string> RouteClaimsRequirement { get; set; }
54-
public bool RouteIsCaseSensitive { get; set; }
55+
public bool RouteIsCaseSensitive { get; set; }
5556
public FileSecurityOptions SecurityOptions { get; set; }
56-
public string ServiceName { get; set; }
57-
public string ServiceNamespace { get; set; }
57+
public string ServiceName { get; set; }
58+
public string ServiceNamespace { get; set; }
5859
public int Timeout { get; set; }
5960
public Dictionary<string, string> UpstreamHeaderTransform { get; set; }
60-
public string UpstreamHost { get; set; }
61+
public string UpstreamHost { get; set; }
6162
public List<string> UpstreamHttpMethod { get; set; }
62-
public string UpstreamPathTemplate { get; set; }
63+
public string UpstreamPathTemplate { get; set; }
64+
public bool ConnectionClose { get; set; }
6365

6466
/// <summary>
6567
/// Clones this object by making a deep copy.
@@ -104,7 +106,8 @@ public static void DeepCopy(FileRoute from, FileRoute to)
104106
to.UpstreamHeaderTransform = new(from.UpstreamHeaderTransform);
105107
to.UpstreamHost = from.UpstreamHost;
106108
to.UpstreamHttpMethod = new(from.UpstreamHttpMethod);
107-
to.UpstreamPathTemplate = from.UpstreamPathTemplate;
109+
to.UpstreamPathTemplate = from.UpstreamPathTemplate;
110+
to.ConnectionClose = from.ConnectionClose;
108111
}
109112
}
110113
}

src/Ocelot/DependencyInjection/OcelotBuilder.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public OcelotBuilder(IServiceCollection services, IConfiguration configurationRo
7676
Services.TryAddSingleton<IAuthenticationOptionsCreator, AuthenticationOptionsCreator>();
7777
Services.TryAddSingleton<IUpstreamTemplatePatternCreator, UpstreamTemplatePatternCreator>();
7878
Services.TryAddSingleton<IRequestIdKeyCreator, RequestIdKeyCreator>();
79+
Services.TryAddSingleton<IConnectionCloseCreator, ConnectionCloseCreator>();
7980
Services.TryAddSingleton<IServiceProviderConfigurationCreator, ServiceProviderConfigurationCreator>();
8081
Services.TryAddSingleton<IQoSOptionsCreator, QoSOptionsCreator>();
8182
Services.TryAddSingleton<IRouteOptionsCreator, RouteOptionsCreator>();
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
using System;
2+
using System.Linq;
3+
using System.Net;
4+
using System.Net.Http;
5+
6+
using Ocelot.Configuration;
7+
8+
using Ocelot.Logging;
9+
10+
namespace Ocelot.Requester
11+
{
12+
public class HttpClientBuilder : IHttpClientBuilder
13+
{
14+
private readonly IDelegatingHandlerHandlerFactory _factory;
15+
private readonly IHttpClientCache _cacheHandlers;
16+
private readonly IOcelotLogger _logger;
17+
private DownstreamRoute _cacheKey;
18+
private HttpClient _httpClient;
19+
private IHttpClient _client;
20+
private readonly TimeSpan _defaultTimeout;
21+
22+
public HttpClientBuilder(
23+
IDelegatingHandlerHandlerFactory factory,
24+
IHttpClientCache cacheHandlers,
25+
IOcelotLogger logger)
26+
{
27+
_factory = factory;
28+
_cacheHandlers = cacheHandlers;
29+
_logger = logger;
30+
31+
// This is hardcoded at the moment but can easily be added to configuration
32+
// if required by a user request.
33+
_defaultTimeout = TimeSpan.FromSeconds(90);
34+
}
35+
36+
public IHttpClient Create(DownstreamRoute downstreamRoute)
37+
{
38+
_cacheKey = downstreamRoute;
39+
40+
var httpClient = _cacheHandlers.Get(_cacheKey);
41+
42+
if (httpClient != null)
43+
{
44+
_client = httpClient;
45+
return httpClient;
46+
}
47+
48+
var handler = CreateHandler(downstreamRoute);
49+
50+
if (downstreamRoute.DangerousAcceptAnyServerCertificateValidator)
51+
{
52+
handler.ServerCertificateCustomValidationCallback =
53+
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
54+
55+
_logger
56+
.LogWarning($"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamRoute, UpstreamPathTemplate: {downstreamRoute.UpstreamPathTemplate}, DownstreamPathTemplate: {downstreamRoute.DownstreamPathTemplate}");
57+
}
58+
59+
var timeout = downstreamRoute.QosOptions.TimeoutValue == 0
60+
? _defaultTimeout
61+
: TimeSpan.FromMilliseconds(downstreamRoute.QosOptions.TimeoutValue);
62+
63+
_httpClient = new HttpClient(CreateHttpMessageHandler(handler, downstreamRoute))
64+
{
65+
Timeout = timeout,
66+
};
67+
68+
_client = new HttpClientWrapper(_httpClient, downstreamRoute.ConnectionClose);
69+
70+
return _client;
71+
}
72+
73+
private static HttpClientHandler CreateHandler(DownstreamRoute downstreamRoute)
74+
{
75+
// Dont' create the CookieContainer if UseCookies is not set or the HttpClient will complain
76+
// under .Net Full Framework
77+
var useCookies = downstreamRoute.HttpHandlerOptions.UseCookieContainer;
78+
79+
return useCookies ? UseCookiesHandler(downstreamRoute) : UseNonCookiesHandler(downstreamRoute);
80+
}
81+
82+
private static HttpClientHandler UseNonCookiesHandler(DownstreamRoute downstreamRoute)
83+
{
84+
return new HttpClientHandler
85+
{
86+
AllowAutoRedirect = downstreamRoute.HttpHandlerOptions.AllowAutoRedirect,
87+
UseCookies = downstreamRoute.HttpHandlerOptions.UseCookieContainer,
88+
UseProxy = downstreamRoute.HttpHandlerOptions.UseProxy,
89+
MaxConnectionsPerServer = downstreamRoute.HttpHandlerOptions.MaxConnectionsPerServer,
90+
91+
};
92+
}
93+
94+
private static HttpClientHandler UseCookiesHandler(DownstreamRoute downstreamRoute)
95+
{
96+
return new HttpClientHandler
97+
{
98+
AllowAutoRedirect = downstreamRoute.HttpHandlerOptions.AllowAutoRedirect,
99+
UseCookies = downstreamRoute.HttpHandlerOptions.UseCookieContainer,
100+
UseProxy = downstreamRoute.HttpHandlerOptions.UseProxy,
101+
MaxConnectionsPerServer = downstreamRoute.HttpHandlerOptions.MaxConnectionsPerServer,
102+
CookieContainer = new CookieContainer(),
103+
};
104+
}
105+
106+
public void Save()
107+
{
108+
_cacheHandlers.Set(_cacheKey, _client, TimeSpan.FromHours(24));
109+
}
110+
111+
private HttpMessageHandler CreateHttpMessageHandler(HttpMessageHandler httpMessageHandler, DownstreamRoute request)
112+
{
113+
//todo handle error
114+
var handlers = _factory.Get(request).Data;
115+
116+
handlers
117+
.Select(handler => handler)
118+
.Reverse()
119+
.ToList()
120+
.ForEach(handler =>
121+
{
122+
var delegatingHandler = handler();
123+
delegatingHandler.InnerHandler = httpMessageHandler;
124+
httpMessageHandler = delegatingHandler;
125+
});
126+
return httpMessageHandler;
127+
}
128+
}
129+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System.Net.Http;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
5+
namespace Ocelot.Requester
6+
{
7+
/// <summary>
8+
/// This class was made to make unit testing easier when HttpClient is used.
9+
/// </summary>
10+
public class HttpClientWrapper : IHttpClient
11+
{
12+
public HttpClient Client { get; }
13+
14+
public bool ConnectionClose { get; }
15+
16+
public HttpClientWrapper(HttpClient client, bool connectionClose = false)
17+
{
18+
Client = client;
19+
ConnectionClose = connectionClose;
20+
}
21+
22+
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default)
23+
{
24+
request.Headers.ConnectionClose = ConnectionClose;
25+
return Client.SendAsync(request, cancellationToken);
26+
}
27+
}
28+
}

0 commit comments

Comments
 (0)