Skip to content

[DRAFT] Allow using H/3 with WinHttpHandler #109508

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

Closed
wants to merge 5 commits into from
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,10 @@ internal static partial class WinHttp
public const uint WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL = 133;
public const uint WINHTTP_OPTION_HTTP_PROTOCOL_USED = 134;
public const uint WINHTTP_PROTOCOL_FLAG_HTTP2 = 0x1;
public const uint WINHTTP_PROTOCOL_FLAG_HTTP3 = 0x2;
public const uint WINHTTP_HTTP2_PLUS_CLIENT_CERT_FLAG = 0x1;
public const uint WINHTTP_OPTION_DISABLE_STREAM_QUEUE = 139;
public const uint WINHTTP_OPTION_HTTP_PROTOCOL_REQUIRED = 145;

public const uint WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET = 114;
public const uint WINHTTP_OPTION_WEB_SOCKET_CLOSE_TIMEOUT = 115;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Security.Authentication;
using System.Threading.Tasks;
using Microsoft.DotNet.XUnitExtensions;
using TestUtilities;
using Xunit;
using Xunit.Abstractions;

Expand Down Expand Up @@ -48,13 +49,14 @@ public void SingletonReturnsTrue()
[InlineData(SslProtocols.None, true)]
public async Task SetDelegate_ConnectionSucceeds(SslProtocols acceptedProtocol, bool requestOnlyThisProtocol)
{
using TestEventListener listener = new TestEventListener(_output, TestEventListener.NetworkingEvents);
#pragma warning disable SYSLIB0039 // TLS 1.0 and 1.1 are obsolete
// Overriding flag for the same reason we skip tests on Catalina
// On OSX 10.13-10.14 we can override this flag to enable the scenario
requestOnlyThisProtocol |= PlatformDetection.IsOSX && acceptedProtocol == SslProtocols.Tls;
#pragma warning restore SYSLIB0039

using (HttpClientHandler handler = CreateHttpClientHandler(allowAllCertificates: true))
using (HttpClientHandler handler = CreateHttpClientHandler(UseVersion, allowAllCertificates: true))
using (HttpClient client = CreateHttpClient(handler))
{
if (requestOnlyThisProtocol)
Expand All @@ -76,13 +78,13 @@ public async Task SetDelegate_ConnectionSucceeds(SslProtocols acceptedProtocol,
#pragma warning restore SYSLIB0039
}

var options = new LoopbackServer.Options { UseSsl = true, SslProtocols = acceptedProtocol };
await LoopbackServer.CreateServerAsync(async (server, url) =>
var options = new GenericLoopbackOptions { UseSsl = true, SslProtocols = acceptedProtocol };
await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
{
await TestHelper.WhenAllCompletedOrAnyFailed(
server.AcceptConnectionSendResponseAndCloseAsync(),
client.GetAsync(url));
}, options);
}, options: options);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ public async Task PostAsync_CancelDuringRequestContentSend_TaskCanceledQuickly(b
return;
}

if (IsWinHttpHandler && UseVersion >= HttpVersion20.Value)
/*if (IsWinHttpHandler && UseVersion >= HttpVersion20.Value)
{
return;
}
}*/

if (PlatformDetection.IsBrowser && LoopbackServerFactory.Version < HttpVersion20.Value)
{
Expand Down Expand Up @@ -100,10 +100,10 @@ public async Task GetAsync_CancelDuringResponseHeadersReceived_TaskCanceledQuick
return;
}

if (IsWinHttpHandler && UseVersion >= HttpVersion20.Value)
/*if (IsWinHttpHandler && UseVersion >= HttpVersion20.Value)
{
return;
}
}*/

using (HttpClient client = CreateHttpClient())
{
Expand Down Expand Up @@ -208,10 +208,10 @@ public async Task GetAsync_CancelDuringResponseBodyReceived_Unbuffered_TaskCance
return;
}

if (IsWinHttpHandler && UseVersion >= HttpVersion20.Value)
/*if (IsWinHttpHandler && UseVersion >= HttpVersion20.Value)
{
return;
}
}*/

