diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index c5edba26a..14b7e099b 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -44,7 +44,9 @@ Template for new versions: ### Other -*None yet* +* Properly re-use `HttpClient` in `NetManager` meaning we properly pool connections to the auth server, improving performance. +* Hub advertisements have extended keep-alive pool timeout, so the connection can be kept active between advertisements. +* All HTTP requests from the engine now have appropriate `User-Agent` header. ### Internal diff --git a/Robust.Server/ServerHub/HubManager.cs b/Robust.Server/ServerHub/HubManager.cs index 72bf83fb1..f9f63c5bf 100644 --- a/Robust.Server/ServerHub/HubManager.cs +++ b/Robust.Server/ServerHub/HubManager.cs @@ -1,6 +1,5 @@ using System; using System.Net.Http; -using System.Net.Http.Headers; using System.Net.Http.Json; using System.Threading.Tasks; using Robust.Shared; @@ -27,16 +26,7 @@ internal sealed class HubManager private bool _active; private bool _firstAdvertisement = true; - private readonly HttpClient _httpClient; - - public HubManager() - { - _httpClient = new HttpClient(); - - var assembly = typeof(HubManager).Assembly.GetName(); - if (assembly is { Name: { } name, Version: { } version }) - _httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(name, version.ToString())); - } + private HttpClient? _httpClient; public async void Start() { @@ -46,7 +36,7 @@ internal sealed class HubManager if (!activate) return; - _cfg.OnValueChanged(CVars.HubAdvertiseInterval, i => _interval = TimeSpan.FromSeconds(i), true); + _cfg.OnValueChanged(CVars.HubAdvertiseInterval, UpdateInterval, true); _cfg.OnValueChanged(CVars.HubMasterUrl, s => _masterUrl = s, true); var url = _cfg.GetCVar(CVars.HubServerUrl); @@ -70,6 +60,21 @@ internal sealed class HubManager _advertiseUrl = url; } + private void UpdateInterval(int interval) + { + _interval = TimeSpan.FromSeconds(interval); + _httpClient?.Dispose(); + + _httpClient = new HttpClient(new SocketsHttpHandler + { + // Keep-alive connections stay open for longer than the advertise interval. + // This way the same HTTPS connection can be re-used. + PooledConnectionIdleTimeout = _interval + TimeSpan.FromSeconds(10), + }); + + HttpClientUserAgent.AddUserAgent(_httpClient); + } + public void Heartbeat() { if (!_active || _advertiseUrl == null) @@ -86,12 +91,13 @@ internal sealed class HubManager private async void SendPing() { DebugTools.AssertNotNull(_advertiseUrl); + DebugTools.AssertNotNull(_httpClient); var apiUrl = $"{_masterUrl}api/servers/advertise"; try { - using var response = await _httpClient.PostAsJsonAsync(apiUrl, new AdvertiseRequest(_advertiseUrl!)); + using var response = await _httpClient!.PostAsJsonAsync(apiUrl, new AdvertiseRequest(_advertiseUrl!)); if (!response.IsSuccessStatusCode) { @@ -118,9 +124,11 @@ internal sealed class HubManager private async Task GuessAddress() { + DebugTools.AssertNotNull(_httpClient); + var ipifyUrl = _cfg.GetCVar(CVars.HubIpifyUrl); - var req = await _httpClient.GetFromJsonAsync(ipifyUrl); + var req = await _httpClient!.GetFromJsonAsync(ipifyUrl); return $"ss14://{req!.Ip}:{_cfg.GetCVar(CVars.NetPort)}/"; } diff --git a/Robust.Server/ServerStatus/WatchdogApi.cs b/Robust.Server/ServerStatus/WatchdogApi.cs index 7ea06ec59..8298908fe 100644 --- a/Robust.Server/ServerStatus/WatchdogApi.cs +++ b/Robust.Server/ServerStatus/WatchdogApi.cs @@ -12,6 +12,7 @@ using Robust.Shared.Configuration; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Timing; +using Robust.Shared.Utility; #nullable enable @@ -35,6 +36,11 @@ namespace Robust.Server.ServerStatus private Uri? _baseUri; private ISawmill _sawmill = default!; + public WatchdogApi() + { + HttpClientUserAgent.AddUserAgent(_httpClient); + } + public void PostInject() { _sawmill = Logger.GetSawmill("watchdogApi"); diff --git a/Robust.Shared/Network/NetManager.ClientConnect.cs b/Robust.Shared/Network/NetManager.ClientConnect.cs index ed902f1af..da275a8fc 100644 --- a/Robust.Shared/Network/NetManager.ClientConnect.cs +++ b/Robust.Shared/Network/NetManager.ClientConnect.cs @@ -192,9 +192,10 @@ namespace Robust.Shared.Network var authHash = Convert.ToBase64String(authHashBytes); var joinReq = new JoinRequest(authHash); - var httpClient = new HttpClient(); - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("SS14Auth", authToken); - var joinResp = await httpClient.PostAsJsonAsync(authServer + "api/session/join", joinReq, cancel); + var request = new HttpRequestMessage(HttpMethod.Post, authServer + "api/session/join"); + request.Content = JsonContent.Create(joinReq); + request.Headers.Authorization = new AuthenticationHeaderValue("SS14Auth", authToken); + var joinResp = await _httpClient.SendAsync(request, cancel); joinResp.EnsureSuccessStatusCode(); diff --git a/Robust.Shared/Network/NetManager.ServerAuth.cs b/Robust.Shared/Network/NetManager.ServerAuth.cs index 81f7cca04..f05fc4ddf 100644 --- a/Robust.Shared/Network/NetManager.ServerAuth.cs +++ b/Robust.Shared/Network/NetManager.ServerAuth.cs @@ -115,9 +115,8 @@ namespace Robust.Shared.Network var authHashBytes = MakeAuthHash(sharedSecret, CryptoPublicKey!); var authHash = Base64Helpers.ConvertToBase64Url(authHashBytes); - var client = new HttpClient(); var url = $"{authServer}api/session/hasJoined?hash={authHash}&userId={msgEncResponse.UserId}"; - var joinedRespJson = await client.GetFromJsonAsync(url); + var joinedRespJson = await _httpClient.GetFromJsonAsync(url); if (joinedRespJson is not {IsValid: true}) { diff --git a/Robust.Shared/Network/NetManager.cs b/Robust.Shared/Network/NetManager.cs index 44d2a6c8e..ee5387ca5 100644 --- a/Robust.Shared/Network/NetManager.cs +++ b/Robust.Shared/Network/NetManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Net; +using System.Net.Http; using System.Net.Sockets; using System.Runtime.Serialization; using System.Threading; @@ -133,6 +134,8 @@ namespace Robust.Shared.Network private readonly HashSet _awaitingDisconnectToConnect = new HashSet(); + private readonly HttpClient _httpClient = new(); + /// public int Port => _config.GetCVar(CVars.NetPort); @@ -239,6 +242,8 @@ namespace Robust.Shared.Network throw new InvalidOperationException("NetManager has already been initialized."); } + HttpClientUserAgent.AddUserAgent(_httpClient); + SynchronizeNetTime(); IsServer = isServer;