Limit concurrent HTTP/2 Streams per connection #2815
This commit is contained in:
parent
43398482a5
commit
0c2923135b
|
|
@ -566,4 +566,10 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
|
||||||
<data name="Http2StreamErrorAfterHeaders" xml:space="preserve">
|
<data name="Http2StreamErrorAfterHeaders" xml:space="preserve">
|
||||||
<value>An error occured after the response headers were sent, a reset is being sent.</value>
|
<value>An error occured after the response headers were sent, a reset is being sent.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Http2ErrorMaxStreams" xml:space="preserve">
|
||||||
|
<value>A new stream was refused because this connection has reached its stream limit.</value>
|
||||||
|
</data>
|
||||||
|
<data name="GreaterThanZeroRequired" xml:space="preserve">
|
||||||
|
<value>A value greater than zero is required.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Limits only applicable to HTTP/2 connections.
|
||||||
|
/// </summary>
|
||||||
|
public class Http2Limits
|
||||||
|
{
|
||||||
|
private int _maxStreamsPerConnection = 100;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Limits the number of concurrent request streams per HTTP/2 connection. Excess streams will be refused.
|
||||||
|
/// <para>
|
||||||
|
/// Defaults to 100
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
public int MaxStreamsPerConnection
|
||||||
|
{
|
||||||
|
get => _maxStreamsPerConnection;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value), value, CoreStrings.GreaterThanZeroRequired);
|
||||||
|
}
|
||||||
|
_maxStreamsPerConnection = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -88,6 +88,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
_context = context;
|
_context = context;
|
||||||
_frameWriter = new Http2FrameWriter(context.Transport.Output, context.Application.Input, _outputFlowControl, this, context.ConnectionId, context.ServiceContext.Log);
|
_frameWriter = new Http2FrameWriter(context.Transport.Output, context.Application.Input, _outputFlowControl, this, context.ConnectionId, context.ServiceContext.Log);
|
||||||
_hpackDecoder = new HPackDecoder((int)_serverSettings.HeaderTableSize);
|
_hpackDecoder = new HPackDecoder((int)_serverSettings.HeaderTableSize);
|
||||||
|
_serverSettings.MaxConcurrentStreams = (uint)context.ServiceContext.ServerOptions.Limits.Http2.MaxStreamsPerConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ConnectionId => _context.ConnectionId;
|
public string ConnectionId => _context.ConnectionId;
|
||||||
|
|
@ -98,6 +99,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
|
|
||||||
public IFeatureCollection ConnectionFeatures => _context.ConnectionFeatures;
|
public IFeatureCollection ConnectionFeatures => _context.ConnectionFeatures;
|
||||||
|
|
||||||
|
internal Http2PeerSettings ServerSettings => _serverSettings;
|
||||||
|
|
||||||
public void OnInputOrOutputCompleted()
|
public void OnInputOrOutputCompleted()
|
||||||
{
|
{
|
||||||
lock (_stateLock)
|
lock (_stateLock)
|
||||||
|
|
@ -172,7 +175,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
|
|
||||||
if (_state != Http2ConnectionState.Closed)
|
if (_state != Http2ConnectionState.Closed)
|
||||||
{
|
{
|
||||||
await _frameWriter.WriteSettingsAsync(_serverSettings);
|
await _frameWriter.WriteSettingsAsync(_serverSettings.GetNonProtocolDefaults());
|
||||||
}
|
}
|
||||||
|
|
||||||
while (_state != Http2ConnectionState.Closed)
|
while (_state != Http2ConnectionState.Closed)
|
||||||
|
|
@ -617,10 +620,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// ParseFrame will not parse an InitialWindowSize > int.MaxValue.
|
// int.MaxValue is the largest allowed windows size.
|
||||||
var previousInitialWindowSize = (int)_clientSettings.InitialWindowSize;
|
var previousInitialWindowSize = (int)_clientSettings.InitialWindowSize;
|
||||||
|
|
||||||
_clientSettings.ParseFrame(_incomingFrame);
|
_clientSettings.Update(_incomingFrame.GetSettings());
|
||||||
|
|
||||||
var ackTask = _frameWriter.WriteSettingsAckAsync(); // Ack before we update the windows, they could send data immediately.
|
var ackTask = _frameWriter.WriteSettingsAckAsync(); // Ack before we update the windows, they could send data immediately.
|
||||||
|
|
||||||
|
|
@ -838,6 +841,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorMissingMandatoryPseudoHeaderFields, Http2ErrorCode.PROTOCOL_ERROR);
|
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorMissingMandatoryPseudoHeaderFields, Http2ErrorCode.PROTOCOL_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_streams.Count >= _serverSettings.MaxConcurrentStreams)
|
||||||
|
{
|
||||||
|
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorMaxStreams, Http2ErrorCode.REFUSED_STREAM);
|
||||||
|
}
|
||||||
|
|
||||||
// This must be initialized before we offload the request or else we may start processing request body frames without it.
|
// 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;
|
_currentHeadersStream.InputRemaining = _currentHeadersStream.RequestHeaders.ContentLength;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,67 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System.Buffers.Binary;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
{
|
{
|
||||||
public partial class Http2Frame
|
public partial class Http2Frame
|
||||||
{
|
{
|
||||||
|
private const int SettingSize = 6; // 2 bytes for the id, 4 bytes for the value.
|
||||||
|
|
||||||
public Http2SettingsFrameFlags SettingsFlags
|
public Http2SettingsFrameFlags SettingsFlags
|
||||||
{
|
{
|
||||||
get => (Http2SettingsFrameFlags)Flags;
|
get => (Http2SettingsFrameFlags)Flags;
|
||||||
set => Flags = (byte)value;
|
set => Flags = (byte)value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PrepareSettings(Http2SettingsFrameFlags flags, Http2PeerSettings settings = null)
|
public int SettingsCount
|
||||||
{
|
{
|
||||||
var settingCount = settings?.Count() ?? 0;
|
get => Length / SettingSize;
|
||||||
|
set => Length = value * SettingSize;
|
||||||
|
}
|
||||||
|
|
||||||
Length = 6 * settingCount;
|
public IList<Http2PeerSetting> GetSettings()
|
||||||
|
{
|
||||||
|
var settings = new Http2PeerSetting[SettingsCount];
|
||||||
|
for (int i = 0; i < settings.Length; i++)
|
||||||
|
{
|
||||||
|
settings[i] = GetSetting(i);
|
||||||
|
}
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Http2PeerSetting GetSetting(int index)
|
||||||
|
{
|
||||||
|
var offset = index * SettingSize;
|
||||||
|
var payload = Payload.Slice(offset);
|
||||||
|
var id = (Http2SettingsParameter)BinaryPrimitives.ReadUInt16BigEndian(payload);
|
||||||
|
var value = BinaryPrimitives.ReadUInt32BigEndian(payload.Slice(2));
|
||||||
|
|
||||||
|
return new Http2PeerSetting(id, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PrepareSettings(Http2SettingsFrameFlags flags, IList<Http2PeerSetting> settings = null)
|
||||||
|
{
|
||||||
|
var settingCount = settings?.Count ?? 0;
|
||||||
|
SettingsCount = settingCount;
|
||||||
Type = Http2FrameType.SETTINGS;
|
Type = Http2FrameType.SETTINGS;
|
||||||
SettingsFlags = flags;
|
SettingsFlags = flags;
|
||||||
StreamId = 0;
|
StreamId = 0;
|
||||||
|
for (int i = 0; i < settingCount; i++)
|
||||||
if (settings != null)
|
|
||||||
{
|
{
|
||||||
Span<byte> payload = Payload;
|
SetSetting(i, settings[i]);
|
||||||
foreach (var setting in settings)
|
|
||||||
{
|
|
||||||
payload[0] = (byte)((ushort)setting.Parameter >> 8);
|
|
||||||
payload[1] = (byte)(ushort)setting.Parameter;
|
|
||||||
payload[2] = (byte)(setting.Value >> 24);
|
|
||||||
payload[3] = (byte)(setting.Value >> 16);
|
|
||||||
payload[4] = (byte)(setting.Value >> 8);
|
|
||||||
payload[5] = (byte)setting.Value;
|
|
||||||
payload = payload.Slice(6);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SetSetting(int index, Http2PeerSetting setting)
|
||||||
|
{
|
||||||
|
var offset = index * SettingSize;
|
||||||
|
var payload = Payload.Slice(offset);
|
||||||
|
BinaryPrimitives.WriteUInt16BigEndian(payload, (ushort)setting.Parameter);
|
||||||
|
BinaryPrimitives.WriteUInt32BigEndian(payload.Slice(2), setting.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -270,12 +270,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task WriteSettingsAsync(Http2PeerSettings settings)
|
public Task WriteSettingsAsync(IList<Http2PeerSetting> settings)
|
||||||
{
|
{
|
||||||
lock (_writeLock)
|
lock (_writeLock)
|
||||||
{
|
{
|
||||||
// TODO: actually send settings
|
_outgoingFrame.PrepareSettings(Http2SettingsFrameFlags.NONE, settings);
|
||||||
_outgoingFrame.PrepareSettings(Http2SettingsFrameFlags.NONE);
|
|
||||||
return WriteFrameUnsynchronizedAsync();
|
return WriteFrameUnsynchronizedAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
{
|
{
|
||||||
public class Http2PeerSettings : IEnumerable<Http2PeerSetting>
|
public class Http2PeerSettings
|
||||||
{
|
{
|
||||||
|
// Note these are protocol defaults, not Kestrel defaults.
|
||||||
public const uint DefaultHeaderTableSize = 4096;
|
public const uint DefaultHeaderTableSize = 4096;
|
||||||
public const bool DefaultEnablePush = true;
|
public const bool DefaultEnablePush = true;
|
||||||
public const uint DefaultMaxConcurrentStreams = uint.MaxValue;
|
public const uint DefaultMaxConcurrentStreams = uint.MaxValue;
|
||||||
|
|
@ -28,20 +28,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
|
|
||||||
public uint MaxHeaderListSize { get; set; } = DefaultMaxHeaderListSize;
|
public uint MaxHeaderListSize { get; set; } = DefaultMaxHeaderListSize;
|
||||||
|
|
||||||
public void ParseFrame(Http2Frame frame)
|
// TODO: Return the diff so we can react
|
||||||
|
public void Update(IList<Http2PeerSetting> settings)
|
||||||
{
|
{
|
||||||
var settingsCount = frame.Length / 6;
|
foreach (var setting in settings)
|
||||||
|
|
||||||
for (var i = 0; i < settingsCount; i++)
|
|
||||||
{
|
{
|
||||||
var offset = i * 6;
|
var value = setting.Value;
|
||||||
var id = (Http2SettingsParameter)((frame.Payload[offset] << 8) | frame.Payload[offset + 1]);
|
|
||||||
var value = (uint)((frame.Payload[offset + 2] << 24)
|
|
||||||
| (frame.Payload[offset + 3] << 16)
|
|
||||||
| (frame.Payload[offset + 4] << 8)
|
|
||||||
| frame.Payload[offset + 5]);
|
|
||||||
|
|
||||||
switch (id)
|
switch (setting.Parameter)
|
||||||
{
|
{
|
||||||
case Http2SettingsParameter.SETTINGS_HEADER_TABLE_SIZE:
|
case Http2SettingsParameter.SETTINGS_HEADER_TABLE_SIZE:
|
||||||
HeaderTableSize = value;
|
HeaderTableSize = value;
|
||||||
|
|
@ -91,16 +85,42 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerator<Http2PeerSetting> GetEnumerator()
|
// Gets the settings that are different from the protocol defaults (as opposed to the server defaults).
|
||||||
|
internal IList<Http2PeerSetting> GetNonProtocolDefaults()
|
||||||
{
|
{
|
||||||
yield return new Http2PeerSetting(Http2SettingsParameter.SETTINGS_HEADER_TABLE_SIZE, HeaderTableSize);
|
var list = new List<Http2PeerSetting>(1);
|
||||||
yield return new Http2PeerSetting(Http2SettingsParameter.SETTINGS_ENABLE_PUSH, EnablePush ? 1u : 0);
|
|
||||||
yield return new Http2PeerSetting(Http2SettingsParameter.SETTINGS_MAX_CONCURRENT_STREAMS, MaxConcurrentStreams);
|
|
||||||
yield return new Http2PeerSetting(Http2SettingsParameter.SETTINGS_INITIAL_WINDOW_SIZE, InitialWindowSize);
|
|
||||||
yield return new Http2PeerSetting(Http2SettingsParameter.SETTINGS_MAX_FRAME_SIZE, MaxFrameSize);
|
|
||||||
yield return new Http2PeerSetting(Http2SettingsParameter.SETTINGS_MAX_HEADER_LIST_SIZE, MaxHeaderListSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
if (HeaderTableSize != DefaultHeaderTableSize)
|
||||||
|
{
|
||||||
|
list.Add(new Http2PeerSetting(Http2SettingsParameter.SETTINGS_HEADER_TABLE_SIZE, HeaderTableSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EnablePush != DefaultEnablePush)
|
||||||
|
{
|
||||||
|
list.Add(new Http2PeerSetting(Http2SettingsParameter.SETTINGS_ENABLE_PUSH, EnablePush ? 1u : 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MaxConcurrentStreams != DefaultMaxConcurrentStreams)
|
||||||
|
{
|
||||||
|
list.Add(new Http2PeerSetting(Http2SettingsParameter.SETTINGS_MAX_CONCURRENT_STREAMS, MaxConcurrentStreams));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (InitialWindowSize != DefaultInitialWindowSize)
|
||||||
|
{
|
||||||
|
list.Add(new Http2PeerSetting(Http2SettingsParameter.SETTINGS_INITIAL_WINDOW_SIZE, InitialWindowSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MaxFrameSize != DefaultMaxFrameSize)
|
||||||
|
{
|
||||||
|
list.Add(new Http2PeerSetting(Http2SettingsParameter.SETTINGS_MAX_FRAME_SIZE, MaxFrameSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MaxHeaderListSize != DefaultMaxHeaderListSize)
|
||||||
|
{
|
||||||
|
list.Add(new Http2PeerSetting(Http2SettingsParameter.SETTINGS_MAX_HEADER_LIST_SIZE, MaxHeaderListSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -251,6 +251,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Limits only applicable to HTTP/2 connections.
|
||||||
|
/// </summary>
|
||||||
|
public Http2Limits Http2 { get; } = new Http2Limits();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the request body minimum data rate in bytes/second.
|
/// Gets or sets the request body minimum data rate in bytes/second.
|
||||||
/// Setting this property to null indicates no minimum data rate should be enforced.
|
/// Setting this property to null indicates no minimum data rate should be enforced.
|
||||||
|
|
|
||||||
|
|
@ -2100,6 +2100,34 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
internal static string FormatHttp2StreamErrorAfterHeaders()
|
internal static string FormatHttp2StreamErrorAfterHeaders()
|
||||||
=> GetString("Http2StreamErrorAfterHeaders");
|
=> GetString("Http2StreamErrorAfterHeaders");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A new stream was refused because this connection has reached its stream limit.
|
||||||
|
/// </summary>
|
||||||
|
internal static string Http2ErrorMaxStreams
|
||||||
|
{
|
||||||
|
get => GetString("Http2ErrorMaxStreams");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A new stream was refused because this connection has reached its stream limit.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatHttp2ErrorMaxStreams()
|
||||||
|
=> GetString("Http2ErrorMaxStreams");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A value greater than zero is required.
|
||||||
|
/// </summary>
|
||||||
|
internal static string GreaterThanZeroRequired
|
||||||
|
{
|
||||||
|
get => GetString("GreaterThanZeroRequired");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A value greater than zero is required.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatGreaterThanZeroRequired()
|
||||||
|
=> GetString("GreaterThanZeroRequired");
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -1439,6 +1439,34 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
|
await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HEADERS_OverMaxStreamLimit_Refused()
|
||||||
|
{
|
||||||
|
_connection.ServerSettings.MaxConcurrentStreams = 1;
|
||||||
|
|
||||||
|
var requestBlocker = new TaskCompletionSource<object>();
|
||||||
|
await InitializeConnectionAsync(context => requestBlocker.Task);
|
||||||
|
|
||||||
|
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
|
||||||
|
|
||||||
|
await StartStreamAsync(3, _browserRequestHeaders, endStream: true);
|
||||||
|
|
||||||
|
await WaitForStreamErrorAsync(3, Http2ErrorCode.REFUSED_STREAM, CoreStrings.Http2ErrorMaxStreams);
|
||||||
|
|
||||||
|
requestBlocker.SetResult(0);
|
||||||
|
|
||||||
|
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: 3, ignoreNonGoAwayFrames: false);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task HEADERS_Received_StreamIdZero_ConnectionError()
|
public async Task HEADERS_Received_StreamIdZero_ConnectionError()
|
||||||
{
|
{
|
||||||
|
|
@ -2198,6 +2226,66 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.RST_STREAM, streamId: 1, headersStreamId: 1));
|
expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.RST_STREAM, streamId: 1, headersStreamId: 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SETTINGS_KestrelDefaults_Sent()
|
||||||
|
{
|
||||||
|
_connectionTask = _connection.ProcessRequestsAsync(new DummyApplication(_noopApplication));
|
||||||
|
|
||||||
|
await SendPreambleAsync().ConfigureAwait(false);
|
||||||
|
await SendSettingsAsync();
|
||||||
|
|
||||||
|
var frame = await ExpectAsync(Http2FrameType.SETTINGS,
|
||||||
|
withLength: 6,
|
||||||
|
withFlags: 0,
|
||||||
|
withStreamId: 0);
|
||||||
|
|
||||||
|
// Only non protocol defaults are sent
|
||||||
|
Assert.Equal(1, frame.SettingsCount);
|
||||||
|
var settings = frame.GetSettings();
|
||||||
|
Assert.Equal(1, settings.Count);
|
||||||
|
var setting = settings[0];
|
||||||
|
Assert.Equal(Http2SettingsParameter.SETTINGS_MAX_CONCURRENT_STREAMS, setting.Parameter);
|
||||||
|
Assert.Equal(100u, setting.Value);
|
||||||
|
|
||||||
|
await ExpectAsync(Http2FrameType.SETTINGS,
|
||||||
|
withLength: 0,
|
||||||
|
withFlags: (byte)Http2SettingsFrameFlags.ACK,
|
||||||
|
withStreamId: 0);
|
||||||
|
|
||||||
|
await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SETTINGS_Custom_Sent()
|
||||||
|
{
|
||||||
|
_connection.ServerSettings.MaxConcurrentStreams = 1;
|
||||||
|
|
||||||
|
_connectionTask = _connection.ProcessRequestsAsync(new DummyApplication(_noopApplication));
|
||||||
|
|
||||||
|
await SendPreambleAsync().ConfigureAwait(false);
|
||||||
|
await SendSettingsAsync();
|
||||||
|
|
||||||
|
var frame = await ExpectAsync(Http2FrameType.SETTINGS,
|
||||||
|
withLength: 6,
|
||||||
|
withFlags: 0,
|
||||||
|
withStreamId: 0);
|
||||||
|
|
||||||
|
// Only non protocol defaults are sent
|
||||||
|
Assert.Equal(1, frame.SettingsCount);
|
||||||
|
var settings = frame.GetSettings();
|
||||||
|
Assert.Equal(1, settings.Count);
|
||||||
|
var setting = settings[0];
|
||||||
|
Assert.Equal(Http2SettingsParameter.SETTINGS_MAX_CONCURRENT_STREAMS, setting.Parameter);
|
||||||
|
Assert.Equal(1u, setting.Value);
|
||||||
|
|
||||||
|
await ExpectAsync(Http2FrameType.SETTINGS,
|
||||||
|
withLength: 0,
|
||||||
|
withFlags: (byte)Http2SettingsFrameFlags.ACK,
|
||||||
|
withStreamId: 0);
|
||||||
|
|
||||||
|
await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task SETTINGS_Received_Sends_ACK()
|
public async Task SETTINGS_Received_Sends_ACK()
|
||||||
{
|
{
|
||||||
|
|
@ -3499,7 +3587,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
await SendSettingsAsync();
|
await SendSettingsAsync();
|
||||||
|
|
||||||
await ExpectAsync(Http2FrameType.SETTINGS,
|
await ExpectAsync(Http2FrameType.SETTINGS,
|
||||||
withLength: 0,
|
withLength: 6,
|
||||||
withFlags: 0,
|
withFlags: 0,
|
||||||
withStreamId: 0);
|
withStreamId: 0);
|
||||||
|
|
||||||
|
|
@ -3637,7 +3725,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
private Task SendSettingsAsync()
|
private Task SendSettingsAsync()
|
||||||
{
|
{
|
||||||
var frame = new Http2Frame();
|
var frame = new Http2Frame();
|
||||||
frame.PrepareSettings(Http2SettingsFrameFlags.NONE, _clientSettings);
|
frame.PrepareSettings(Http2SettingsFrameFlags.NONE, _clientSettings.GetNonProtocolDefaults());
|
||||||
return SendAsync(frame.Raw);
|
return SendAsync(frame.Raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3652,7 +3740,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
private Task SendSettingsWithInvalidStreamIdAsync(int streamId)
|
private Task SendSettingsWithInvalidStreamIdAsync(int streamId)
|
||||||
{
|
{
|
||||||
var frame = new Http2Frame();
|
var frame = new Http2Frame();
|
||||||
frame.PrepareSettings(Http2SettingsFrameFlags.NONE, _clientSettings);
|
frame.PrepareSettings(Http2SettingsFrameFlags.NONE, _clientSettings.GetNonProtocolDefaults());
|
||||||
frame.StreamId = streamId;
|
frame.StreamId = streamId;
|
||||||
return SendAsync(frame.Raw);
|
return SendAsync(frame.Raw);
|
||||||
}
|
}
|
||||||
|
|
@ -3660,7 +3748,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
private Task SendSettingsWithInvalidLengthAsync(int length)
|
private Task SendSettingsWithInvalidLengthAsync(int length)
|
||||||
{
|
{
|
||||||
var frame = new Http2Frame();
|
var frame = new Http2Frame();
|
||||||
frame.PrepareSettings(Http2SettingsFrameFlags.NONE, _clientSettings);
|
frame.PrepareSettings(Http2SettingsFrameFlags.NONE, _clientSettings.GetNonProtocolDefaults());
|
||||||
frame.Length = length;
|
frame.Length = length;
|
||||||
return SendAsync(frame.Raw);
|
return SendAsync(frame.Raw);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1868,7 +1868,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
await SendSettingsAsync();
|
await SendSettingsAsync();
|
||||||
|
|
||||||
await ExpectAsync(Http2FrameType.SETTINGS,
|
await ExpectAsync(Http2FrameType.SETTINGS,
|
||||||
withLength: 0,
|
withLength: 6,
|
||||||
withFlags: 0,
|
withFlags: 0,
|
||||||
withStreamId: 0);
|
withStreamId: 0);
|
||||||
|
|
||||||
|
|
@ -1937,7 +1937,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
private Task SendSettingsAsync()
|
private Task SendSettingsAsync()
|
||||||
{
|
{
|
||||||
var frame = new Http2Frame();
|
var frame = new Http2Frame();
|
||||||
frame.PrepareSettings(Http2SettingsFrameFlags.NONE, _clientSettings);
|
frame.PrepareSettings(Http2SettingsFrameFlags.NONE, _clientSettings.GetNonProtocolDefaults());
|
||||||
return SendAsync(frame.Raw);
|
return SendAsync(frame.Raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public Task Server_Http2Only_Cleartext_Success()
|
public Task Server_Http2Only_Cleartext_Success()
|
||||||
{
|
{
|
||||||
// Expect a SETTINGS frame (type 0x4) with no payload and no flags
|
// Expect a SETTINGS frame (type 0x4) with default settings
|
||||||
return TestSuccess(HttpProtocols.Http2, Encoding.ASCII.GetString(Http2Connection.ClientPreface), "\x00\x00\x00\x04\x00\x00\x00\x00\x00");
|
return TestSuccess(HttpProtocols.Http2, Encoding.ASCII.GetString(Http2Connection.ClientPreface),
|
||||||
|
"\x00\x00\x06\x04\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x64");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task TestSuccess(HttpProtocols serverProtocols, string request, string expectedResponse)
|
private async Task TestSuccess(HttpProtocols serverProtocols, string request, string expectedResponse)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue