Skip to content

Commit 7f11b2f

Browse files
author
tekgator
committed
Share HttpClient to all plugins preventing Socket exhaustion
1 parent 1a10c5f commit 7f11b2f

18 files changed

Lines changed: 156 additions & 73 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
44
## [Unreleased]
55

66

7+
## [1.3.1] - 2023-06-01
8+
9+
### Fixed
10+
- All plugin share the same (static) HttpClient now if HttpClientFactory is not provided, preventing Socket exhaustion
11+
12+
713
## [1.3.0] - 2023-06-01
814
### Added
915
- Paper plugin flag pre and snapshot builds as IsSnapshot
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using System.Reflection;
2+
3+
namespace MinecraftJars.Core;
4+
5+
public class PluginHttpClientFactory
6+
{
7+
private static readonly object LockObject = new();
8+
9+
private readonly IHttpClientFactory? _httpClientFactory;
10+
private HttpClient? _httpClient;
11+
12+
public PluginHttpClientFactory(IHttpClientFactory? httpClientFactory = null)
13+
{
14+
_httpClientFactory = httpClientFactory;
15+
}
16+
17+
public HttpClient GetClient()
18+
{
19+
lock (LockObject)
20+
{
21+
HttpClient client;
22+
23+
if (_httpClientFactory != null)
24+
{
25+
client = _httpClientFactory.CreateClient();
26+
}
27+
else
28+
{
29+
_httpClient ??= new HttpClient(new SocketsHttpHandler
30+
{
31+
PooledConnectionLifetime = TimeSpan.FromMinutes(15)
32+
});
33+
34+
client = _httpClient;
35+
}
36+
37+
if (!client.DefaultRequestHeaders.UserAgent.Any())
38+
{
39+
var assembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly();
40+
client.DefaultRequestHeaders.UserAgent.TryParseAdd(assembly.GetName().Name);
41+
}
42+
43+
return client;
44+
}
45+
}
46+
}
Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,10 @@
1-
using System.Reflection;
2-
3-
namespace MinecraftJars.Core.Providers;
1+
namespace MinecraftJars.Core.Providers;
42

53
public class ProviderOptions
64
{
75
/// <summary>
8-
/// If provided the CreatClient Method is utilized to create a HttpClient
9-
/// otherwise a new HttpClient is instantiated by each plugin
6+
/// If provided the CreateClient Method is utilized to create a HttpClient
7+
/// otherwise a new HttpClient is instantiated by the MinecraftJarManager
108
/// </summary>
119
public IHttpClientFactory? HttpClientFactory { get; init; }
12-
13-
public HttpClient GetHttpClient()
14-
{
15-
var client = HttpClientFactory?.CreateClient() ?? new HttpClient(new SocketsHttpHandler
16-
{
17-
PooledConnectionLifetime = TimeSpan.FromMinutes(15)
18-
});
19-
20-
if (client.DefaultRequestHeaders.UserAgent.Any())
21-
return client;
22-
23-
var assembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly();
24-
client.DefaultRequestHeaders.UserAgent.TryParseAdd(assembly.GetName().Name);
25-
26-
return client;
27-
}
2810
}

