Skip to content

[Trimming] Put code for resolving HttpMessageHandler type name behind feature switch #10002

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 73 additions & 45 deletions src/Mono.Android/Android.Runtime/AndroidEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
using Java.Security;
using Javax.Net.Ssl;

using Microsoft.Android.Runtime;

namespace Android.Runtime {

public static class AndroidEnvironment {
Expand All @@ -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 ()
{
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions src/Mono.Android/ILLink/ILLink.Substitutions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,9 @@
<method signature="System.Boolean get_ManagedTypeMap()" body="stub" feature="Microsoft.Android.Runtime.RuntimeFeature.ManagedTypeMap" featurevalue="false" value="false" />
<method signature="System.Boolean get_ManagedTypeMap()" body="stub" feature="Microsoft.Android.Runtime.RuntimeFeature.ManagedTypeMap" featurevalue="true" value="true" />
</type>
<type fullname="Microsoft.Android.Runtime.RuntimeFeature">
<method signature="System.Boolean get_XaHttpClientHandlerTypeEnvironmentVariable()" body="stub" feature="Microsoft.Android.Runtime.RuntimeFeature.XaHttpClientHandlerTypeEnvironmentVariable" featurevalue="false" value="false" />
<method signature="System.Boolean get_XaHttpClientHandlerTypeEnvironmentVariable()" body="stub" feature="Microsoft.Android.Runtime.RuntimeFeature.XaHttpClientHandlerTypeEnvironmentVariable" featurevalue="true" value="true" />
</type>
</assembly>
</linker>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ See: https://github.com/dotnet/runtime/blob/b13715b6984889a709ba29ea8a1961db469f
Value="$([MSBuild]::ValueOrDefault('$(_AndroidUseManagedTypeMap)', 'false'))"
Trim="true"
/>
<RuntimeHostConfigurationOption Include="Microsoft.Android.Runtime.RuntimeFeature.XaHttpClientHandlerTypeEnvironmentVariable"
Value="$([MSBuild]::ValueOrDefault('$(_AndroidUseXaHttpClientHandlerTypeEnvironmentVariable)', 'false'))"
Trim="true"
/>
</ItemGroup>

<Target Name="_ParseRuntimeConfigFiles"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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);
}
}
}
Loading