Skip to content

Commit 7afd279

Browse files
author
Cesar Blum Silveira
authored
Don't pause and resume read timing on upgrade requests (#1904).
1 parent f67a105 commit 7afd279

File tree

3 files changed

+112
-2
lines changed

3 files changed

+112
-2
lines changed

src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/MessageBody.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Microsoft.AspNetCore.Http;
99
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
1010
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
11+
using Microsoft.Extensions.Logging;
1112

1213
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
1314
{
@@ -92,14 +93,14 @@ private async Task PumpAsync()
9293
{
9394
// Backpressure, stop controlling incoming data rate until data is read.
9495
backpressure = true;
95-
_context.TimeoutControl.PauseTimingReads();
96+
TryPauseTimingReads();
9697
}
9798

9899
await writeAwaitable;
99100

100101
if (backpressure)
101102
{
102-
_context.TimeoutControl.ResumeTimingReads();
103+
TryResumeTimingReads();
103104
}
104105

105106
if (done)
@@ -273,6 +274,22 @@ private void TryStartTimingReads()
273274
}
274275
}
275276

277+
private void TryPauseTimingReads()
278+
{
279+
if (!RequestUpgrade)
280+
{
281+
_context.TimeoutControl.PauseTimingReads();
282+
}
283+
}
284+
285+
private void TryResumeTimingReads()
286+
{
287+
if (!RequestUpgrade)
288+
{
289+
_context.TimeoutControl.ResumeTimingReads();
290+
}
291+
}
292+
276293
private void TryStopTimingReads()
277294
{
278295
if (!RequestUpgrade)

test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/MessageBodyTests.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,29 @@ public async Task LogsWhenStopsReadingRequestBody()
648648
}
649649
}
650650

651+
[Fact]
652+
public async Task PausesAndResumesRequestBodyTimeoutOnBackpressure()
653+
{
654+
using (var input = new TestInput())
655+
{
656+
var mockTimeoutControl = new Mock<ITimeoutControl>();
657+
input.FrameContext.TimeoutControl = mockTimeoutControl.Object;
658+
659+
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "12" }, input.Frame);
660+
661+
// Add some input and read it to start PumpAsync
662+
input.Add("hello,");
663+
Assert.Equal(6, await body.ReadAsync(new ArraySegment<byte>(new byte[6])));
664+
665+
input.Add(" world");
666+
Assert.Equal(6, await body.ReadAsync(new ArraySegment<byte>(new byte[6])));
667+
668+
// Due to the limits set on Frame.RequestBodyPipe, backpressure should be triggered on every write to that pipe.
669+
mockTimeoutControl.Verify(timeoutControl => timeoutControl.PauseTimingReads(), Times.Exactly(2));
670+
mockTimeoutControl.Verify(timeoutControl => timeoutControl.ResumeTimingReads(), Times.Exactly(2));
671+
}
672+
}
673+
651674
[Fact]
652675
public async Task OnlyEnforcesRequestBodyTimeoutAfterSending100Continue()
653676
{
@@ -701,6 +724,12 @@ public async Task DoesNotEnforceRequestBodyTimeoutOnUpgradeRequests()
701724

702725
mockTimeoutControl.Verify(timeoutControl => timeoutControl.StartTimingReads(), Times.Never);
703726
mockTimeoutControl.Verify(timeoutControl => timeoutControl.StopTimingReads(), Times.Never);
727+
728+
// Due to the limits set on Frame.RequestBodyPipe, backpressure should be triggered on every
729+
// write to that pipe. Verify that read timing pause and resume are not called on upgrade
730+
// requests.
731+
mockTimeoutControl.Verify(timeoutControl => timeoutControl.PauseTimingReads(), Times.Never);
732+
mockTimeoutControl.Verify(timeoutControl => timeoutControl.ResumeTimingReads(), Times.Never);
704733
}
705734
}
706735

test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/RequestTests.cs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
using Microsoft.AspNetCore.Http;
1818
using Microsoft.AspNetCore.Http.Features;
1919
using Microsoft.AspNetCore.Server.Kestrel.Core;
20+
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
2021
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
22+
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
2123
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions;
2224
using Microsoft.AspNetCore.Testing;
2325
using Microsoft.AspNetCore.Testing.xunit;
@@ -1416,6 +1418,68 @@ await connection.ReceiveForcedEnd(
14161418
}
14171419
}
14181420

1421+
[Fact]
1422+
public async Task DoesNotEnforceRequestBodyMinimumDataRateOnUpgradedRequest()
1423+
{
1424+
var appEvent = new ManualResetEventSlim();
1425+
var delayEvent = new ManualResetEventSlim();
1426+
var serviceContext = new TestServiceContext
1427+
{
1428+
SystemClock = new SystemClock()
1429+
};
1430+
1431+
using (var server = new TestServer(async context =>
1432+
{
1433+
context.Features.Get<IHttpRequestBodyMinimumDataRateFeature>().MinimumDataRate = new MinimumDataRate(rate: double.MaxValue, gracePeriod: TimeSpan.Zero);
1434+
1435+
using (var stream = await context.Features.Get<IHttpUpgradeFeature>().UpgradeAsync())
1436+
{
1437+
appEvent.Set();
1438+
1439+
// Read once to go through one set of TryPauseTimingReads()/TryResumeTimingReads() calls
1440+
await stream.ReadAsync(new byte[1], 0, 1);
1441+
1442+
delayEvent.Wait();
1443+
1444+
// Read again to check that the connection is still alive
1445+
await stream.ReadAsync(new byte[1], 0, 1);
1446+
1447+
// Send a response to distinguish from the timeout case where the 101 is still received, but without any content
1448+
var response = Encoding.ASCII.GetBytes("hello");
1449+
await stream.WriteAsync(response, 0, response.Length);
1450+
}
1451+
}, serviceContext))
1452+
{
1453+
using (var connection = server.CreateConnection())
1454+
{
1455+
await connection.Send(
1456+
"GET / HTTP/1.1",
1457+
"Host:",
1458+
"Connection: upgrade",
1459+
"",
1460+
"a");
1461+
1462+
Assert.True(appEvent.Wait(TimeSpan.FromSeconds(10)));
1463+
1464+
await Task.Delay(TimeSpan.FromSeconds(5));
1465+
1466+
delayEvent.Set();
1467+
1468+
await connection.Send("b");
1469+
1470+
await connection.Receive(
1471+
"HTTP/1.1 101 Switching Protocols",
1472+
"Connection: Upgrade",
1473+
"");
1474+
await connection.ReceiveStartsWith(
1475+
$"Date: ");
1476+
await connection.ReceiveForcedEnd(
1477+
"",
1478+
"hello");
1479+
}
1480+
}
1481+
}
1482+
14191483
private async Task TestRemoteIPAddress(string registerAddress, string requestAddress, string expectAddress)
14201484
{
14211485
var builder = new WebHostBuilder()

0 commit comments

Comments
 (0)