Skip to content

Commit 3279793

Browse files
Honor property Maximum pool size per route.
1 parent 7053cc9 commit 3279793

File tree

3 files changed

+82
-10
lines changed

3 files changed

+82
-10
lines changed

dotnet/src/dotnetframework/GxClasses/Core/gxconfig.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1449,13 +1449,14 @@ internal static bool SingletonHttpClient()
14491449
{
14501450
if (singletonHttpClient == -1)
14511451
{
1452-
if (Config.GetValueOf("SingletonHttpClient", out string sValue) && int.TryParse(sValue, out int value))
1452+
if (Config.GetValueOrEnvironmentVarOf("SINGLETON_HTTPCLIENT", out string sValue) && int.TryParse(sValue, out int value))
14531453
singletonHttpClient = value;
1454+
else
1455+
singletonHttpClient = 1;
14541456
}
14551457
return singletonHttpClient==1;
1456-
}
1457-
1458-
internal static string CorsAllowedOrigins()
1458+
}
1459+
internal static string CorsAllowedOrigins()
14591460
{
14601461
if (Config.GetValueOf("CORS_ALLOW_ORIGIN", out string corsOrigin))
14611462
return corsOrigin;
@@ -1504,7 +1505,7 @@ public static int GetHttpClientMaxConnectionPerRoute()
15041505
try
15051506
{
15061507
string strmax;
1507-
if (Config.GetValueOf("HTTPCLIENT_MAX_PER_ROUTE", out strmax))
1508+
if (Config.GetValueOrEnvironmentVarOf("HTTPCLIENT_MAX_PER_ROUTE", out strmax))
15081509
{
15091510
httpclient_max_per_route = Convert.ToInt32(strmax);
15101511
}

dotnet/src/dotnetframework/GxClasses/Domain/GxHttpClient.cs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ internal byte[] ReceiveData
151151

152152
#if NETCORE
153153
private const int POOLED_CONNECTION_LIFETIME_MINUTES = 2;
154-
private static ConcurrentDictionary<string, HttpClient> _httpClientInstances = new ConcurrentDictionary<string, HttpClient>();
154+
internal static ConcurrentDictionary<string, HttpClient> _httpClientInstances = new ConcurrentDictionary<string, HttpClient>();
155155
private static HttpClient GetHttpClientInstance(Uri URI, int timeout, ArrayList authCollection, ArrayList authProxyCollection, X509Certificate2Collection certificateCollection, List<string> fileCertificateCollection, string proxyHost, int proxyPort, out bool disposableInstance)
156156
{
157157
if (CacheableInstance(authCollection, authProxyCollection))
@@ -209,8 +209,13 @@ private static SocketsHttpHandler GetHandler(Uri URI, ArrayList authCollection,
209209
{
210210
SocketsHttpHandler handler = new SocketsHttpHandler()
211211
{
212-
PooledConnectionLifetime = TimeSpan.FromMinutes(POOLED_CONNECTION_LIFETIME_MINUTES)
212+
PooledConnectionLifetime = TimeSpan.FromMinutes(POOLED_CONNECTION_LIFETIME_MINUTES),
213213
};
214+
int maxConnections = Preferences.GetHttpClientMaxConnectionPerRoute();
215+
if (maxConnections != 0)
216+
{
217+
handler.MaxConnectionsPerServer = maxConnections;
218+
}
214219
handler.Credentials = getCredentialCache(URI, authCollection);
215220

216221
if (ServicePointManager.ServerCertificateValidationCallback != null)
@@ -1015,11 +1020,13 @@ public void HttpClientExecute(string method, string name)
10151020
internal void LoadResponseHeaders(HttpResponseMessage resp)
10161021
{
10171022
_respHeaders = new NameValueCollection();
1018-
foreach (KeyValuePair<string, IEnumerable<string>> header in resp.Headers)
1023+
HttpResponseHeaders headers = resp.Headers;
1024+
foreach (KeyValuePair<string, IEnumerable<string>> header in headers)
10191025
{
10201026
_respHeaders.Add(header.Key, String.Join(",", header.Value));
10211027
}
1022-
foreach (KeyValuePair<string, IEnumerable<string>> header in resp.Content.Headers)
1028+
HttpContentHeaders contentHeaders = resp.Content.Headers;
1029+
foreach (KeyValuePair<string, IEnumerable<string>> header in contentHeaders)
10231030
{
10241031
_respHeaders.Add(header.Key, String.Join(",", header.Value));
10251032
}

dotnet/test/DotNetUnitTest/Domain/GxHttpClientTest.cs

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.IO;
4+
using System.Linq;
35
using System.Net;
6+
using System.Net.Http;
7+
using System.Threading.Tasks;
48
using GeneXus.Application;
59
using GeneXus.Http.Client;
610
using Xunit;
@@ -14,6 +18,10 @@ namespace xUnitTesting
1418

1519
public class GxHttpClientTest
1620
{
21+
const int MAX_CONNECTIONS= 5;
22+
public GxHttpClientTest() {
23+
Environment.SetEnvironmentVariable("GX_HTTPCLIENT_MAX_PER_ROUTE", MAX_CONNECTIONS.ToString(), EnvironmentVariableTarget.Process);
24+
}
1725
[Fact]
1826
public void AddHeaderWithSpecialCharactersDoesNotThrowException()
1927
{
@@ -70,8 +78,64 @@ public void HttpClientCookieHeader()
7078
Assert.NotEqual(((int)HttpStatusCode.InternalServerError), oldHttpclient.StatusCode);
7179
}
7280
}
81+
#if NETCORE
82+
[Fact(Skip = "For local testing only")]
83+
public void TestHttpClientMaxPoolSize()
84+
{
85+
HttpClientMaxPoolSize().Wait();
86+
}
87+
async Task HttpClientMaxPoolSize() {
88+
GxContext context = new GxContext();
89+
string baseUrl = "http://localhost:8082/HttpClientTestNETSQLServer/testcookies.aspx";
90+
91+
var tasks = new List<Task>();
92+
93+
for (int i = 0; i < MAX_CONNECTIONS * 10; i++)
94+
{
95+
string url = $"{baseUrl}?id=" + i;
96+
tasks.Add(Task.Run(() => ExecuteGet(url)));
97+
}
98+
await Task.WhenAll(tasks);
99+
100+
Assert.Single(GxHttpClient._httpClientInstances);
101+
102+
HttpClient c = GxHttpClient._httpClientInstances.First().Value;
103+
Assert.NotNull(c);
104+
105+
Assert.True(pendingRequestsCount <= MAX_CONNECTIONS, $"Active connections ({pendingRequestsCount}) exceed MaxConnectionsPerServer ({MAX_CONNECTIONS})");
106+
}
107+
static private int pendingRequestsCount = 0;
108+
static private readonly object syncObject = new object();
109+
static private void IncrementPendingRequestsCount()
110+
{
111+
lock (syncObject)
112+
{
113+
pendingRequestsCount++;
114+
}
115+
}
73116

74-
[Fact(Skip ="For local testing only")]
117+
static private void DecrementPendingRequestsCount()
118+
{
119+
lock (syncObject)
120+
{
121+
pendingRequestsCount--;
122+
}
123+
}
124+
private string ExecuteGet(string url)
125+
{
126+
GxContext context = new GxContext();
127+
using (GxHttpClient httpclient = new GxHttpClient(context))
128+
{
129+
IncrementPendingRequestsCount();
130+
httpclient.HttpClientExecute("GET", url);
131+
Assert.Equal((int)HttpStatusCode.OK, httpclient.StatusCode); //When failed, turn on log.config to see server side error.
132+
DecrementPendingRequestsCount();
133+
return httpclient.ToString();
134+
}
135+
}
136+
137+
#endif
138+
[Fact(Skip = "For local testing only")]
75139
public void HttpClientCookiesTest()
76140
{
77141
GxContext context = new GxContext();

0 commit comments

Comments
 (0)