MinecraftJars.Plugin/MinecraftJars.Plugin.Fabric/FabricProvider.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.ComponentModel.Composition;
2+
using MinecraftJars.Core;
23
using MinecraftJars.Core.Projects;
34
using MinecraftJars.Core.Providers;
45
using MinecraftJars.Core.Versions;
@@ -9,12 +10,14 @@ namespace MinecraftJars.Plugin.Fabric;
910
public class FabricProvider : IMinecraftProvider
1011
{
1112
[ImportingConstructor]
12-
public FabricProvider(ProviderOptions? options)
13+
public FabricProvider(
14+
PluginHttpClientFactory httpClientFactory,
15+
ProviderOptions? options)
1316
{
17+
FabricVersionFactory.HttpClientFactory = httpClientFactory;
1418
ProviderOptions = options ?? new ProviderOptions();
15-
FabricVersionFactory.HttpClient = ProviderOptions.GetHttpClient();
1619
}
17-
20+
1821
public ProviderOptions ProviderOptions { get; }
1922
public string Name => "Fabric";
2023
public byte[] Logo => Properties.Resources.Fabric;

MinecraftJars.Plugin/MinecraftJars.Plugin.Fabric/FabricVersionFactory.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Net.Http.Json;
2+
using MinecraftJars.Core;
23
using MinecraftJars.Core.Downloads;
34
using MinecraftJars.Core.Versions;
45
using MinecraftJars.Plugin.Fabric.Model;
@@ -13,7 +14,7 @@ internal static class FabricVersionFactory
1314
private const string FabricLoaderRequestUri = FabricBaseRequestUri + "/versions/loader/{0}";
1415
private const string FabricDownloadRequestUri = FabricBaseRequestUri + "/versions/loader/{0}/{1}/{2}/server/jar";
1516

16-
public static HttpClient HttpClient { get; set; } = default!;
17+
public static PluginHttpClientFactory HttpClientFactory { get; set; } = default!;
1718

1819
public static async Task<List<FabricVersion>> GetVersion(
1920
string projectName,
@@ -22,7 +23,8 @@ public static async Task<List<FabricVersion>> GetVersion(
2223
{
2324
var project = FabricProjectFactory.Projects.Single(p => p.Name.Equals(projectName));
2425

25-
var versionsApi = await HttpClient.GetFromJsonAsync<Versions>(FabricVersionsRequestUri, cancellationToken) ??
26+
var client = HttpClientFactory.GetClient();
27+
var versionsApi = await client.GetFromJsonAsync<Versions>(FabricVersionsRequestUri, cancellationToken) ??
2628
throw new InvalidOperationException("Could not acquire game type details.");
2729

2830
var versions = (from game in versionsApi.Games
@@ -48,7 +50,8 @@ public static async Task<IMinecraftDownload> GetDownload(
4850
CancellationToken cancellationToken)
4951
{
5052
var requestUriCompatibleLoaders = string.Format(FabricLoaderRequestUri, version.Version);
51-
var compatibleLoaders = await HttpClient
53+
var client = HttpClientFactory.GetClient();
54+
var compatibleLoaders = await client
5255
.GetFromJsonAsync<List<CompatibleLoader>>(requestUriCompatibleLoaders, cancellationToken);
5356

5457
if (compatibleLoaders == null || !compatibleLoaders.Any())

MinecraftJars.Plugin/MinecraftJars.Plugin.Mohist/MohistProvider.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.ComponentModel.Composition;
2+
using MinecraftJars.Core;
23
using MinecraftJars.Core.Projects;
34
using MinecraftJars.Core.Providers;
45
using MinecraftJars.Core.Versions;
@@ -9,12 +10,14 @@ namespace MinecraftJars.Plugin.Mohist;
910
public class MohistProvider : IMinecraftProvider
1011
{
1112
[ImportingConstructor]
12-
public MohistProvider(ProviderOptions? options)
13+
public MohistProvider(
14+
PluginHttpClientFactory httpClientFactory,
15+
ProviderOptions? options)
1316
{
17+
MohistVersionFactory.HttpClientFactory = httpClientFactory;
1418
ProviderOptions = options ?? new ProviderOptions();
15-
MohistVersionFactory.HttpClient = ProviderOptions.GetHttpClient();
1619
}
17-
20+
1821
public ProviderOptions ProviderOptions { get; }
1922
public string Name => "Mohist";
2023
public byte[] Logo => Properties.Resources.Mohist;

MinecraftJars.Plugin/MinecraftJars.Plugin.Mohist/MohistVersionFactory.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Net.Http.Json;
2+
using MinecraftJars.Core;
23
using MinecraftJars.Core.Downloads;
34
using MinecraftJars.Core.Versions;
45
using MinecraftJars.Plugin.Mohist.Model;
@@ -11,7 +12,7 @@ internal static class MohistVersionFactory
1112
private const string MohistVersionRequestUri = "https://mohistmc.com/api/versions";
1213
private const string MohistLatestBuildRequestUri = "https://mohistmc.com/api/{0}/latest/";
1314

14-
public static HttpClient HttpClient { get; set; } = default!;
15+
public static PluginHttpClientFactory HttpClientFactory { get; set; } = default!;
1516

1617
public static async Task<List<MohistVersion>> GetVersion(
1718
string projectName,
@@ -20,7 +21,8 @@ public static async Task<List<MohistVersion>> GetVersion(
2021
{
2122
var project = MohistProjectFactory.Projects.Single(p => p.Name.Equals(projectName));
2223

23-
var versionApi = await HttpClient.GetFromJsonAsync<List<string>>(MohistVersionRequestUri, cancellationToken) ??
24+
var client = HttpClientFactory.GetClient();
25+
var versionApi = await client.GetFromJsonAsync<List<string>>(MohistVersionRequestUri, cancellationToken) ??
2426
throw new InvalidOperationException("Could not acquire version details.");
2527

2628
versionApi.Reverse();
@@ -42,8 +44,9 @@ public static async Task<IMinecraftDownload> GetDownload(
4244
MohistVersion version,
4345
CancellationToken cancellationToken)
4446
{
47+
var client = HttpClientFactory.GetClient();
4548
var requestUriLatestBuild = string.Format(MohistLatestBuildRequestUri, version.Version);
46-
var latestBuild = await HttpClient.GetFromJsonAsync<Build>(requestUriLatestBuild, cancellationToken);
49+
var latestBuild = await client.GetFromJsonAsync<Build>(requestUriLatestBuild, cancellationToken);
4750

4851
if (latestBuild == null || string.IsNullOrWhiteSpace(latestBuild.Url))
4952
throw new InvalidOperationException("Could not acquire download details.");
@@ -54,7 +57,7 @@ public static async Task<IMinecraftDownload> GetDownload(
5457
if (options.LoadFilesize)
5558
{
5659
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, latestBuild.Url);
57-
using var httpResponse = await HttpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
60+
using var httpResponse = await client.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
5861

5962
if (httpResponse.IsSuccessStatusCode)
6063
{

MinecraftJars.Plugin/MinecraftJars.Plugin.Mojang/MojangProvider.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.ComponentModel.Composition;
2+
using MinecraftJars.Core;
23
using MinecraftJars.Core.Projects;
34
using MinecraftJars.Core.Providers;
45
using MinecraftJars.Core.Versions;
@@ -9,10 +10,12 @@ namespace MinecraftJars.Plugin.Mojang;
910
public class MojangProvider : IMinecraftProvider
1011
{
1112
[ImportingConstructor]
12-
public MojangProvider(ProviderOptions? options)
13+
public MojangProvider(
14+
PluginHttpClientFactory httpClientFactory,
15+
ProviderOptions? options)
1316
{
17+
MojangVersionFactory.HttpClientFactory = httpClientFactory;
1418
ProviderOptions = options ?? new ProviderOptions();
15-
MojangVersionFactory.HttpClient = ProviderOptions.GetHttpClient();
1619
}
1720

1821
public ProviderOptions ProviderOptions { get; }

MinecraftJars.Plugin/MinecraftJars.Plugin.Mojang/MojangVersionFactory.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Net.Http.Json;
33
using System.Net.Mime;
44
using System.Text.RegularExpressions;
5+
using MinecraftJars.Core;
56
using MinecraftJars.Core.Downloads;
67
using MinecraftJars.Core.Versions;
78
using MinecraftJars.Plugin.Mojang.Models;
@@ -18,7 +19,7 @@ internal static partial class MojangVersionFactory
1819
private static partial Regex MojangBedrockDownloadLink();
1920
private const string MojangBedrockRequestUri = "https://www.minecraft.net/download/server/bedrock";
2021

21-
public static HttpClient HttpClient { get; set; } = default!;
22+
public static PluginHttpClientFactory HttpClientFactory { get; set; } = default!;
2223

2324
public static Task<List<MojangVersion>> GetVersion(
2425
string projectName,
@@ -39,8 +40,8 @@ private static async Task<List<MojangVersion>> GetVersionVanilla(
3940
CancellationToken cancellationToken)
4041
{
4142
var project = MojangProjectFactory.Projects.Single(p => p.Name.Equals(projectName));
42-
43-
var manifest = await HttpClient.GetFromJsonAsync<Manifest>(MojangVanillaRequestUri, cancellationToken) ??
43+
var client = HttpClientFactory.GetClient();
44+
var manifest = await client.GetFromJsonAsync<Manifest>(MojangVanillaRequestUri, cancellationToken) ??
4445
throw new InvalidOperationException("Could not acquire version details.");
4546

4647
var versions = (from version in manifest.Versions
@@ -74,7 +75,8 @@ private static async Task<List<MojangVersion>> GetVersionBedrock(
7475
request.Headers.AcceptLanguage.ParseAdd("en-US, en");
7576
request.Headers.CacheControl = new CacheControlHeaderValue { NoCache = true };
7677

77-
var response = await HttpClient.SendAsync(request, cancellationToken);
78+
var client = HttpClientFactory.GetClient();
79+
var response = await client.SendAsync(request, cancellationToken);
7880

7981
if (!response.IsSuccessStatusCode)
8082
throw new InvalidOperationException("Could not acquire version details.");
@@ -120,7 +122,8 @@ private static async Task<IMinecraftDownload> GetDownloadVanilla(
120122
MojangVersion version,
121123
CancellationToken cancellationToken)
122124
{
123-
var detail = await HttpClient.GetFromJsonAsync<Detail>(version.DetailUrl, cancellationToken) ??
125+
var client = HttpClientFactory.GetClient();
126+
var detail = await client.GetFromJsonAsync<Detail>(version.DetailUrl, cancellationToken) ??
124127
throw new InvalidOperationException("Could not acquire download details.");
125128

126129
if (detail.Downloads.Server == null)
@@ -153,8 +156,9 @@ private static async Task<IMinecraftDownload> GetDownloadBedrock(
153156

154157
if (options.LoadFilesize)
155158
{
159+
var client = HttpClientFactory.GetClient();
156160
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, version.DetailUrl);
157-
using var httpResponse = await HttpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
161+
using var httpResponse = await client.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
158162

159163
if (httpResponse.IsSuccessStatusCode)
160164
contentLength = httpResponse.Content.Headers.ContentLength ?? 0;

MinecraftJars.Plugin/MinecraftJars.Plugin.Paper/PaperProvider.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.ComponentModel.Composition;
2+
using MinecraftJars.Core;
23
using MinecraftJars.Core.Projects;
34
using MinecraftJars.Core.Providers;
45
using MinecraftJars.Core.Versions;
@@ -9,12 +10,14 @@ namespace MinecraftJars.Plugin.Paper;
910
public class PaperProvider : IMinecraftProvider
1011
{
1112
[ImportingConstructor]
12-
public PaperProvider(ProviderOptions? options)
13+
public PaperProvider(
14+
PluginHttpClientFactory httpClientFactory,
15+
ProviderOptions? options)
1316
{
17+
PaperVersionFactory.HttpClientFactory = httpClientFactory;
1418
ProviderOptions = options ?? new ProviderOptions();
15-
PaperVersionFactory.HttpClient = ProviderOptions.GetHttpClient();
1619
}
17-
20+
1821
public ProviderOptions ProviderOptions { get; }
1922
public string Name => "Paper";
2023
public byte[] Logo => Properties.Resources.Paper;

0 commit comments

Comments
 (0)