diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs index 45b74472b402cb..7497ac2614e2ff 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs @@ -1142,6 +1142,11 @@ private async ValueTask ReadNextDataFrameAsync(HttpResponseMessage respons switch (frameType) { case Http3FrameType.Data: + // Ignore DATA frames with 0 length. + if (payloadLength == 0) + { + continue; + } _responseDataPayloadRemaining = payloadLength; return true; case Http3FrameType.Headers: diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs index df5b59b500eb37..653e1ead3360aa 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs @@ -380,7 +380,6 @@ public async Task ServerCertificateCustomValidationCallback_Succeeds() [OuterLoop] [ConditionalTheory(nameof(IsMsQuicSupported))] [MemberData(nameof(InteropUris))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/54726")] public async Task Public_Interop_ExactVersion_Success(string uri) { if (UseQuicImplementationProvider == QuicImplementationProviders.Mock) @@ -402,10 +401,36 @@ public async Task Public_Interop_ExactVersion_Success(string uri) Assert.Equal(3, response.Version.Major); } + [OuterLoop] + [ConditionalTheory(nameof(IsMsQuicSupported))] + [MemberData(nameof(InteropUrisWithContent))] + public async Task Public_Interop_ExactVersion_BufferContent_Success(string uri) + { + if (UseQuicImplementationProvider == QuicImplementationProviders.Mock) + { + return; + } + + using HttpClient client = CreateHttpClient(); + using HttpRequestMessage request = new HttpRequestMessage + { + Method = HttpMethod.Get, + RequestUri = new Uri(uri, UriKind.Absolute), + Version = HttpVersion.Version30, + VersionPolicy = HttpVersionPolicy.RequestVersionExact + }; + using HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseContentRead).WaitAsync(TimeSpan.FromSeconds(20)); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(3, response.Version.Major); + + var content = await response.Content.ReadAsStringAsync(); + Assert.NotEmpty(content); + } + [OuterLoop] [ConditionalTheory(nameof(IsMsQuicSupported))] [MemberData(nameof(InteropUris))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/54726")] public async Task Public_Interop_Upgrade_Success(string uri) { if (UseQuicImplementationProvider == QuicImplementationProviders.Mock) @@ -413,7 +438,9 @@ public async Task Public_Interop_Upgrade_Success(string uri) return; } - using HttpClient client = CreateHttpClient(); + // Create the handler manually without passing in useVersion = Http3 to avoid using VersionHttpClientHandler, + // because it overrides VersionPolicy on each request with RequestVersionExact (bypassing Alt-Svc code path completely). + using HttpClient client = CreateHttpClient(CreateHttpClientHandler(quicImplementationProvider: UseQuicImplementationProvider)); // First request uses HTTP/1 or HTTP/2 and receives an Alt-Svc either by header or (with HTTP/2) by frame. @@ -443,7 +470,7 @@ public async Task Public_Interop_Upgrade_Success(string uri) using HttpResponseMessage responseB = await client.SendAsync(requestB).WaitAsync(TimeSpan.FromSeconds(20)); Assert.Equal(HttpStatusCode.OK, responseB.StatusCode); - Assert.NotEqual(3, responseB.Version.Major); + Assert.Equal(3, responseB.Version.Major); } } @@ -754,14 +781,26 @@ private SslApplicationProtocol ExtractMsQuicNegotiatedAlpn(Http3LoopbackConnecti /// /// These are public interop test servers for various QUIC and HTTP/3 implementations, - /// taken from https://github.com/quicwg/base-drafts/wiki/Implementations + /// taken from https://github.com/quicwg/base-drafts/wiki/Implementations and https://bagder.github.io/HTTP3-test/. /// public static TheoryData InteropUris() => new TheoryData { - { "https://quic.rocks:4433/" }, // Chromium - { "https://http3-test.litespeedtech.com:4433/" }, // LiteSpeed - { "https://quic.tech:8443/" } // Cloudflare + { "https://www.litespeedtech.com/" }, // LiteSpeed + { "https://quic.tech:8443/" }, // Cloudflare + { "https://quic.aiortc.org:443/" }, // aioquic + { "https://h2o.examp1e.net/" } // h2o/quicly + }; + + /// + /// These are public interop test servers for various QUIC and HTTP/3 implementations, + /// taken from https://github.com/quicwg/base-drafts/wiki/Implementations and https://bagder.github.io/HTTP3-test/. + /// + public static TheoryData InteropUrisWithContent() => + new TheoryData + { + { "https://cloudflare-quic.com/" }, // Cloudflare with content + { "https://pgjones.dev/" }, // aioquic with content }; } }