Skip to content
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

Include native underlying handler support in HttpClientHandler for iOS/tvOS/MacCatalyst and Android #55384

Merged
merged 16 commits into from
Jul 15, 2021

Conversation

steveisok
Copy link
Member

Completes the plan for net6 laid out in https://github.com/dotnet/designs/blob/main/accepted/2020/mono-convergence/platform-specific-httpclient.md#the-plan-for-net-5

This change supports using either the native HttpMessageHandler types that exist in the Xamarin repos (NSUrlSessionHandler for iOS/tvOS/Catalyst and AndroidMessageHandler for Android) or SocketsHttpHandler as the underlying handler in HttpClientHandler.

The behavior for new HttpClient() was established earlier in net6, but did not go all the way. For example, if the System.Net.Http.UseNativeHttpHandler feature switch was set to true, using HttpClient in different ways would lead to different underlying handlers.

Before this PR:

// System.Net.Http.UseNativeHttpHandler == true

new HttpClient();    // Chooses the native handler as the underlying handler

var handler = new HttpClientHandler();       // SocketsHttpHandler is the only choice 
new HttpClient(handler);

The change creates a handful of partial HttpClientHandler files in order to split out the platform specific parts. As you review the PR, you'll notice a bunch of if (IsSocketHandler) blocks. The intent of these are to make use of the linker and get linked out once the linker knows which handler is the active one.

get
{
    if (IsSocketHandler)
    {
        return _socketHandler!.UseCookies;
    }
    else
    {
        return GetUseCookies();
    }
}

Get and Set methods like GetUseCookies make it easier to tell the linker via DynamicDependency to preserve the reflection calls being made to the native underlying handler.

[DynamicDependency("get_UseCookies", "Xamarin.Android.Net.AndroidMessageHandler", "Mono.Android")]
private bool GetUseCookies() => (bool)InvokeNativeHandlerMethod("get_UseCookies");

It is important to point out that the underlying handler has to be derived from HttpMessageHandler. It cannot be HttpClientHandler or you'll end up with a circular dependency.

Note, there will need to be additional changes to Xamarin Android once this change goes in and before they take on the new version. We will coordinate appropriately.

/cc @dalexsoto @rolfbjarne @jonpryor @jonathanpeppers

@dotnet-issue-labeler
Copy link

Note regarding the new-api-needs-documentation label:

This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, to please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change.

@ghost
Copy link

ghost commented Jul 9, 2021

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Issue Details

Completes the plan for net6 laid out in https://github.com/dotnet/designs/blob/main/accepted/2020/mono-convergence/platform-specific-httpclient.md#the-plan-for-net-5

This change supports using either the native HttpMessageHandler types that exist in the Xamarin repos (NSUrlSessionHandler for iOS/tvOS/Catalyst and AndroidMessageHandler for Android) or SocketsHttpHandler as the underlying handler in HttpClientHandler.

The behavior for new HttpClient() was established earlier in net6, but did not go all the way. For example, if the System.Net.Http.UseNativeHttpHandler feature switch was set to true, using HttpClient in different ways would lead to different underlying handlers.

Before this PR:

// System.Net.Http.UseNativeHttpHandler == true

new HttpClient();    // Chooses the native handler as the underlying handler

var handler = new HttpClientHandler();       // SocketsHttpHandler is the only choice 
new HttpClient(handler);

The change creates a handful of partial HttpClientHandler files in order to split out the platform specific parts. As you review the PR, you'll notice a bunch of if (IsSocketHandler) blocks. The intent of these are to make use of the linker and get linked out once the linker knows which handler is the active one.

get
{
    if (IsSocketHandler)
    {
        return _socketHandler!.UseCookies;
    }
    else
    {
        return GetUseCookies();
    }
}

Get and Set methods like GetUseCookies make it easier to tell the linker via DynamicDependency to preserve the reflection calls being made to the native underlying handler.

[DynamicDependency("get_UseCookies", "Xamarin.Android.Net.AndroidMessageHandler", "Mono.Android")]
private bool GetUseCookies() => (bool)InvokeNativeHandlerMethod("get_UseCookies");

It is important to point out that the underlying handler has to be derived from HttpMessageHandler. It cannot be HttpClientHandler or you'll end up with a circular dependency.

