Skip to content
This repository was archived by the owner on Dec 18, 2018. It is now read-only.

Commit 23a4e11

Browse files
committed
Send RST for canceled HTTP/2 writes #3007
1 parent d3f2ca9 commit 23a4e11

File tree

3 files changed

+89
-3
lines changed

3 files changed

+89
-3
lines changed

src/Kestrel.Core/Internal/Http2/Http2OutputProducer.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,11 @@ public void Dispose()
7474
}
7575
}
7676

77-
// Review: This is called when a CancellationToken fires mid-write. In HTTP/1.x, this aborts the entire connection.
78-
// Should we do that here?
77+
// This is called when a CancellationToken fires mid-write. In HTTP/1.x, this aborts the entire connection.
78+
// For HTTP/2 we abort the stream.
7979
void IHttpOutputAborter.Abort(ConnectionAbortedException abortReason)
8080
{
81+
_stream.ResetAndAbort(abortReason, Http2ErrorCode.INTERNAL_ERROR);
8182
Dispose();
8283
}
8384

src/Kestrel.Core/Internal/Http2/Http2Stream.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ protected override void ApplicationAbort()
427427
ResetAndAbort(abortReason, Http2ErrorCode.INTERNAL_ERROR);
428428
}
429429

430-
private void ResetAndAbort(ConnectionAbortedException abortReason, Http2ErrorCode error)
430+
internal void ResetAndAbort(ConnectionAbortedException abortReason, Http2ErrorCode error)
431431
{
432432
// Future incoming frames will drain for a default grace period to avoid destabilizing the connection.
433433
var states = ApplyCompletionFlag(StreamCompletionFlags.Aborted);

test/Kestrel.InMemory.FunctionalTests/Http2/Http2StreamTests.cs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1998,5 +1998,90 @@ await ExpectAsync(Http2FrameType.HEADERS,
19981998
await WaitForConnectionErrorAsync<HPackEncodingException>(ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, Http2ErrorCode.INTERNAL_ERROR,
19991999
CoreStrings.HPackErrorNotEnoughBuffer);
20002000
}
2001+
2002+
[Fact]
2003+
public async Task WriteAsync_PreCancelledCancellationToken_DoesNotAbort()
2004+
{
2005+
var headers = new[]
2006+
{
2007+
new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
2008+
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
2009+
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
2010+
};
2011+
await InitializeConnectionAsync(async context =>
2012+
{
2013+
// The cancellation is checked at the start of WriteAsync and no application state is changed.
2014+
await Assert.ThrowsAsync<OperationCanceledException>(() => context.Response.WriteAsync("hello,", new CancellationToken(true)));
2015+
Assert.False(context.Response.HasStarted);
2016+
});
2017+
2018+
await StartStreamAsync(1, headers, endStream: true);
2019+
2020+
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
2021+
withLength: 55,
2022+
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
2023+
withStreamId: 1);
2024+
await ExpectAsync(Http2FrameType.DATA,
2025+
withLength: 0,
2026+
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
2027+
withStreamId: 1);
2028+
2029+
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
2030+
2031+
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
2032+
2033+
Assert.Equal(3, _decodedHeaders.Count);
2034+
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
2035+
Assert.Equal("200", _decodedHeaders[HeaderNames.Status]);
2036+
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
2037+
}
2038+
2039+
[Fact]
2040+
public async Task WriteAsync_CancellationTokenTriggeredDueToFlowControl_SendRST()
2041+
{
2042+
var cts = new CancellationTokenSource();
2043+
var writeStarted = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
2044+
var headers = new[]
2045+
{
2046+
new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
2047+
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
2048+
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
2049+
};
2050+
await InitializeConnectionAsync(async context =>
2051+
{
2052+
await context.Response.Body.FlushAsync(); // https://github.com/aspnet/KestrelHttpServer/issues/3031
2053+
var writeTask = context.Response.WriteAsync("hello,", cts.Token);
2054+
writeStarted.SetResult(0);
2055+
await Assert.ThrowsAsync<OperationCanceledException>(() => writeTask);
2056+
});
2057+
2058+
_clientSettings.InitialWindowSize = 0;
2059+
await SendSettingsAsync();
2060+
await ExpectAsync(Http2FrameType.SETTINGS,
2061+
withLength: 0,
2062+
withFlags: (byte)Http2SettingsFrameFlags.ACK,
2063+
withStreamId: 0);
2064+
2065+
await StartStreamAsync(1, headers, endStream: true);
2066+
2067+
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
2068+
withLength: 37,
2069+
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
2070+
withStreamId: 1);
2071+
2072+
await writeStarted.Task;
2073+
2074+
cts.Cancel();
2075+
2076+
await WaitForStreamErrorAsync(1, Http2ErrorCode.INTERNAL_ERROR, null);
2077+
2078+
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
2079+
2080+
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
2081+
2082+
Assert.Equal(2, _decodedHeaders.Count);
2083+
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
2084+
Assert.Equal("200", _decodedHeaders[HeaderNames.Status]);
2085+
}
20012086
}
20022087
}

0 commit comments

Comments
 (0)