From 1783e533e2d54ab8afa06d0ee5d28f03bd747e31 Mon Sep 17 00:00:00 2001 From: maxisoft Date: Fri, 11 Oct 2024 16:24:01 +0200 Subject: [PATCH] Use SocketsHttpHandler & Implement property access with reflection for resiliency (fix #99) Change HttpClientHandler to SocketsHttpHandler to match ArchiSteamFarm upstream code This commit addresses the issue raised in #99 by using reflection to set properties on `SocketsHttpHandler` and `HttpClient`. This ensures that our code continues to function even if the property names are changed in a future trimmed binary. **Changes:** * Modified `SimpleHttpClient` constructor to use reflection-based property setting for: * `AutomaticDecompression` * `MaxConnectionsPerServer` * `EnableMultipleHttp2Connections` * Added a new helper method `SetPropertyValue` for generic property access with logging. * Updated `SetExpectContinueProperty` to use reflection as well. * Introduced a new method `SetPropertyWithLogging` to handle potential exceptions and log warnings if property access fails. * Updated `Directory.Build.props` to increment the version to `1.7.1.0`. **Additional Notes:** * Reflection can be slightly slower than direct property access. However, this approach offers greater flexibility and resilience to potential changes in the underlying libraries. **This commit is related to issue #99.** --- .../HttpClientSimple/SimpleHttpClient.cs | 96 +++++++++++-------- Directory.Build.props | 2 +- 2 files changed, 58 insertions(+), 40 deletions(-) diff --git a/ASFFreeGames/HttpClientSimple/SimpleHttpClient.cs b/ASFFreeGames/HttpClientSimple/SimpleHttpClient.cs index 3a0186b..4c331ec 100644 --- a/ASFFreeGames/HttpClientSimple/SimpleHttpClient.cs +++ b/ASFFreeGames/HttpClientSimple/SimpleHttpClient.cs @@ -6,7 +6,6 @@ using System.Net.Http.Headers; using System.Reflection; using System.Runtime.CompilerServices; -using System.Text; using System.Threading; using System.Threading.Tasks; @@ -15,32 +14,31 @@ namespace Maxisoft.ASF.HttpClientSimple; #nullable enable public sealed class SimpleHttpClient : IDisposable { - private readonly HttpClientHandler HttpClientHandler; + private readonly HttpMessageHandler HttpMessageHandler; private readonly HttpClient HttpClient; public SimpleHttpClient(IWebProxy? proxy = null, long timeout = 25_000) { - HttpClientHandler = new HttpClientHandler { - AutomaticDecompression = DecompressionMethods.All, - MaxConnectionsPerServer = 5 - }; + SocketsHttpHandler handler = new(); - SetCheckCertificateRevocationList(HttpClientHandler, true); + SetPropertyWithLogging(handler, nameof(SocketsHttpHandler.AutomaticDecompression), DecompressionMethods.All); + SetPropertyWithLogging(handler, nameof(SocketsHttpHandler.MaxConnectionsPerServer), 5, debugLogLevel: true); + SetPropertyWithLogging(handler, nameof(SocketsHttpHandler.EnableMultipleHttp2Connections), true); if (proxy is not null) { - HttpClientHandler.Proxy = proxy; - HttpClientHandler.UseProxy = true; + SetPropertyWithLogging(handler, nameof(SocketsHttpHandler.Proxy), proxy); + SetPropertyWithLogging(handler, nameof(SocketsHttpHandler.UseProxy), true); if (proxy.Credentials is not null) { - HttpClientHandler.PreAuthenticate = true; + SetPropertyWithLogging(handler, nameof(SocketsHttpHandler.PreAuthenticate), true); } } + HttpMessageHandler = handler; #pragma warning disable CA5399 - HttpClient = new HttpClient(HttpClientHandler, false) { - DefaultRequestVersion = HttpVersion.Version30, - Timeout = TimeSpan.FromMilliseconds(timeout) - }; + HttpClient = new HttpClient(handler, false); #pragma warning restore CA5399 + SetPropertyWithLogging(HttpClient, nameof(HttpClient.DefaultRequestVersion), HttpVersion.Version30); + SetPropertyWithLogging(HttpClient, nameof(HttpClient.Timeout), TimeSpan.FromMilliseconds(timeout)); SetExpectContinueProperty(HttpClient, false); @@ -82,21 +80,28 @@ public async Task GetStreamAsync(Uri uri, IEnumerable(T targetObject, string propertyName, object value) where T : class { try { - // Get the DefaultRequestHeaders property - PropertyInfo? defaultRequestHeadersProperty = httpClient.GetType().GetProperty("DefaultRequestHeaders", BindingFlags.Public | BindingFlags.Instance); - - if (defaultRequestHeadersProperty == null) { - throw new InvalidOperationException("HttpClient does not have DefaultRequestHeaders property."); - } + // Get the type of the target object + Type targetType = targetObject.GetType(); - if (defaultRequestHeadersProperty.GetValue(httpClient) is not HttpRequestHeaders defaultRequestHeaders) { - throw new InvalidOperationException("DefaultRequestHeaders is null."); - } - - // Get the ExpectContinue property - PropertyInfo? expectContinueProperty = defaultRequestHeaders.GetType().GetProperty("ExpectContinue", BindingFlags.Public | BindingFlags.Instance); + // Get the property information + PropertyInfo? propertyInfo = targetType.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance); - if ((expectContinueProperty != null) && expectContinueProperty.CanWrite) { - expectContinueProperty.SetValue(defaultRequestHeaders, value); + if ((propertyInfo is not null) && propertyInfo.CanWrite) { + // Set the property value + propertyInfo.SetValue(targetObject, value); return true; } @@ -136,6 +134,26 @@ private static bool SetExpectContinueProperty(HttpClient httpClient, bool value) return false; } + + private static void SetPropertyWithLogging(T targetObject, string propertyName, object value, bool debugLogLevel = false) where T : class { + try { + if (TrySetPropertyValue(targetObject, propertyName, value)) { + return; + } + } + catch (Exception) { + // ignored + } + + string logMessage = $"Failed to set {targetObject.GetType().Name} property {propertyName} to {value}. Please report this issue to github."; + + if (debugLogLevel) { + ArchiSteamFarm.Core.ASF.ArchiLogger.LogGenericDebug(logMessage); + } + else { + ArchiSteamFarm.Core.ASF.ArchiLogger.LogGenericWarning(logMessage); + } + } #endregion } diff --git a/Directory.Build.props b/Directory.Build.props index 336818b..7d67f32 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,7 +3,7 @@ ASFFreeGames - 1.7.0.0 + 1.7.1.0 net8.0