Send RST for canceled HTTP/2 writes #3007

This commit is contained in:
Chris Ross (ASP.NET) 2018-10-18 14:41:31 -07:00
parent d3f2ca9c9a
commit 23a4e11261
3 changed files with 89 additions and 3 deletions

View File

@ -74,10 +74,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
}
}
// Review: This is called when a CancellationToken fires mid-write. In HTTP/1.x, this aborts the entire connection.
// Should we do that here?
// This is called when a CancellationToken fires mid-write. In HTTP/1.x, this aborts the entire connection.
// For HTTP/2 we abort the stream.
void IHttpOutputAborter.Abort(ConnectionAbortedException abortReason)
{
_stream.ResetAndAbort(abortReason, Http2ErrorCode.INTERNAL_ERROR);
Dispose();
}

View File

@ -427,7 +427,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
ResetAndAbort(abortReason, Http2ErrorCode.INTERNAL_ERROR);
}
private void ResetAndAbort(ConnectionAbortedException abortReason, Http2ErrorCode error)
internal void ResetAndAbort(ConnectionAbortedException abortReason, Http2ErrorCode error)
{
// Future incoming frames will drain for a default grace period to avoid destabilizing the connection.
var states = ApplyCompletionFlag(StreamCompletionFlags.Aborted);

View File

@ -1998,5 +1998,90 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await WaitForConnectionErrorAsync<HPackEncodingException>(ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, Http2ErrorCode.INTERNAL_ERROR,
CoreStrings.HPackErrorNotEnoughBuffer);
}
[Fact]
public async Task WriteAsync_PreCancelledCancellationToken_DoesNotAbort()
{
var headers = new[]
{
new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
// The cancellation is checked at the start of WriteAsync and no application state is changed.
await Assert.ThrowsAsync<OperationCanceledException>(() => context.Response.WriteAsync("hello,", new CancellationToken(true)));
Assert.False(context.Response.HasStarted);
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 55,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[HeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task WriteAsync_CancellationTokenTriggeredDueToFlowControl_SendRST()
{
var cts = new CancellationTokenSource();
var writeStarted = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
var headers = new[]
{
new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
await context.Response.Body.FlushAsync(); // https://github.com/aspnet/KestrelHttpServer/issues/3031
var writeTask = context.Response.WriteAsync("hello,", cts.Token);
writeStarted.SetResult(0);
await Assert.ThrowsAsync<OperationCanceledException>(() => writeTask);
});
_clientSettings.InitialWindowSize = 0;
await SendSettingsAsync();
await ExpectAsync(Http2FrameType.SETTINGS,
withLength: 0,
withFlags: (byte)Http2SettingsFrameFlags.ACK,
withStreamId: 0);
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 37,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await writeStarted.Task;
cts.Cancel();
await WaitForStreamErrorAsync(1, Http2ErrorCode.INTERNAL_ERROR, null);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[HeaderNames.Status]);
}
}
}