Send RST for canceled HTTP/2 writes #3007
This commit is contained in:
parent
d3f2ca9c9a
commit
23a4e11261
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue