diff --git a/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs b/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs index 4840ad7ab4459..b2c5d7015f25f 100644 --- a/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs +++ b/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs @@ -24,7 +24,6 @@ public class Http2LoopbackConnection : GenericLoopbackConnection private Stream _connectionStream; private TaskCompletionSource _ignoredSettingsAckPromise; private bool _ignoreWindowUpdates; - private TaskCompletionSource _expectPingFrame; private bool _transparentPingResponse; private readonly TimeSpan _timeout; private int _lastStreamId; @@ -201,7 +200,7 @@ public async Task ReadFrameAsync(CancellationToken cancellationToken) return await ReadFrameAsync(cancellationToken).ConfigureAwait(false); } - if (header.Type == FrameType.Ping && (_expectPingFrame != null || _transparentPingResponse)) + if (header.Type == FrameType.Ping && _transparentPingResponse) { PingFrame pingFrame = PingFrame.ReadFrom(header, data); @@ -237,13 +236,7 @@ public async Task ReadFrameAsync(CancellationToken cancellationToken) private async Task TryProcessExpectedPingFrameAsync(PingFrame pingFrame) { - if (_expectPingFrame != null) - { - _expectPingFrame.SetResult(pingFrame); - _expectPingFrame = null; - return true; - } - else if (_transparentPingResponse && !pingFrame.AckFlag) + if (_transparentPingResponse && !pingFrame.AckFlag) { try { @@ -293,22 +286,6 @@ public void IgnoreWindowUpdates() _ignoreWindowUpdates = true; } - // Set up loopback server to expect a PING frame among other frames. - // Once PING frame is read in ReadFrameAsync, the returned task is completed. - // The returned task is canceled in ReadPingAsync if no PING frame has been read so far. - // Does not work when Http2Options.EnableTransparentPingResponse == true - public Task ExpectPingFrameAsync() - { - if (_transparentPingResponse) - { - throw new InvalidOperationException( - $"{nameof(Http2LoopbackConnection)}.{nameof(ExpectPingFrameAsync)} can not be used when transparent PING response is enabled."); - } - - _expectPingFrame ??= new TaskCompletionSource(); - return _expectPingFrame.Task; - } - public async Task ReadRstStreamAsync(int streamId) { Frame frame = await ReadFrameAsync(_timeout); @@ -772,9 +749,6 @@ public async Task PingPong() public async Task ReadPingAsync(TimeSpan timeout) { - _expectPingFrame?.TrySetCanceled(); - _expectPingFrame = null; - Frame frame = await ReadFrameAsync(timeout).ConfigureAwait(false); Assert.NotNull(frame); Assert.Equal(FrameType.Ping, frame.Type); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index ce524c4841f76..b6a33b2f28012 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -1206,7 +1206,7 @@ private Task SendSettingsAckAsync() => private Task SendPingAsync(long pingContent, bool isAck = false) => PerformWriteAsync(FrameHeader.Size + FrameHeader.PingLength, (thisRef: this, pingContent, isAck), static (state, writeBuffer) => { - if (NetEventSource.Log.IsEnabled()) state.thisRef.Trace("Started writing."); + if (NetEventSource.Log.IsEnabled()) state.thisRef.Trace($"Started writing. {nameof(pingContent)}={state.pingContent}"); Debug.Assert(sizeof(long) == FrameHeader.PingLength); diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs index a66c33bfde89f..fc94b54bba975 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs @@ -1783,149 +1783,6 @@ public async Task Http2_SendOverConnectionWindowSizeWithoutExplicitFlush_ClientS } } - public static IEnumerable KeepAliveTestDataSource() - { - yield return new object[] { Timeout.InfiniteTimeSpan, HttpKeepAlivePingPolicy.Always, false }; - yield return new object[] { TimeSpan.FromSeconds(1), HttpKeepAlivePingPolicy.WithActiveRequests, false }; - yield return new object[] { TimeSpan.FromSeconds(1), HttpKeepAlivePingPolicy.Always, false }; - yield return new object[] { TimeSpan.FromSeconds(1), HttpKeepAlivePingPolicy.WithActiveRequests, true }; - } - - [OuterLoop("Significant delay.")] - [MemberData(nameof(KeepAliveTestDataSource))] - [ConditionalTheory(nameof(SupportsAlpn))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/41929")] - public void Http2_PingKeepAlive(TimeSpan keepAlivePingDelay, HttpKeepAlivePingPolicy keepAlivePingPolicy, bool expectRequestFail) - { - RemoteExecutor.Invoke(RunTest, keepAlivePingDelay.Ticks.ToString(), keepAlivePingPolicy.ToString(), expectRequestFail.ToString()).Dispose(); - - static async Task RunTest(string keepAlivePingDelayString, string keepAlivePingPolicyString, string expectRequestFailString) - { - // We should refactor this test so it can react to RTT PINGs. - // For now, avoid interference by disabling them: - AppContext.SetSwitch("System.Net.SocketsHttpHandler.Http2FlowControl.DisableDynamicWindowSizing", true); - - bool expectRequestFail = bool.Parse(expectRequestFailString); - TimeSpan keepAlivePingDelay = TimeSpan.FromTicks(long.Parse(keepAlivePingDelayString)); - HttpKeepAlivePingPolicy keepAlivePingPolicy = Enum.Parse(keepAlivePingPolicyString); - - TimeSpan pingTimeout = TimeSpan.FromSeconds(5); - // Simulate failure by delaying the pong, otherwise send it immediately. - TimeSpan pongDelay = expectRequestFail ? pingTimeout * 2 : TimeSpan.Zero; - // Pings are send only if KeepAlivePingDelay is not infinite. - bool expectStreamPing = keepAlivePingDelay != Timeout.InfiniteTimeSpan; - // Pings (regardless ongoing communication) are send only if sending is on and policy is set to always. - bool expectPingWithoutStream = expectStreamPing && keepAlivePingPolicy == HttpKeepAlivePingPolicy.Always; - - TaskCompletionSource serverFinished = new TaskCompletionSource(); - - await Http2LoopbackServer.CreateClientAndServerAsync( - async uri => - { - SocketsHttpHandler handler = new SocketsHttpHandler() - { - KeepAlivePingTimeout = pingTimeout, - KeepAlivePingPolicy = keepAlivePingPolicy, - KeepAlivePingDelay = keepAlivePingDelay - }; - handler.SslOptions.RemoteCertificateValidationCallback = delegate { return true; }; - - using HttpClient client = new HttpClient(handler); - client.DefaultRequestVersion = HttpVersion.Version20; - - // Warmup request to create connection. - await client.GetStringAsync(uri); - // Request under the test scope. - if (expectRequestFail) - { - await Assert.ThrowsAsync(() => client.GetStringAsync(uri)); - // As stream is closed we don't want to continue with sending data. - return; - } - else - { - await client.GetStringAsync(uri); - } - - // Let connection live until server finishes. - try - { - await serverFinished.Task.WaitAsync(pingTimeout * 3); - } - catch (TimeoutException) { } - }, - async server => - { - using Http2LoopbackConnection connection = await server.EstablishConnectionAsync(); - - Task receivePingTask = expectStreamPing ? connection.ExpectPingFrameAsync() : null; - - // Warmup the connection. - int streamId1 = await connection.ReadRequestHeaderAsync(); - await connection.SendDefaultResponseAsync(streamId1); - - // Request under the test scope. - int streamId2 = await connection.ReadRequestHeaderAsync(); - - // Test ping with active stream. - if (!expectStreamPing) - { - await Assert.ThrowsAsync(() => connection.ReadPingAsync(pingTimeout)); - } - else - { - PingFrame ping; - if (receivePingTask != null && receivePingTask.IsCompleted) - { - ping = await receivePingTask; - } - else - { - ping = await connection.ReadPingAsync(pingTimeout); - } - if (pongDelay > TimeSpan.Zero) - { - await Task.Delay(pongDelay); - } - - await connection.SendPingAckAsync(ping.Data); - } - - // Send response and close the stream. - if (expectRequestFail) - { - await Assert.ThrowsAsync(() => connection.SendDefaultResponseAsync(streamId2)); - // As stream is closed we don't want to continue with sending data. - return; - } - await connection.SendDefaultResponseAsync(streamId2); - // Test ping with no active stream. - if (expectPingWithoutStream) - { - PingFrame ping = await connection.ReadPingAsync(pingTimeout); - await connection.SendPingAckAsync(ping.Data); - } - else - { - // If the pings were recently coming, just give the connection time to clear up streams - // and still accept one stray ping. - if (expectStreamPing) - { - try - { - await connection.ReadPingAsync(pingTimeout); - } - catch (OperationCanceledException) { } // if it failed once, it will fail again - } - await Assert.ThrowsAsync(() => connection.ReadPingAsync(pingTimeout)); - } - serverFinished.SetResult(); - await connection.WaitForClientDisconnectAsync(true); - }, - new Http2Options() { EnableTransparentPingResponse = false }); - } - } - [OuterLoop("Uses Task.Delay")] [ConditionalFact(nameof(SupportsAlpn))] public async Task Http2_MaxConcurrentStreams_LimitEnforced() diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2FlowControl.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2FlowControl.cs index 36bb2c9ade80e..84ea142f933af 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2FlowControl.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2FlowControl.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics; using System.Linq; using System.Net.Test.Common; using System.Threading; @@ -236,7 +235,6 @@ private static async Task TestClientWindowScalingAsync( int nextRemainingBytes = remainingBytes - bytesToSend; bool endStream = nextRemainingBytes == 0; - await writeSemaphore.WaitAsync(); Interlocked.Add(ref credit, -bytesToSend); await connection.SendResponseDataAsync(streamId, responseData, endStream); diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2KeepAlivePing.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2KeepAlivePing.cs new file mode 100644 index 0000000000000..aad7f5d1a873d --- /dev/null +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2KeepAlivePing.cs @@ -0,0 +1,371 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Test.Common; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace System.Net.Http.Functional.Tests +{ + [Collection(nameof(NonParallelTestCollection))] + [ConditionalClass(typeof(SocketsHttpHandler_Http2KeepAlivePing_Test), nameof(IsSupported))] + public sealed class SocketsHttpHandler_Http2KeepAlivePing_Test : HttpClientHandlerTestBase + { + public static readonly bool IsSupported = PlatformDetection.SupportsAlpn && PlatformDetection.IsNotBrowser; + + protected override Version UseVersion => HttpVersion20.Value; + + private int _pingCounter; + private Http2LoopbackConnection _connection; + private SemaphoreSlim _writeSemaphore = new SemaphoreSlim(1); + private Channel _framesChannel = Channel.CreateUnbounded(); + private CancellationTokenSource _incomingFramesCts = new CancellationTokenSource(); + private Task _incomingFramesTask; + private TaskCompletionSource _serverFinished = new TaskCompletionSource(); + private int _sendPingResponse = 1; + + private static Http2Options NoAutoPingResponseHttp2Options => new Http2Options() { EnableTransparentPingResponse = false }; + + private static readonly TimeSpan TestTimeout = TimeSpan.FromSeconds(60); + + public SocketsHttpHandler_Http2KeepAlivePing_Test(ITestOutputHelper output) : base(output) + { + } + + [OuterLoop("Runs long")] + [Fact] + public async Task KeepAlivePingDelay_Infinite_NoKeepAlivePingIsSent() + { + await Http2LoopbackServer.CreateClientAndServerAsync(async uri => + { + SocketsHttpHandler handler = new SocketsHttpHandler() + { + KeepAlivePingTimeout = TimeSpan.FromSeconds(1), + KeepAlivePingPolicy = HttpKeepAlivePingPolicy.Always, + KeepAlivePingDelay = Timeout.InfiniteTimeSpan + }; + handler.SslOptions.RemoteCertificateValidationCallback = delegate { return true; }; + + using HttpClient client = new HttpClient(handler); + client.DefaultRequestVersion = HttpVersion.Version20; + + // Warmup request to create connection: + await client.GetStringAsync(uri); + + // Actual request: + await client.GetStringAsync(uri); + + // Let connection live until server finishes: + await _serverFinished.Task.WaitAsync(TestTimeout); + }, + async server => + { + await EstablishConnectionAsync(server); + + // Warmup the connection. + int streamId1 = await ReadRequestHeaderAsync(); + await GuardConnetionWriteAsync(() => _connection.SendDefaultResponseAsync(streamId1)); + + Interlocked.Exchange(ref _pingCounter, 0); // reset the PING counter + // Request under the test scope. + int streamId2 = await ReadRequestHeaderAsync(); + + // Simulate inactive period: + await Task.Delay(5_000); + + // We may have received one RTT PING in response to HEADERS, but should receive no KeepAlive PING + Assert.True(_pingCounter <= 1); + Interlocked.Exchange(ref _pingCounter, 0); // reset the counter + + // Finish the response: + await GuardConnetionWriteAsync(() => _connection.SendDefaultResponseAsync(streamId2)); + + // Simulate inactive period: + await Task.Delay(5_000); + + // We may have received one RTT PING in response to HEADERS, but should receive no KeepAlive PING + Assert.True(_pingCounter <= 1); + + await TerminateLoopbackConnectionAsync(); + }).WaitAsync(TestTimeout); + } + + [OuterLoop("Runs long")] + [Theory] + [InlineData(HttpKeepAlivePingPolicy.Always)] + [InlineData(HttpKeepAlivePingPolicy.WithActiveRequests)] + public async Task KeepAliveConfigured_KeepAlivePingsAreSentAccordingToPolicy(HttpKeepAlivePingPolicy policy) + { + await Http2LoopbackServer.CreateClientAndServerAsync(async uri => + { + SocketsHttpHandler handler = new SocketsHttpHandler() + { + KeepAlivePingTimeout = TimeSpan.FromSeconds(10), + KeepAlivePingPolicy = policy, + KeepAlivePingDelay = TimeSpan.FromSeconds(1) + }; + handler.SslOptions.RemoteCertificateValidationCallback = delegate { return true; }; + + using HttpClient client = new HttpClient(handler); + client.DefaultRequestVersion = HttpVersion.Version20; + + // Warmup request to create connection: + HttpResponseMessage response0 = await client.GetAsync(uri); + Assert.Equal(HttpStatusCode.OK, response0.StatusCode); + + // Actual request: + HttpResponseMessage response1 = await client.GetAsync(uri); + Assert.Equal(HttpStatusCode.OK, response1.StatusCode); + + // Let connection live until server finishes: + await _serverFinished.Task.WaitAsync(TestTimeout); + }, + async server => + { + await EstablishConnectionAsync(server); + + // Warmup the connection. + int streamId1 = await ReadRequestHeaderAsync(); + await GuardConnetionWriteAsync(() => _connection.SendDefaultResponseAsync(streamId1)); + + // Request under the test scope. + int streamId2 = await ReadRequestHeaderAsync(); + Interlocked.Exchange(ref _pingCounter, 0); // reset the PING counter + + // Simulate inactive period: + await Task.Delay(5_000); + + // We may receive one RTT PING in response to HEADERS. + // Upon that, we expect to receive at least 1 keep alive PING: + Assert.True(_pingCounter > 1); + + // Finish the response: + await GuardConnetionWriteAsync(() => _connection.SendDefaultResponseAsync(streamId2)); + Interlocked.Exchange(ref _pingCounter, 0); // reset the PING counter + + if (policy == HttpKeepAlivePingPolicy.Always) + { + // Simulate inactive period: + await Task.Delay(5_000); + + // We may receive one RTT PING in response to HEADERS. + // Upon that, we expect to receive at least 1 keep alive PING: + Assert.True(_pingCounter > 1); + } + else + { + // We should receive no more KeepAlive PINGs + Assert.True(_pingCounter <= 1); + } + + await TerminateLoopbackConnectionAsync(); + + List unexpectedFrames = new List(); + while (_framesChannel.Reader.Count > 0) + { + Frame unexpectedFrame = await _framesChannel.Reader.ReadAsync(); + unexpectedFrames.Add(unexpectedFrame); + } + + Assert.False(unexpectedFrames.Any(), "Received unexpected frames: \n" + string.Join('\n', unexpectedFrames.Select(f => f.ToString()).ToArray())); + }, NoAutoPingResponseHttp2Options).WaitAsync(TestTimeout); + } + + [OuterLoop("Runs long")] + [Fact] + public async Task KeepAliveConfigured_NoPingResponseDuringActiveStream_RequestShouldFail() + { + await Http2LoopbackServer.CreateClientAndServerAsync(async uri => + { + SocketsHttpHandler handler = new SocketsHttpHandler() + { + KeepAlivePingTimeout = TimeSpan.FromSeconds(1.5), + KeepAlivePingPolicy = HttpKeepAlivePingPolicy.WithActiveRequests, + KeepAlivePingDelay = TimeSpan.FromSeconds(1) + }; + handler.SslOptions.RemoteCertificateValidationCallback = delegate { return true; }; + + using HttpClient client = new HttpClient(handler); + client.DefaultRequestVersion = HttpVersion.Version20; + + // Warmup request to create connection: + HttpResponseMessage response0 = await client.GetAsync(uri); + Assert.Equal(HttpStatusCode.OK, response0.StatusCode); + + // Actual request: + await Assert.ThrowsAsync(() => client.GetAsync(uri)); + + // Let connection live until server finishes: + await _serverFinished.Task; + }, + async server => + { + await EstablishConnectionAsync(server); + + // Warmup the connection. + int streamId1 = await ReadRequestHeaderAsync(); + await GuardConnetionWriteAsync(() => _connection.SendDefaultResponseAsync(streamId1)); + + // Request under the test scope. + int streamId2 = await ReadRequestHeaderAsync(); + + DisablePingResponse(); + + // Simulate inactive period: + await Task.Delay(6_000); + + // Finish the response: + await GuardConnetionWriteAsync(() => _connection.SendDefaultResponseAsync(streamId2)); + + await TerminateLoopbackConnectionAsync(); + }, NoAutoPingResponseHttp2Options).WaitAsync(TestTimeout); + } + + [OuterLoop("Runs long")] + [Fact] + public async Task HttpKeepAlivePingPolicy_Always_NoPingResponseBetweenStreams_SecondRequestShouldFail() + { + await Http2LoopbackServer.CreateClientAndServerAsync(async uri => + { + SocketsHttpHandler handler = new SocketsHttpHandler() + { + KeepAlivePingTimeout = TimeSpan.FromSeconds(1.5), + KeepAlivePingPolicy = HttpKeepAlivePingPolicy.Always, + KeepAlivePingDelay = TimeSpan.FromSeconds(1) + }; + handler.SslOptions.RemoteCertificateValidationCallback = delegate { return true; }; + + using HttpClient client = new HttpClient(handler); + client.DefaultRequestVersion = HttpVersion.Version20; + + // Warmup request to create connection: + HttpResponseMessage response0 = await client.GetAsync(uri); + Assert.Equal(HttpStatusCode.OK, response0.StatusCode); + + // Second request should fail: + await Assert.ThrowsAsync(() => client.GetAsync(uri)); + + // Let connection live until server finishes: + await _serverFinished.Task; + }, + async server => + { + await EstablishConnectionAsync(server); + + // Warmup the connection. + int streamId1 = await ReadRequestHeaderAsync(); + await GuardConnetionWriteAsync(() => _connection.SendDefaultResponseAsync(streamId1)); + + DisablePingResponse(); + + // Simulate inactive period: + await Task.Delay(6_000); + + // Request under the test scope. + int streamId2 = await ReadRequestHeaderAsync(); + + // Finish the response: + await GuardConnetionWriteAsync(() => _connection.SendDefaultResponseAsync(streamId2)); + + await TerminateLoopbackConnectionAsync(); + }, NoAutoPingResponseHttp2Options).WaitAsync(TestTimeout); + } + + private async Task ProcessIncomingFramesAsync(CancellationToken cancellationToken) + { + try + { + while (!cancellationToken.IsCancellationRequested) + { + Frame frame = await _connection.ReadFrameAsync(cancellationToken); + + if (frame is PingFrame pingFrame) + { + if (pingFrame.AckFlag) + { + _output?.WriteLine($"Received unexpected PING ACK ({pingFrame.Data})"); + await _framesChannel.Writer.WriteAsync(frame, cancellationToken); + } + else + { + _output?.WriteLine($"Received PING ({pingFrame.Data})"); + Interlocked.Increment(ref _pingCounter); + + if (_sendPingResponse > 0) + { + await GuardConnetionWriteAsync(() => _connection.SendPingAckAsync(pingFrame.Data, cancellationToken), cancellationToken); + } + } + } + else if (frame is WindowUpdateFrame windowUpdateFrame) + { + _output?.WriteLine($"Received WINDOW_UPDATE"); + } + else if (frame is not null) + { + //_output?.WriteLine($"Received {frame}"); + await _framesChannel.Writer.WriteAsync(frame, cancellationToken); + } + } + } + catch (OperationCanceledException) + { + } + + _output?.WriteLine("ProcessIncomingFramesAsync finished"); + _connection.Dispose(); + } + + private void DisablePingResponse() => Interlocked.Exchange(ref _sendPingResponse, 0); + + private async Task EstablishConnectionAsync(Http2LoopbackServer server) + { + _connection = await server.EstablishConnectionAsync(); + _incomingFramesTask = ProcessIncomingFramesAsync(_incomingFramesCts.Token); + } + + private async Task TerminateLoopbackConnectionAsync() + { + _serverFinished.SetResult(); + _incomingFramesCts.Cancel(); + await _incomingFramesTask; + } + + private async Task GuardConnetionWriteAsync(Func action, CancellationToken cancellationToken = default) + { + await _writeSemaphore.WaitAsync(cancellationToken); + await action(); + _writeSemaphore.Release(); + } + + private async Task ReadRequestHeaderFrameAsync(bool expectEndOfStream = true, CancellationToken cancellationToken = default) + { + // Receive HEADERS frame for request. + Frame frame = await _framesChannel.Reader.ReadAsync(cancellationToken).ConfigureAwait(false); + if (frame == null) + { + throw new IOException("Failed to read Headers frame."); + } + + Assert.Equal(FrameType.Headers, frame.Type); + Assert.Equal(FrameFlags.EndHeaders, frame.Flags & FrameFlags.EndHeaders); + if (expectEndOfStream) + { + Assert.Equal(FrameFlags.EndStream, frame.Flags & FrameFlags.EndStream); + } + return (HeadersFrame)frame; + } + + private async Task ReadRequestHeaderAsync(bool expectEndOfStream = true, CancellationToken cancellationToken = default) + { + HeadersFrame frame = await ReadRequestHeaderFrameAsync(expectEndOfStream, cancellationToken); + return frame.StreamId; + } + } +} diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj b/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj index 667b35e9a8afe..5c17759805721 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj @@ -1,4 +1,4 @@ - + ../../src/Resources/Strings.resx $(DefineConstants);TargetsWindows @@ -135,6 +135,7 @@ +