From d5163cce0a0fb26304276db5cb363053cdf0b08c Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 3 Apr 2025 13:34:11 +0200 Subject: [PATCH 1/2] Put code for resolving HttpMessageHandler type from environment variable or MSBuild property behind a feature switch --- .../Android.Runtime/AndroidEnvironment.cs | 118 +++++++++++------- .../ILLink/ILLink.Substitutions.xml | 4 + .../RuntimeFeature.cs | 5 + ...icrosoft.Android.Sdk.RuntimeConfig.targets | 4 + 4 files changed, 86 insertions(+), 45 deletions(-) diff --git a/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs b/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs index a4ab68225b0..271b8a73c8d 100644 --- a/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs +++ b/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs @@ -15,6 +15,8 @@ using Java.Security; using Javax.Net.Ssl; +using Microsoft.Android.Runtime; + namespace Android.Runtime { public static class AndroidEnvironment { @@ -26,6 +28,7 @@ public static class AndroidEnvironment { static object lock_ = new object (); [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] static Type? httpMessageHandlerType; + static bool isFirstGetHttpMessageHandlerCall = true; static void SetupTrustManager () { @@ -333,66 +336,91 @@ static IWebProxy GetDefaultProxy () // This is invoked by // System.Net.Http.dll!System.Net.Http.HttpClient.cctor // DO NOT REMOVE - [DynamicDependency (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor, typeof (Xamarin.Android.Net.AndroidMessageHandler))] static object GetHttpMessageHandler () { - [UnconditionalSuppressMessage ("Trimming", "IL2057", Justification = "Preserved by the MarkJavaObjects trimmer step.")] - [return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - static Type TypeGetType (string typeName) => - Type.GetType (typeName, throwOnError: false); - - if (httpMessageHandlerType is null) { - var handlerTypeName = Environment.GetEnvironmentVariable ("XA_HTTP_CLIENT_HANDLER_TYPE")?.Trim (); - Type? handlerType = null; - if (!String.IsNullOrEmpty (handlerTypeName)) - handlerType = TypeGetType (handlerTypeName); - - // We don't do any type checking or casting here to avoid dependency on System.Net.Http in Mono.Android.dll - if (handlerType is null || !IsAcceptableHttpMessageHandlerType (handlerType)) { - handlerType = GetFallbackHttpMessageHandlerType (); + lock (lock_) { + if (isFirstGetHttpMessageHandlerCall) { + isFirstGetHttpMessageHandlerCall = false; + + string? handlerTypeName = Environment.GetEnvironmentVariable ("XA_HTTP_CLIENT_HANDLER_TYPE")?.Trim (); + if (RuntimeFeature.XaHttpClientHandlerTypeEnvironmentVariable) { + if (!string.IsNullOrEmpty (handlerTypeName)) { + Logger.Log (LogLevel.Warn, AndroidLogAppName, $"The $(AndroidHttpClientHandlerType) MSBuild property and the XA_HTTP_CLIENT_HANDLER_TYPE environment variable have been deprecated. If you need to use a custom HTTP handler, consider pass it to the HttpClient via constructor."); + } + + httpMessageHandlerType = GetHttpMessageHandlerType (handlerTypeName); + } else { + if (!string.IsNullOrEmpty (handlerTypeName)) { + Logger.Log (LogLevel.Warn, AndroidLogAppName, $"The $(AndroidHttpClientHandlerType) MSBuild property and the XA_HTTP_CLIENT_HANDLER_TYPE environment variable have been deprecated and they will be ignored (value: '{handlerTypeName}'). The default {typeof(Xamarin.Android.Net.AndroidMessageHandler).FullName} handler will be used. If you need to use a custom HTTP handler, pass it to the HttpClient via the constructor."); + Logger.Log (LogLevel.Warn, AndroidLogAppName, $"If your codebase relies on the legacy behavior, you can reenable the old behavior by setting the $(AndroidEnableLegacyXaHttpClientHandlerTypeEnvironmentVariable) MSBuild property to 'true' in your project file."); + } + } } + } + + if (RuntimeFeature.XaHttpClientHandlerTypeEnvironmentVariable) { + System.Diagnostics.Debug.Assert(httpMessageHandlerType is not null); - httpMessageHandlerType = handlerType; + return Activator.CreateInstance (httpMessageHandlerType) + ?? throw new InvalidOperationException ($"Could not create an instance of HTTP message handler type {httpMessageHandlerType.AssemblyQualifiedName}"); } - return Activator.CreateInstance (httpMessageHandlerType) - ?? throw new InvalidOperationException ($"Could not create an instance of HTTP message handler type {httpMessageHandlerType.AssemblyQualifiedName}"); + return new Xamarin.Android.Net.AndroidMessageHandler (); } - static bool IsAcceptableHttpMessageHandlerType (Type handlerType) + [RequiresUnreferencedCode ("The handler type might be removed by the trimmer.")] + private static Type GetHttpMessageHandlerType (string? handlerTypeName) { - if (Extends (handlerType, "System.Net.Http.HttpClientHandler, System.Net.Http")) { - // It's not possible to construct HttpClientHandler in this method because it would cause infinite recursion - // as HttpClientHandler's constructor calls the GetHttpMessageHandler function - Logger.Log (LogLevel.Warn, "MonoAndroid", $"The type {handlerType.AssemblyQualifiedName} cannot be used as the native HTTP handler because it is derived from System.Net.Htt.HttpClientHandler. Use a type that extends System.Net.Http.HttpMessageHandler instead."); - return false; + Type? handlerType = null; + if (!String.IsNullOrEmpty (handlerTypeName)) { + handlerType = Type.GetType (handlerTypeName); + + if (handlerType is null) { + Logger.Log (LogLevel.Warn, AndroidLogAppName, $"The type {handlerTypeName} set as the default HTTP handler was not found. The type was probably linked away."); + } } - if (!Extends (handlerType, "System.Net.Http.HttpMessageHandler, System.Net.Http")) { - Logger.Log (LogLevel.Warn, "MonoAndroid", $"The type {handlerType.AssemblyQualifiedName} set as the default HTTP handler is invalid. Use a type that extends System.Net.Http.HttpMessageHandler."); - return false; + + if (handlerType is null || !IsAcceptableHttpMessageHandlerType (handlerType)) { + handlerType = GetFallbackHttpMessageHandlerType (); } - return true; - } + return handlerType; - static bool Extends ( - Type handlerType, - [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - string baseTypeName) - { - var baseType = Type.GetType (baseTypeName, throwOnError: false); - return baseType?.IsAssignableFrom (handlerType) ?? false; - } + static bool IsAcceptableHttpMessageHandlerType (Type handlerType) + { + if (Extends (handlerType, "System.Net.Http.HttpClientHandler, System.Net.Http")) { + // It's not possible to construct HttpClientHandler in this method because it would cause infinite recursion + // as HttpClientHandler's constructor calls the GetHttpMessageHandler function + Logger.Log (LogLevel.Warn, "MonoAndroid", $"The type {handlerType.AssemblyQualifiedName} cannot be used as the native HTTP handler because it is derived from System.Net.Htt.HttpClientHandler. Use a type that extends System.Net.Http.HttpMessageHandler instead."); + return false; + } + if (!Extends (handlerType, "System.Net.Http.HttpMessageHandler, System.Net.Http")) { + Logger.Log (LogLevel.Warn, "MonoAndroid", $"The type {handlerType.AssemblyQualifiedName} set as the default HTTP handler is invalid. Use a type that extends System.Net.Http.HttpMessageHandler."); + return false; + } - [return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - static Type GetFallbackHttpMessageHandlerType () - { - const string typeName = "Xamarin.Android.Net.AndroidMessageHandler, Mono.Android"; - var handlerType = Type.GetType (typeName, throwOnError: false) - ?? throw new InvalidOperationException ($"The {typeName} was not found. The type was probably linked away."); + return true; + } - Logger.Log (LogLevel.Info, "MonoAndroid", $"Using {typeName} as the native HTTP message handler."); - return handlerType; + static bool Extends ( + Type handlerType, + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + string baseTypeName) + { + var baseType = Type.GetType (baseTypeName, throwOnError: false); + return baseType?.IsAssignableFrom (handlerType) ?? false; + } + + [return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + static Type GetFallbackHttpMessageHandlerType () + { + const string typeName = "Xamarin.Android.Net.AndroidMessageHandler, Mono.Android"; + var handlerType = Type.GetType (typeName, throwOnError: false) + ?? throw new InvalidOperationException ($"The {typeName} was not found. The type was probably linked away."); + + Logger.Log (LogLevel.Info, "MonoAndroid", $"Using {typeName} as the native HTTP message handler."); + return handlerType; + } } class _Proxy : IWebProxy { diff --git a/src/Mono.Android/ILLink/ILLink.Substitutions.xml b/src/Mono.Android/ILLink/ILLink.Substitutions.xml index 239252fe937..9ebb1d8784c 100644 --- a/src/Mono.Android/ILLink/ILLink.Substitutions.xml +++ b/src/Mono.Android/ILLink/ILLink.Substitutions.xml @@ -8,5 +8,9 @@ + + + + diff --git a/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs b/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs index 814b8c5ab7e..32b7a2ff93d 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs @@ -10,4 +10,9 @@ static class RuntimeFeature [FeatureSwitchDefinition ($"{FeatureSwitchPrefix}{nameof (ManagedTypeMap)}")] internal static bool ManagedTypeMap { get; } = AppContext.TryGetSwitch ($"{FeatureSwitchPrefix}{nameof (ManagedTypeMap)}", out bool isEnabled) ? isEnabled : false; + + [FeatureSwitchDefinition ($"{FeatureSwitchPrefix}{nameof (XaHttpClientHandlerTypeEnvironmentVariable)}")] + [FeatureGuard (typeof (RequiresUnreferencedCodeAttribute))] + internal static bool XaHttpClientHandlerTypeEnvironmentVariable { get; } = + AppContext.TryGetSwitch ($"{FeatureSwitchPrefix}{nameof (XaHttpClientHandlerTypeEnvironmentVariable)}", out bool isEnabled) ? isEnabled : false; } diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.RuntimeConfig.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.RuntimeConfig.targets index d2053ce9e21..6e9f076b883 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.RuntimeConfig.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.RuntimeConfig.targets @@ -51,6 +51,10 @@ See: https://github.com/dotnet/runtime/blob/b13715b6984889a709ba29ea8a1961db469f Value="$([MSBuild]::ValueOrDefault('$(_AndroidUseManagedTypeMap)', 'false'))" Trim="true" /> + Date: Thu, 3 Apr 2025 21:07:11 +0200 Subject: [PATCH 2/2] Update AndroidEnvironment tests --- .../Android.Runtime/AndroidEnvironmentTest.cs | 37 ++++--------------- 1 file changed, 7 insertions(+), 30 deletions(-) diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/AndroidEnvironmentTest.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/AndroidEnvironmentTest.cs index 76ff2e50b69..a4d86d137e0 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/AndroidEnvironmentTest.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/AndroidEnvironmentTest.cs @@ -27,43 +27,18 @@ public void TearDown () [Test] [TestCase (null)] - [TestCase ("Xamarin.Android.Net.AndroidHttpResponseMessage")] // does not extend HttpMessageHandler - // instantiating AndroidClientHandler or HttpClientHandler (or any other type extending HttpClientHandler) - // would cause infinite recursion in the .NET build and so it is replaced with AndroidMessageHandler - [TestCase ("System.Net.Http.HttpClientHandler, System.Net.Http")] + [TestCase ("Xamarin.Android.Net.AndroidHttpResponseMessage")] [TestCase ("Xamarin.Android.Net.AndroidClientHandler")] - public void GetHttpMessageHandler_FallbackToAndroidMessageHandler (string? typeName) - { - var handler = GetHttpMessageHandler (typeName); - - Assert.IsNotNull (handler, "GetHttpMessageHandler returned null"); - Assert.IsNotNull ("Xamarin.Android.Net.AndroidMessageHandler", handler.GetType ().FullName); - } - - [Test] - [TestCase ("System.Net.Http.HttpClientHandler")] // the type name doesn't contain the name of the assembly so the type won't be found + [TestCase ("System.Net.Http.HttpClientHandler, System.Net.Http")] + [TestCase ("System.Net.Http.HttpClientHandler")] [TestCase ("Some.Nonexistent.Type")] - public void GetHttpMessageHandler_FallbackForInaccessibleTypes (string typeName) - { - var handler = GetHttpMessageHandler (typeName); - - Assert.IsNotNull (handler, "GetHttpMessageHandler returned null"); - Assert.IsNotNull ("Xamarin.Android.Net.AndroidMessageHandler", handler.GetType ().FullName); - } - - [Test] - [TestCase ("Xamarin.Android.Net.AndroidMessageHandler")] [TestCase ("System.Net.Http.SocketsHttpHandler, System.Net.Http")] - public void GetHttpMessageHandler_OverridesDefaultValue (string typeName) + public void GetHttpMessageHandler_IgnoresValueOfEnvironmentVariable (string? typeName) { var handler = GetHttpMessageHandler (typeName); Assert.IsNotNull (handler, "GetHttpMessageHandler returned null"); - - // type's FullName doesn't contain the assembly name - var indexOfComma = typeName.IndexOf(','); - var expectedTypeName = indexOfComma > 0 ? typeName.Substring(0, indexOfComma) : typeName; - Assert.AreEqual (expectedTypeName, handler.GetType ().FullName); + Assert.AreEqual ("Xamarin.Android.Net.AndroidMessageHandler", handler.GetType ().FullName); } private static object? GetHttpMessageHandler (string? typeName) @@ -80,6 +55,8 @@ private static void ClearHttpMessageHandlerTypeCache () { var cacheField = typeof (AndroidEnvironment).GetField ("httpMessageHandlerType", BindingFlags.Static | BindingFlags.NonPublic)!; cacheField.SetValue (null, null); + var isFirstGetHttpMessageHandlerCallField = typeof (AndroidEnvironment).GetField ("isFirstGetHttpMessageHandlerCall", BindingFlags.Static | BindingFlags.NonPublic)!; + isFirstGetHttpMessageHandlerCallField.SetValue (null, false); } } }