using (HttpClient client = CreateHttpClient())
{
Expand Down Expand Up @@ -276,10 +276,10 @@ await ValidateClientCancellationAsync(async () =>
[SkipOnPlatform(TestPlatforms.Browser, "Browser doesn't have blocking synchronous Stream.ReadByte and so it waits for whole body")]
public async Task GetAsync_CancelPendingRequests_DoesntCancelReadAsyncOnResponseStream(CancellationMode mode, bool copyToAsync)
{
if (IsWinHttpHandler && UseVersion >= HttpVersion20.Value)
/*if (IsWinHttpHandler && UseVersion >= HttpVersion20.Value)
{
return;
}
}*/

using (HttpClient client = CreateHttpClient())
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ public HttpClientHandler_Proxy_Test(ITestOutputHelper output) : base(output) { }
[Fact]
public async Task Dispose_HandlerWithProxy_ProxyNotDisposed()
{
if (IsWinHttpHandler && UseVersion >= HttpVersion20.Value)
/*if (IsWinHttpHandler && UseVersion >= HttpVersion20.Value)
{
return;
}
}*/

var proxy = new TrackDisposalProxy();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ public HttpClientHandler_ServerCertificates_Test(ITestOutputHelper output) : bas
[Fact]
public void Ctor_ExpectedDefaultValues()
{
if (IsWinHttpHandler && UseVersion >= HttpVersion20.Value)
/*if (IsWinHttpHandler && UseVersion >= HttpVersion20.Value)
{
return;
}
}*/

using (HttpClientHandler handler = CreateHttpClientHandler())
{
Expand All @@ -55,10 +55,10 @@ public void Ctor_ExpectedDefaultValues()
[Fact]
public void ServerCertificateCustomValidationCallback_SetGet_Roundtrips()
{
if (IsWinHttpHandler && UseVersion >= HttpVersion20.Value)
/*if (IsWinHttpHandler && UseVersion >= HttpVersion20.Value)
{
return;
}
}*/

using (HttpClientHandler handler = CreateHttpClientHandler())
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -903,17 +903,7 @@ private async Task StartRequestAsync(WinHttpRequestState state)
ThrowOnInvalidHandle(connectHandle, nameof(Interop.WinHttp.WinHttpConnect));
connectHandle.SetParentHandle(_sessionHandle);

// Try to use the requested version if a known/supported version was explicitly requested.
// Otherwise, we simply use winhttp's default.
string? httpVersion = null;
if (state.RequestMessage.Version == HttpVersion.Version10)
{
httpVersion = "HTTP/1.0";
}
else if (state.RequestMessage.Version == HttpVersion.Version11)
{
httpVersion = "HTTP/1.1";
}
string httpVersion = $"HTTP/{state.RequestMessage.Version.Major}.{state.RequestMessage.Version.Minor}";
Copy link
Member

Choose a reason for hiding this comment

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

note that this change causes a (minor) allocation of the string per each request.

Copy link
Member

@liveans liveans Jan 27, 2025

Choose a reason for hiding this comment

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

Thanks for reminding this! This PR contains some experiments for the CI machines atm, will get back to this once my plate is not full anymore


OpenRequestHandle(state, connectHandle, httpVersion, out WinHttpChunkMode chunkedModeForSend, out SafeWinHttpHandle requestHandle);
state.RequestHandle = requestHandle;
Expand Down Expand Up @@ -1271,7 +1261,7 @@ private void SetRequestHandleOptions(WinHttpRequestState state)
SetRequestHandleClientCertificateOptions(state.RequestHandle, state.RequestMessage.RequestUri);
SetRequestHandleCredentialsOptions(state);
SetRequestHandleBufferingOptions(state.RequestHandle);
SetRequestHandleHttp2Options(state.RequestHandle, state.RequestMessage.Version);
SetRequestHandleHttpProtocolOptions(state.RequestHandle, state.RequestMessage.Version);
}

private void SetRequestHandleProxyOptions(WinHttpRequestState state)
Expand Down Expand Up @@ -1536,20 +1526,30 @@ private void SetRequestHandleBufferingOptions(SafeWinHttpHandle requestHandle)
SetWinHttpOption(requestHandle, Interop.WinHttp.WINHTTP_OPTION_MAX_RESPONSE_DRAIN_SIZE, ref optionData);
}

private void SetRequestHandleHttp2Options(SafeWinHttpHandle requestHandle, Version requestVersion)
private void SetRequestHandleHttpProtocolOptions(SafeWinHttpHandle requestHandle, Version requestVersion)
{
Debug.Assert(requestHandle != null);
uint optionData = (requestVersion == HttpVersion20) ? Interop.WinHttp.WINHTTP_PROTOCOL_FLAG_HTTP2 : 0;
uint optionData = (requestVersion == HttpVersion30) ? Interop.WinHttp.WINHTTP_PROTOCOL_FLAG_HTTP3 :
(requestVersion == HttpVersion20) ? Interop.WinHttp.WINHTTP_PROTOCOL_FLAG_HTTP2 : 0;
if (Interop.WinHttp.WinHttpSetOption(
requestHandle,
Interop.WinHttp.WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL,
ref optionData))
{
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"HTTP/2 option supported, setting to {optionData}");
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"HTTP/{requestVersion.Major} option supported, setting to {optionData}");
}
else
{
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, "HTTP/2 option not supported");
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"HTTP/{requestVersion.Major} option not supported");
}

uint protocolRequired = 1;
if (optionData != 0 && !Interop.WinHttp.WinHttpSetOption(
requestHandle,
Interop.WinHttp.WINHTTP_OPTION_HTTP_PROTOCOL_REQUIRED,
ref protocolRequired))
{
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, "HTTP protocol required option not supported");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ protected static LoopbackServerFactory GetFactoryForVersion(Version useVersion)
{
return useVersion.Major switch
{
#if !NETFRAMEWORK
3 => Http3LoopbackServerFactory.Singleton,
#endif
2 => Http2LoopbackServerFactory.Singleton,
_ => Http11LoopbackServerFactory.Singleton
};
Expand Down
Loading
Loading