-
Notifications
You must be signed in to change notification settings - Fork 404
Expand file tree
/
Copy pathSimpleHttpClientFactory.cs
More file actions
143 lines (122 loc) · 5.01 KB
/
SimpleHttpClientFactory.cs
File metadata and controls
143 lines (122 loc) · 5.01 KB
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
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Concurrent;
using System.Net.Http;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using Microsoft.Identity.Client.Http;
using Microsoft.Identity.Client.ManagedIdentity;
namespace Microsoft.Identity.Client.PlatformsCommon.Shared
{
/// <summary>
/// A simple implementation of the HttpClient factory that uses a managed HttpClientHandler
/// </summary>
/// <remarks>
/// .NET should use the IHttpClientFactory, but MSAL cannot take a dependency on it.
/// .NET should use SocketHandler, but UseDefaultCredentials doesn't work with it
/// </remarks>
internal class SimpleHttpClientFactory : IMsalMtlsHttpClientFactory, IMsalSFHttpClientFactory
{
//Please see (https://aka.ms/msal-httpclient-info) for important information regarding the HttpClient.
private static readonly ConcurrentDictionary<string, HttpClient> s_httpClientPool = new ConcurrentDictionary<string, HttpClient>();
private static readonly object s_cacheLock = new object();
private static int s_httpClientCreationCount;
// referenced in unit tests
internal static int HttpClientCreationCount => s_httpClientCreationCount;
private static HttpClient CreateHttpClient()
{
Interlocked.Increment(ref s_httpClientCreationCount);
CheckAndManageCache();
var httpClient = new HttpClient(new HttpClientHandler()
{
/* important for IWA */
UseDefaultCredentials = true
});
HttpClientConfig.ConfigureRequestHeadersAndSize(httpClient);
return httpClient;
}
private static HttpClient CreateMtlsHttpClient(X509Certificate2 bindingCertificate)
{
#if SUPPORTS_MTLS
CheckAndManageCache();
if (bindingCertificate == null)
{
throw new ArgumentNullException(nameof(bindingCertificate), "A valid X509 certificate must be provided for mTLS.");
}
//Create an HttpClientHandler and configure it to use the client certificate
HttpClientHandler handler = new();
handler.ClientCertificates.Add(bindingCertificate);
var httpClient = new HttpClient(handler);
HttpClientConfig.ConfigureRequestHeadersAndSize(httpClient);
return httpClient;
#else
throw new NotSupportedException("mTLS is not supported on this platform.");
#endif
}
public HttpClient GetHttpClient()
{
return s_httpClientPool.GetOrAdd("non_mtls", _ => CreateHttpClient());
}
public HttpClient GetHttpClient(X509Certificate2 x509Certificate2)
{
if (x509Certificate2 == null)
{
return GetHttpClient();
}
string key = x509Certificate2.Thumbprint;
return s_httpClientPool.GetOrAdd(key, _ => CreateMtlsHttpClient(x509Certificate2));
}
private static void CheckAndManageCache()
{
lock (s_cacheLock)
{
if (s_httpClientPool.Count >= 1000)
{
s_httpClientPool.Clear();
}
}
}
// referenced in unit tests
internal static void ResetStaticStateForTest()
{
lock (s_cacheLock)
{
foreach (KeyValuePair<string, HttpClient> pooledClient in s_httpClientPool)
{
pooledClient.Value?.Dispose();
}
s_httpClientPool.Clear();
Interlocked.Exchange(ref s_httpClientCreationCount, 0);
}
client.Dispose();
}
s_httpClientPool.Clear();
s_httpClientCreationCount = 0;
}
// This method is used for Service Fabric scenarios where a custom server certificate validation callback is required.
// It allows the caller to provide a custom HttpClientHandler with the callback.
// The server cert rotates so we need a new HttpClient for each call.
public HttpClient GetHttpClient(Func<HttpRequestMessage, X509Certificate2, X509Chain, SslPolicyErrors, bool> validateServerCert)
{
if (validateServerCert == null)
{
return GetHttpClient();
}
#if NET471_OR_GREATER || NETSTANDARD || NET
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) =>
{
return validateServerCert(message, cert, chain, sslPolicyErrors);
}
};
string key = handler.GetHashCode().ToString();
return s_httpClientPool.GetOrAdd(key, new HttpClient(handler));
#else
return GetHttpClient();
#endif
}
}
}