-
Notifications
You must be signed in to change notification settings - Fork 843
/
ForwarderHttpClientFactory.cs
165 lines (138 loc) · 5.96 KB
/
ForwarderHttpClientFactory.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Diagnostics;
using System.Net;
using System.Net.Http;
using System.Text;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Yarp.ReverseProxy.Configuration;
namespace Yarp.ReverseProxy.Forwarder;
/// <summary>
/// Default implementation of <see cref="IForwarderHttpClientFactory"/>.
/// </summary>
public class ForwarderHttpClientFactory : IForwarderHttpClientFactory
{
private readonly ILogger<ForwarderHttpClientFactory> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="ForwarderHttpClientFactory"/> class.
/// </summary>
public ForwarderHttpClientFactory() : this(NullLogger<ForwarderHttpClientFactory>.Instance) { }
/// <summary>
/// Initializes a new instance of the <see cref="ForwarderHttpClientFactory"/> class.
/// </summary>
public ForwarderHttpClientFactory(ILogger<ForwarderHttpClientFactory> logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <inheritdoc/>
public HttpMessageInvoker CreateClient(ForwarderHttpClientContext context)
{
if (CanReuseOldClient(context))
{
Log.ClientReused(_logger, context.ClusterId);
return context.OldClient!;
}
var handler = new SocketsHttpHandler
{
UseProxy = false,
AllowAutoRedirect = false,
AutomaticDecompression = DecompressionMethods.None,
UseCookies = false,
EnableMultipleHttp2Connections = true,
ActivityHeadersPropagator = new ReverseProxyPropagator(DistributedContextPropagator.Current),
ConnectTimeout = TimeSpan.FromSeconds(15),
// NOTE: MaxResponseHeadersLength = 64, which means up to 64 KB of headers are allowed by default as of .NET Core 3.1.
};
ConfigureHandler(context, handler);
var middleware = WrapHandler(context, handler);
Log.ClientCreated(_logger, context.ClusterId);
return new HttpMessageInvoker(middleware, disposeHandler: true);
}
/// <summary>
/// Checks if the options have changed since the old client was created. If not then the
/// old client will be re-used. Re-use can avoid the latency of creating new connections.
/// </summary>
protected virtual bool CanReuseOldClient(ForwarderHttpClientContext context)
{
return context.OldClient is not null && context.NewConfig == context.OldConfig;
}
/// <summary>
/// Allows configuring the <see cref="SocketsHttpHandler"/> instance. The base implementation
/// applies settings from <see cref="ForwarderHttpClientContext.NewConfig"/>.
/// <see cref="SocketsHttpHandler.UseProxy"/>, <see cref="SocketsHttpHandler.AllowAutoRedirect"/>,
/// <see cref="SocketsHttpHandler.AutomaticDecompression"/>, and <see cref="SocketsHttpHandler.UseCookies"/>
/// are disabled prior to this call.
/// </summary>
protected virtual void ConfigureHandler(ForwarderHttpClientContext context, SocketsHttpHandler handler)
{
var newConfig = context.NewConfig;
if (newConfig.SslProtocols.HasValue)
{
handler.SslOptions.EnabledSslProtocols = newConfig.SslProtocols.Value;
}
if (newConfig.MaxConnectionsPerServer is not null)
{
handler.MaxConnectionsPerServer = newConfig.MaxConnectionsPerServer.Value;
}
if (newConfig.DangerousAcceptAnyServerCertificate ?? false)
{
handler.SslOptions.RemoteCertificateValidationCallback = delegate { return true; };
}
handler.EnableMultipleHttp2Connections = newConfig.EnableMultipleHttp2Connections.GetValueOrDefault(true);
if (newConfig.RequestHeaderEncoding is not null)
{
var encoding = Encoding.GetEncoding(newConfig.RequestHeaderEncoding);
handler.RequestHeaderEncodingSelector = (_, _) => encoding;
}
if (newConfig.ResponseHeaderEncoding is not null)
{
var encoding = Encoding.GetEncoding(newConfig.ResponseHeaderEncoding);
handler.ResponseHeaderEncodingSelector = (_, _) => encoding;
}
var webProxy = TryCreateWebProxy(newConfig.WebProxy);
if (webProxy is not null)
{
handler.Proxy = webProxy;
handler.UseProxy = true;
}
}
private static IWebProxy? TryCreateWebProxy(WebProxyConfig? webProxyConfig)
{
if (webProxyConfig is null || webProxyConfig.Address is null)
{
return null;
}
var webProxy = new WebProxy(webProxyConfig.Address);
webProxy.UseDefaultCredentials = webProxyConfig.UseDefaultCredentials.GetValueOrDefault(false);
webProxy.BypassProxyOnLocal = webProxyConfig.BypassOnLocal.GetValueOrDefault(false);
return webProxy;
}
/// <summary>
/// Adds any wrapping middleware around the <see cref="HttpMessageHandler"/>.
/// </summary>
protected virtual HttpMessageHandler WrapHandler(ForwarderHttpClientContext context, HttpMessageHandler handler)
{
return handler;
}
private static class Log
{
private static readonly Action<ILogger, string, Exception?> _clientCreated = LoggerMessage.Define<string>(
LogLevel.Debug,
EventIds.ClientCreated,
"New client created for cluster '{clusterId}'.");
private static readonly Action<ILogger, string, Exception?> _clientReused = LoggerMessage.Define<string>(
LogLevel.Debug,
EventIds.ClientReused,
"Existing client reused for cluster '{clusterId}'.");
public static void ClientCreated(ILogger logger, string clusterId)
{
_clientCreated(logger, clusterId, null);
}
public static void ClientReused(ILogger logger, string clusterId)
{
_clientReused(logger, clusterId, null);
}
}
}