Note, there will need to be additional changes to Xamarin Android once this change goes in and before they take on the new version. We will coordinate appropriately.

/cc @dalexsoto @rolfbjarne @jonpryor @jonathanpeppers

Author: steveisok
Assignees: -
Labels:

area-System.Net.Http, new-api-needs-documentation

Milestone: -

{
get => throw new PlatformNotSupportedException();
set => throw new PlatformNotSupportedException();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be nice to have on Apple platforms. There's an underlying API with slightly different signature that is missing the X509Certificate2 wrapping and passes in the native Apple objects instead. In some earlier Xamarin issue/PR there was a code to do conversion between the two worlds. The reason it was not done directly was because of code size issues.

If this PR gets in then we should open a tracking issue to map this property (and possibly others too). Just putting it here as a reminder.

CancellationToken cancellationToken)
{
throw new PlatformNotSupportedException();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also be tracked in some issue. It's likely something that people will run into.

[UnsupportedOSPlatform("tvos")]
[UnsupportedOSPlatform("maccatalyst")]
public static Func<HttpRequestMessage, X509Certificate2?, X509Chain?, SslPolicyErrors, bool> DangerousAcceptAnyServerCertificateValidator =>
throw new PlatformNotSupportedException();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to run counter to the documentation:

Particularly in test scenarios, a common pattern use HttpClient to connect to a server with a certificate that shouldn't be validated, such as a self-signed certificate. You commonly do this with HttpClientHandler by setting the ServerCertificateCustomValidationCallback property to a delegate that always returns True; this indicates that the certificate has passed validation. However, not all implementations support this callback, and some throw PlatformNotSupportedException.

The DangerousAcceptAnyServerCertificateValidator property addresses this limitation. The delegate returned by the DangerousAcceptAnyServerCertificateValidator property can be assigned to the ServerCertificateCustomValidationCallback property, as the following example does:

handler.ServerCertificateCustomValidationCallback = httpClientHandler.DangerousAcceptAnyServerCertificateValidator;

This gives HttpClientHandler implementations a known object reference identity that expresses the developer's intention. If the object stored in the DangerousAcceptAnyServerCertificateValidator property is reference equals to DangerousAcceptAnyServerCertificateValidator, the runtime is able to entirely disable validation on a platform that would otherwise throw a PlatformNotSupportedException.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may be able to pull something off w/ the native handlers. As for SocketsHttpHandler (on Android at least), I don't think we'll be able to be able to support it. Maybe we'll be able to apply an ugly tweak, but as of right now it's not likely.

Copy link
Member

@filipnavara filipnavara Jul 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The minimal implementation is to return static (...) => true delegate here in DangerousAcceptAnyServerCertificateValidator and then check for it by reference in ServerCertificateCustomValidationCallback setter and not throw PlatformNotSupportedException. At least that's how I read the documentation. It doesn't necessarily guarantee that the validation gets bypassed on platforms where that is not possible. I could be reading the documentation wrong though (and on second re-read I think I read it wrong 😩).

SockersHttpHandler on Android may be tricky and I am aware of the limitations in SslStream implementation. For something like NSUrlSessionHandler it would be possible to expose DangerousAcceptAnyTrust callback and assign it through reflection to TrustOverrideForUrl.

<type fullname="System.Net.Http.HttpClient">
<method signature="System.Boolean IsNativeHandlerEnabled()" body="stub" value="true" feature="System.Net.Http.UseNativeHttpHandler" featurevalue="true" />
<type fullname="System.Net.Http.HttpClientHandler">
<method signature="System.Boolean IsNativeHandlerEnabled()" body="stub" value="false" feature="System.Net.Http.UseNativeHttpHandler" featurevalue="false" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to substitute on both true and false values?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I've tested, the current xml does substitute on true and false values.

@rolfbjarne
Copy link
Member

CC @spouliot

@steveisok
Copy link
Member Author

Failures are infra related.

@steveisok steveisok merged commit 44655fd into dotnet:main Jul 15, 2021
@steveisok steveisok deleted the streamline-http-usage-mobile branch July 15, 2021 04:36
@karelz karelz added this to the 6.0.0 milestone Jul 15, 2021
jonpryor pushed a commit to dotnet/android that referenced this pull request Jul 21, 2021
Fixes: dotnet/java-interop#854

Changes: dotnet/installer@e8b3b6b...9c46371

    % git diff --shortstat e8b3b6be...9c463710
     103 files changed, 2301 insertions(+), 2757 deletions(-)

Changes: dotnet/linker@a07cab7...460dd6d

    % git diff --shortstat a07cab7b...460dd6dd
     84 files changed, 2403 insertions(+), 258 deletions(-)

Changes: dotnet/runtime@02f70d0...96ce6b3

    % git diff --shortstat 02f70d0b90...96ce6b3535
     2586 files changed, 123677 insertions(+), 34433 deletions(-)

Changes: dotnet/java-interop@a5ed891...4fb7c14

  * dotnet/java-interop@4fb7c147: [build] set $(DisableImplicitNamespaceImports) by default (#859)
  * dotnet/java-interop@855ecfa3: [generator] Don't generate unexpected NRT types like `void?` (#856)
  * dotnet/java-interop@4a02bc32: Revert "[Xamarin.Android.Tools.Bytecode] hide nested types (#827)" (#855)
  * dotnet/java-interop@95c9b79d: [generator] Avoid 'error (…):' construct in diagnostic messages (#851)
  * dotnet/java-interop@7c4f7db0: [build] Bump to Mono with MSBuild 16.10 (#848)
  * dotnet/java-interop@0227cdae: [generator] Gracefully handle BindingGeneratorException. (#845)
  * dotnet/java-interop@ce1750fd: Add SECURITY.md (#846)

Context: dotnet/runtime#55384
Context: dotnet/sdk#18639

Updates:

  * Microsoft.Dotnet.Sdk.Internal: from 6.0.100-preview.7.21327.2 to 6.0.100-rc.1.21369.3
  * Microsoft.NET.ILLink.Tasks: from 6.0.100-preview.6.21322.1 to 6.0.100-preview.6.21366.2
  * Microsoft.NETCore.App.Ref: from 6.0.0-preview.7.21326.8 to 6.0.0-rc.1.21368.1

dotnet/runtime#55384 broke how .NET 6 interacts with
`AndroidClientHandler`.  Fix this by introducing a new
`Xamarin.Android.Net.AndroidMessageHandler` type for use on .NET 6,
and update the .NET 6 `AndroidClientHandler` to delegate to
`AndroidMessageHandler`.

`AndroidMessageHandler` doesn't exist on Legacy.

Update `.apkdesc` files:

  * `BuildReleaseArm64SimpleDotNet` is ~37KB smaller
  * `BuildReleaseArm64XFormsDotNet` is ~62KB larger.

Update `tests/api-compatibility/api-compat-exclude-attributes.txt`
so that `T:System.Runtime.CompilerServices.CompilerGeneratedAttribute`
is ignored.  `[CompilerGeneratedAttribute]` is emitted as part of
C# 3 "auto-props":

	public T Property { get; set; }

Converting this into a "real" property:

	T value;
	public T Property {
	    get => value;
	    set => this.value = value;
	}

results in the `_CheckApiCompatibility` target complaining about an
API break.  We don't care; ignore `[CompilerGeneratedAttribute]`.

Remove `$(SelfContained)` property: early on in .NET 5 (yes 5)
development, the Xamarin.Android SDK needed to specify
`$(SelfContained)` by default in order to produce `.apk` files.

After we became a proper workload, setting the value became
unnecessary.  It also didn't actually do anything because
dotnet/sdk overwrote the value.

Starting in dotnet/sdk#18639,
`Microsoft.NET.RuntimeIdentifierInference.targets` is now being
imported *after* a workload, which meant that dotnet/sdk no longer
overwrote our `$(SelfContained)` value, which broke things:

	error NETSDK1031: It is not supported to build or publish a self-contained application without specifying a RuntimeIdentifier. You must either specify a RuntimeIdentifier or set SelfContained to false.

We can simply remove `$(SelfContained)` now, and rely on
dotnet/sdk to set this value.

Co-authored-by: Jonathan Peppers <jonathan.peppers@microsoft.com>
Co-authored-by: Steve Pfister <steve.pfister@microsoft.com>
jonpryor pushed a commit to dotnet/android that referenced this pull request Jul 21, 2021
Fixes: dotnet/java-interop#854

Changes: dotnet/installer@e8b3b6b...808795c

    % git diff --shortstat e8b3b6be...808795cc
     102 files changed, 2218 insertions(+), 2674 deletions(-)

Changes: dotnet/linker@a07cab7...9ecf5bd

    % git diff --shortstat a07cab7b...9ecf5bd2
     81 files changed, 2122 insertions(+), 246 deletions(-)


Changes: dotnet/runtime@02f70d0...8d3afa3

    % git diff --shortstat 02f70d0b...8d3afa3a
     2518 files changed, 122843 insertions(+), 33676 deletions(-)

Changes: dotnet/java-interop@a5ed891...4fb7c14

  * dotnet/java-interop@4fb7c147: [build] set $(DisableImplicitNamespaceImports) by default (#859)
  * dotnet/java-interop@855ecfa3: [generator] Don't generate unexpected NRT types like `void?` (#856)
  * dotnet/java-interop@4a02bc32: Revert "[Xamarin.Android.Tools.Bytecode] hide nested types (#827)" (#855)
  * dotnet/java-interop@95c9b79d: [generator] Avoid 'error (…):' construct in diagnostic messages (#851)
  * dotnet/java-interop@7c4f7db0: [build] Bump to Mono with MSBuild 16.10 (#848)
  * dotnet/java-interop@0227cdae: [generator] Gracefully handle BindingGeneratorException. (#845)
  * dotnet/java-interop@ce1750fd: Add SECURITY.md (#846)

Context: dotnet/runtime#55384
Context: dotnet/sdk#18639

Updates:

  * Microsoft.Dotnet.Sdk.Internal: from 6.0.100-preview.7.21327.2 to 6.0.100-preview.7.21369.5
  * Microsoft.NET.ILLink.Tasks: from 6.0.100-preview.6.21322.1 to 6.0.100-preview.6.21365.1
  * Microsoft.NETCore.App.Ref: from 6.0.0-preview.7.21326.8 to 6.0.0-preview.7.21368.2

dotnet/runtime#55384 broke how .NET 6 interacts with
`AndroidClientHandler`.  Fix this by introducing a new
`Xamarin.Android.Net.AndroidMessageHandler` type for use on .NET 6,
and update the .NET 6 `AndroidClientHandler` to delegate to
`AndroidMessageHandler`.

`AndroidMessageHandler` doesn't exist on Legacy.

Update `.apkdesc` files:

  * `BuildReleaseArm64SimpleDotNet` is ~37KB smaller
  * `BuildReleaseArm64XFormsDotNet` is ~62KB larger.

Update `tests/api-compatibility/api-compat-exclude-attributes.txt`
so that `T:System.Runtime.CompilerServices.CompilerGeneratedAttribute`
is ignored.  `[CompilerGeneratedAttribute]` is emitted as part of
C# 3 "auto-props":

	public T Property { get; set; }

Converting this into a "real" property:

	T value;
	public T Property {
	    get => value;
	    set => this.value = value;
	}

results in the `_CheckApiCompatibility` target complaining about an
API break.  We don't care; ignore `[CompilerGeneratedAttribute]`.

Remove `$(SelfContained)` property: early on in .NET 5 (yes 5)
development, the Xamarin.Android SDK needed to specify
`$(SelfContained)` by default in order to produce `.apk` files.

After we became a proper workload, setting the value became
unnecessary.  It also didn't actually do anything because
dotnet/sdk overwrote the value.

Starting in dotnet/sdk#18639,
`Microsoft.NET.RuntimeIdentifierInference.targets` is now being
imported *after* a workload, which meant that dotnet/sdk no longer
overwrote our `$(SelfContained)` value, which broke things:

	error NETSDK1031: It is not supported to build or publish a self-contained application without specifying a RuntimeIdentifier. You must either specify a RuntimeIdentifier or set SelfContained to false.

We can simply remove `$(SelfContained)` now, and rely on
dotnet/sdk to set this value.

Co-authored-by: Jonathan Peppers <jonathan.peppers@microsoft.com>
Co-authored-by: Steve Pfister <steve.pfister@microsoft.com>
@ghost ghost locked as resolved and limited conversation to collaborators Aug 14, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants