Verify request Content-Length #2733
This commit is contained in:
parent
3cdb73440e
commit
beca0259c2
|
|
@ -557,4 +557,10 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
|
|||
<data name="Http2StreamErrorSchemeMismatch" xml:space="preserve">
|
||||
<value>The request :scheme header '{requestScheme}' does not match the transport scheme '{transportScheme}'.</value>
|
||||
</data>
|
||||
<data name="Http2StreamErrorLessDataThanLength" xml:space="preserve">
|
||||
<value>Less data received than specified in the Content-Length header.</value>
|
||||
</data>
|
||||
<data name="Http2StreamErrorMoreDataThanLength" xml:space="preserve">
|
||||
<value>More data received than specified in the Content-Length header.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -73,6 +73,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
private Http2Stream _currentHeadersStream;
|
||||
private RequestHeaderParsingState _requestHeaderParsingState;
|
||||
private PseudoHeaderFields _parsedPseudoHeaderFields;
|
||||
private Http2HeadersFrameFlags _headerFlags;
|
||||
private bool _isMethodConnect;
|
||||
private int _highestOpenedStreamId;
|
||||
|
||||
|
|
@ -469,12 +470,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
TimeoutControl = this,
|
||||
});
|
||||
|
||||
if ((_incomingFrame.HeadersFlags & Http2HeadersFrameFlags.END_STREAM) == Http2HeadersFrameFlags.END_STREAM)
|
||||
{
|
||||
_currentHeadersStream.OnEndStreamReceived();
|
||||
}
|
||||
|
||||
_currentHeadersStream.Reset();
|
||||
_headerFlags = _incomingFrame.HeadersFlags;
|
||||
|
||||
var endHeaders = (_incomingFrame.HeadersFlags & Http2HeadersFrameFlags.END_HEADERS) == Http2HeadersFrameFlags.END_HEADERS;
|
||||
await DecodeHeadersAsync(application, endHeaders, _incomingFrame.HeadersPayload);
|
||||
|
|
@ -768,6 +765,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorMissingMandatoryPseudoHeaderFields, Http2ErrorCode.PROTOCOL_ERROR);
|
||||
}
|
||||
|
||||
// This must be initialized before we offload the request or else we may start processing request body frames without it.
|
||||
_currentHeadersStream.InputRemaining = _currentHeadersStream.RequestHeaders.ContentLength;
|
||||
|
||||
// This must wait until we've received all of the headers so we can verify the content-length.
|
||||
if ((_headerFlags & Http2HeadersFrameFlags.END_STREAM) == Http2HeadersFrameFlags.END_STREAM)
|
||||
{
|
||||
_currentHeadersStream.OnEndStreamReceived();
|
||||
}
|
||||
|
||||
_streams[_incomingFrame.StreamId] = _currentHeadersStream;
|
||||
// Must not allow app code to block the connection handling loop.
|
||||
ThreadPool.UnsafeQueueUserWorkItem(state =>
|
||||
|
|
@ -788,6 +794,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
_currentHeadersStream = null;
|
||||
_requestHeaderParsingState = RequestHeaderParsingState.Ready;
|
||||
_parsedPseudoHeaderFields = PseudoHeaderFields.None;
|
||||
_headerFlags = Http2HeadersFrameFlags.NONE;
|
||||
_isMethodConnect = false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,8 +48,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
|
||||
public int StreamId => _context.StreamId;
|
||||
|
||||
public long? InputRemaining { get; internal set; }
|
||||
|
||||
public bool RequestBodyStarted { get; private set; }
|
||||
public bool EndStreamReceived => (_completionState & StreamCompletionFlags.EndStreamReceived) == StreamCompletionFlags.EndStreamReceived;
|
||||
private bool IsAborted => (_completionState & StreamCompletionFlags.Aborted) == StreamCompletionFlags.Aborted;
|
||||
|
||||
public override bool IsUpgradableRequest => false;
|
||||
|
||||
|
|
@ -263,8 +266,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
|
||||
public Task OnDataAsync(Http2Frame dataFrame)
|
||||
{
|
||||
// TODO: content-length accounting
|
||||
|
||||
// Since padding isn't buffered, immediately count padding bytes as read for flow control purposes.
|
||||
if (dataFrame.DataHasPadding)
|
||||
{
|
||||
|
|
@ -288,6 +289,25 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
|
||||
_inputFlowControl.Advance(payload.Count);
|
||||
|
||||
if (IsAborted)
|
||||
{
|
||||
// Ignore data frames for aborted streams, but only after counting them for purposes of connection level flow control.
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// This check happens after flow control so that when we throw and abort, the byte count is returned to the connection
|
||||
// level accounting.
|
||||
if (InputRemaining.HasValue)
|
||||
{
|
||||
// https://tools.ietf.org/html/rfc7540#section-8.1.2.6
|
||||
if (payload.Count > InputRemaining.Value)
|
||||
{
|
||||
throw new Http2StreamErrorException(StreamId, CoreStrings.Http2StreamErrorMoreDataThanLength, Http2ErrorCode.PROTOCOL_ERROR);
|
||||
}
|
||||
|
||||
InputRemaining -= payload.Count;
|
||||
}
|
||||
|
||||
RequestBodyPipe.Writer.Write(payload);
|
||||
var flushTask = RequestBodyPipe.Writer.FlushAsync();
|
||||
|
||||
|
|
@ -306,6 +326,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
|
||||
public void OnEndStreamReceived()
|
||||
{
|
||||
if (InputRemaining.HasValue)
|
||||
{
|
||||
// https://tools.ietf.org/html/rfc7540#section-8.1.2.6
|
||||
if (InputRemaining.Value != 0)
|
||||
{
|
||||
throw new Http2StreamErrorException(StreamId, CoreStrings.Http2StreamErrorLessDataThanLength, Http2ErrorCode.PROTOCOL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
TryApplyCompletionFlag(StreamCompletionFlags.EndStreamReceived);
|
||||
|
||||
RequestBodyPipe.Writer.Complete();
|
||||
|
|
|
|||
|
|
@ -2058,6 +2058,34 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
internal static string FormatHttp2StreamErrorSchemeMismatch(object requestScheme, object transportScheme)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("Http2StreamErrorSchemeMismatch", "requestScheme", "transportScheme"), requestScheme, transportScheme);
|
||||
|
||||
/// <summary>
|
||||
/// Less data received than specified in the Content-Length header.
|
||||
/// </summary>
|
||||
internal static string Http2StreamErrorLessDataThanLength
|
||||
{
|
||||
get => GetString("Http2StreamErrorLessDataThanLength");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Less data received than specified in the Content-Length header.
|
||||
/// </summary>
|
||||
internal static string FormatHttp2StreamErrorLessDataThanLength()
|
||||
=> GetString("Http2StreamErrorLessDataThanLength");
|
||||
|
||||
/// <summary>
|
||||
/// More data received than specified in the Content-Length header.
|
||||
/// </summary>
|
||||
internal static string Http2StreamErrorMoreDataThanLength
|
||||
{
|
||||
get => GetString("Http2StreamErrorMoreDataThanLength");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// More data received than specified in the Content-Length header.
|
||||
/// </summary>
|
||||
internal static string FormatHttp2StreamErrorMoreDataThanLength()
|
||||
=> GetString("Http2StreamErrorMoreDataThanLength");
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -42,11 +42,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
new KeyValuePair<string, string>("upgrade-insecure-requests", "1"),
|
||||
};
|
||||
|
||||
private readonly MemoryPool<byte> _memoryPool = KestrelMemoryPool.Create();
|
||||
private readonly DuplexPipe.DuplexPipePair _pair;
|
||||
private MemoryPool<byte> _memoryPool = KestrelMemoryPool.Create();
|
||||
private DuplexPipe.DuplexPipePair _pair;
|
||||
private readonly TestApplicationErrorLogger _logger;
|
||||
private readonly Http2ConnectionContext _connectionContext;
|
||||
private readonly Http2Connection _connection;
|
||||
private Http2ConnectionContext _connectionContext;
|
||||
private Http2Connection _connection;
|
||||
private readonly Http2PeerSettings _clientSettings = new Http2PeerSettings();
|
||||
private readonly HPackEncoder _hpackEncoder = new HPackEncoder();
|
||||
private readonly HPackDecoder _hpackDecoder;
|
||||
|
|
@ -68,24 +68,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
public Http2StreamTests()
|
||||
{
|
||||
// Always dispatch test code back to the ThreadPool. This prevents deadlocks caused by continuing
|
||||
// Http2Connection.ProcessRequestsAsync() loop with writer locks acquired. Run product code inline to make
|
||||
// it easier to verify request frames are processed correctly immediately after sending the them.
|
||||
var inputPipeOptions = new PipeOptions(
|
||||
pool: _memoryPool,
|
||||
readerScheduler: PipeScheduler.Inline,
|
||||
writerScheduler: PipeScheduler.ThreadPool,
|
||||
useSynchronizationContext: false
|
||||
);
|
||||
var outputPipeOptions = new PipeOptions(
|
||||
pool: _memoryPool,
|
||||
readerScheduler: PipeScheduler.ThreadPool,
|
||||
writerScheduler: PipeScheduler.Inline,
|
||||
useSynchronizationContext: false
|
||||
);
|
||||
|
||||
_pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions);
|
||||
|
||||
_noopApplication = context => Task.CompletedTask;
|
||||
|
||||
_echoMethod = context =>
|
||||
|
|
@ -178,6 +160,31 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
_logger = new TestApplicationErrorLogger();
|
||||
|
||||
InitializeConnectionFields(KestrelMemoryPool.Create());
|
||||
}
|
||||
|
||||
private void InitializeConnectionFields(MemoryPool<byte> memoryPool)
|
||||
{
|
||||
_memoryPool = memoryPool;
|
||||
|
||||
// Always dispatch test code back to the ThreadPool. This prevents deadlocks caused by continuing
|
||||
// Http2Connection.ProcessRequestsAsync() loop with writer locks acquired. Run product code inline to make
|
||||
// it easier to verify request frames are processed correctly immediately after sending the them.
|
||||
var inputPipeOptions = new PipeOptions(
|
||||
pool: _memoryPool,
|
||||
readerScheduler: PipeScheduler.Inline,
|
||||
writerScheduler: PipeScheduler.ThreadPool,
|
||||
useSynchronizationContext: false
|
||||
);
|
||||
var outputPipeOptions = new PipeOptions(
|
||||
pool: _memoryPool,
|
||||
readerScheduler: PipeScheduler.ThreadPool,
|
||||
writerScheduler: PipeScheduler.Inline,
|
||||
useSynchronizationContext: false
|
||||
);
|
||||
|
||||
_pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions);
|
||||
|
||||
_connectionContext = new Http2ConnectionContext
|
||||
{
|
||||
ConnectionFeatures = new FeatureCollection(),
|
||||
|
|
@ -754,6 +761,308 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ContentLength_Received_SingleDataFrame_Verified()
|
||||
{
|
||||
var headers = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>(HeaderNames.Method, "POST"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
|
||||
};
|
||||
await InitializeConnectionAsync(async context =>
|
||||
{
|
||||
var buffer = new byte[100];
|
||||
var read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
|
||||
Assert.Equal(12, read);
|
||||
});
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: false);
|
||||
await SendDataAsync(1, new byte[12].AsSpan(), 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.HeadersPayload, 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 ContentLength_ReceivedInContinuation_SingleDataFrame_Verified()
|
||||
{
|
||||
await InitializeConnectionAsync(async context =>
|
||||
{
|
||||
var buffer = new byte[100];
|
||||
var read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
|
||||
Assert.Equal(12, read);
|
||||
});
|
||||
|
||||
var headers = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>(HeaderNames.Method, "POST"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||
new KeyValuePair<string, string>("a", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("b", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("c", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("d", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
|
||||
};
|
||||
await StartStreamAsync(1, headers, endStream: false);
|
||||
await SendDataAsync(1, new byte[12].AsSpan(), 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.HeadersPayload, 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 ContentLength_Received_MultipleDataFrame_Verified()
|
||||
{
|
||||
var headers = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>(HeaderNames.Method, "POST"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
|
||||
};
|
||||
await InitializeConnectionAsync(async context =>
|
||||
{
|
||||
var buffer = new byte[100];
|
||||
var read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
|
||||
var total = read;
|
||||
while (read > 0)
|
||||
{
|
||||
read = await context.Request.Body.ReadAsync(buffer, total, buffer.Length - total);
|
||||
total += read;
|
||||
}
|
||||
Assert.Equal(12, total);
|
||||
});
|
||||
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: false);
|
||||
await SendDataAsync(1, new byte[1].AsSpan(), endStream: false);
|
||||
await SendDataAsync(1, new byte[3].AsSpan(), endStream: false);
|
||||
await SendDataAsync(1, new byte[8].AsSpan(), 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.HeadersPayload, 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 ContentLength_Received_NoDataFrames_Reset()
|
||||
{
|
||||
var headers = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>(HeaderNames.Method, "POST"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
|
||||
};
|
||||
await InitializeConnectionAsync(_noopApplication);
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: true);
|
||||
|
||||
await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2StreamErrorLessDataThanLength);
|
||||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ContentLength_ReceivedInContinuation_NoDataFrames_Reset()
|
||||
{
|
||||
var headers = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>(HeaderNames.Method, "POST"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||
new KeyValuePair<string, string>("a", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("b", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("c", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("d", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
|
||||
};
|
||||
await InitializeConnectionAsync(_noopApplication);
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: true);
|
||||
|
||||
await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2StreamErrorLessDataThanLength);
|
||||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ContentLength_Received_SingleDataFrameOverSize_Reset()
|
||||
{
|
||||
var headers = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>(HeaderNames.Method, "POST"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
|
||||
};
|
||||
await InitializeConnectionAsync(async context =>
|
||||
{
|
||||
await Assert.ThrowsAsync<ConnectionAbortedException>(async () =>
|
||||
{
|
||||
var buffer = new byte[100];
|
||||
while (await context.Request.Body.ReadAsync(buffer, 0, buffer.Length) > 0) { }
|
||||
});
|
||||
});
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: false);
|
||||
await SendDataAsync(1, new byte[13].AsSpan(), endStream: true);
|
||||
|
||||
await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2StreamErrorMoreDataThanLength);
|
||||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ContentLength_Received_SingleDataFrameUnderSize_Reset()
|
||||
{
|
||||
// I hate doing this, but it avoids exceptions from MemoryPool.Dipose() in debug mode. The problem is since
|
||||
// the stream's ProcessRequestsAsync loop is never awaited by the connection, it's not really possible to
|
||||
// observe when all the blocks are returned. This can be removed after we implement graceful shutdown.
|
||||
Dispose();
|
||||
InitializeConnectionFields(new DiagnosticMemoryPool(KestrelMemoryPool.CreateSlabMemoryPool(), allowLateReturn: true));
|
||||
|
||||
var headers = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>(HeaderNames.Method, "POST"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
|
||||
};
|
||||
await InitializeConnectionAsync(async context =>
|
||||
{
|
||||
await Assert.ThrowsAsync<ConnectionAbortedException>(async () =>
|
||||
{
|
||||
var buffer = new byte[100];
|
||||
while (await context.Request.Body.ReadAsync(buffer, 0, buffer.Length) > 0) { }
|
||||
});
|
||||
});
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: false);
|
||||
await SendDataAsync(1, new byte[11].AsSpan(), endStream: true);
|
||||
|
||||
await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2StreamErrorLessDataThanLength);
|
||||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ContentLength_Received_MultipleDataFramesOverSize_Reset()
|
||||
{
|
||||
// I hate doing this, but it avoids exceptions from MemoryPool.Dipose() in debug mode. The problem is since
|
||||
// the stream's ProcessRequestsAsync loop is never awaited by the connection, it's not really possible to
|
||||
// observe when all the blocks are returned. This can be removed after we implement graceful shutdown.
|
||||
Dispose();
|
||||
InitializeConnectionFields(new DiagnosticMemoryPool(KestrelMemoryPool.CreateSlabMemoryPool(), allowLateReturn: true));
|
||||
|
||||
var headers = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>(HeaderNames.Method, "POST"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
|
||||
};
|
||||
await InitializeConnectionAsync(async context =>
|
||||
{
|
||||
await Assert.ThrowsAsync<ConnectionAbortedException>(async () =>
|
||||
{
|
||||
var buffer = new byte[100];
|
||||
while (await context.Request.Body.ReadAsync(buffer, 0, buffer.Length) > 0) { }
|
||||
});
|
||||
});
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: false);
|
||||
await SendDataAsync(1, new byte[1].AsSpan(), endStream: false);
|
||||
await SendDataAsync(1, new byte[2].AsSpan(), endStream: false);
|
||||
await SendDataAsync(1, new byte[10].AsSpan(), endStream: false);
|
||||
await SendDataAsync(1, new byte[2].AsSpan(), endStream: true);
|
||||
|
||||
await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2StreamErrorMoreDataThanLength);
|
||||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ContentLength_Received_MultipleDataFramesUnderSize_Reset()
|
||||
{
|
||||
// I hate doing this, but it avoids exceptions from MemoryPool.Dipose() in debug mode. The problem is since
|
||||
// the stream's ProcessRequestsAsync loop is never awaited by the connection, it's not really possible to
|
||||
// observe when all the blocks are returned. This can be removed after we implement graceful shutdown.
|
||||
Dispose();
|
||||
InitializeConnectionFields(new DiagnosticMemoryPool(KestrelMemoryPool.CreateSlabMemoryPool(), allowLateReturn: true));
|
||||
|
||||
var headers = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>(HeaderNames.Method, "POST"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
|
||||
};
|
||||
await InitializeConnectionAsync(async context =>
|
||||
{
|
||||
await Assert.ThrowsAsync<ConnectionAbortedException>(async () =>
|
||||
{
|
||||
var buffer = new byte[100];
|
||||
while (await context.Request.Body.ReadAsync(buffer, 0, buffer.Length) > 0) { }
|
||||
});
|
||||
});
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: false);
|
||||
await SendDataAsync(1, new byte[1].AsSpan(), endStream: false);
|
||||
await SendDataAsync(1, new byte[2].AsSpan(), endStream: true);
|
||||
|
||||
await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2StreamErrorLessDataThanLength);
|
||||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RST_STREAM_Received_AbortsStream()
|
||||
{
|
||||
|
|
@ -1182,8 +1491,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
if (expectedErrorMessage != null)
|
||||
{
|
||||
var message = Assert.Single(_logger.Messages, m => m.Exception is ConnectionAbortedException);
|
||||
Assert.Contains(expectedErrorMessage, message.Exception.Message);
|
||||
Assert.Contains(_logger.Messages, m => m.Exception?.Message.Contains(expectedErrorMessage) ?? false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#if NETCOREAPP2_2
|
||||
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -56,7 +57,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2
|
|||
get
|
||||
{
|
||||
var dataset = new TheoryData<H2SpecTestCase>();
|
||||
var toSkip = new[] { "hpack/4.2/1", "http2/5.1/8", "http2/8.1.2.6/1", "http2/8.1.2.6/2" };
|
||||
var toSkip = new[] { "hpack/4.2/1", "http2/5.1/8" };
|
||||
|
||||
foreach (var testcase in H2SpecCommands.EnumerateTestCases())
|
||||
{
|
||||
|
|
@ -123,9 +124,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2
|
|||
|
||||
private void ConfigureHelloWorld(IApplicationBuilder app)
|
||||
{
|
||||
app.Run(context =>
|
||||
app.Run(async context =>
|
||||
{
|
||||
return context.Request.Body.CopyToAsync(context.Response.Body);
|
||||
// Read the whole request body to check for errors.
|
||||
await context.Request.Body.CopyToAsync(Stream.Null);
|
||||
await context.Response.WriteAsync("Hello World");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ namespace Microsoft.AspNetCore.Testing
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue