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

Commit 01b35bc

Browse files
committed
Make HTTP/2 connection and stream windows configurable #2814
1 parent ceaa3c8 commit 01b35bc

File tree

7 files changed

+315
-116
lines changed

7 files changed

+315
-116
lines changed

src/Kestrel.Core/Http2Limits.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ public class Http2Limits
1515
private int _headerTableSize = (int)Http2PeerSettings.DefaultHeaderTableSize;
1616
private int _maxFrameSize = (int)Http2PeerSettings.DefaultMaxFrameSize;
1717
private int _maxRequestHeaderFieldSize = 8192;
18+
private int _initialConnectionWindowSize = 1024 * 128; // Larger than the default 64kb, and larger than any one single stream.
19+
private int _initialStreamWindowSize = 1024 * 96; // Larger than the default 64kb
1820

1921
/// <summary>
2022
/// Limits the number of concurrent request streams per HTTP/2 connection. Excess streams will be refused.
@@ -95,5 +97,49 @@ public int MaxRequestHeaderFieldSize
9597
_maxRequestHeaderFieldSize = value;
9698
}
9799
}
100+
101+
/// <summary>
102+
/// Indicates how much request body data the server is willing to receive and buffer at a time aggregated across all
103+
/// requests (streams) per connection. Note requests are also limited by <see cref="InitialStreamWindowSize"/>
104+
/// <para>
105+
/// Value must be greater than or equal to 65,535 and less than 2^31, defaults to 128 kb.
106+
/// </para>
107+
/// </summary>
108+
public int InitialConnectionWindowSize
109+
{
110+
get => _initialConnectionWindowSize;
111+
set
112+
{
113+
if (value < Http2PeerSettings.DefaultInitialWindowSize || value > Http2PeerSettings.MaxWindowSize)
114+
{
115+
throw new ArgumentOutOfRangeException(nameof(value), value,
116+
CoreStrings.FormatArgumentOutOfRange(Http2PeerSettings.DefaultInitialWindowSize, Http2PeerSettings.MaxWindowSize));
117+
}
118+
119+
_initialConnectionWindowSize = value;
120+
}
121+
}
122+
123+
/// <summary>
124+
/// Indicates how much request body data the server is willing to receive and buffer at a time per stream.
125+
/// Note connections are also limited by <see cref="InitialConnectionWindowSize"/>
126+
/// <para>
127+
/// Value must be greater than or equal to 65,535 and less than 2^31, defaults to 96 kb.
128+
/// </para>
129+
/// </summary>
130+
public int InitialStreamWindowSize
131+
{
132+
get => _initialStreamWindowSize;
133+
set
134+
{
135+
if (value < Http2PeerSettings.DefaultInitialWindowSize || value > Http2PeerSettings.MaxWindowSize)
136+
{
137+
throw new ArgumentOutOfRangeException(nameof(value), value,
138+
CoreStrings.FormatArgumentOutOfRange(Http2PeerSettings.DefaultInitialWindowSize, Http2PeerSettings.MaxWindowSize));
139+
}
140+
141+
_initialStreamWindowSize = value;
142+
}
143+
}
98144
}
99145
}

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ private enum PseudoHeaderFields
6363
private readonly Http2ConnectionContext _context;
6464
private readonly Http2FrameWriter _frameWriter;
6565
private readonly HPackDecoder _hpackDecoder;
66-
private readonly InputFlowControl _inputFlowControl = new InputFlowControl(Http2PeerSettings.DefaultInitialWindowSize, Http2PeerSettings.DefaultInitialWindowSize / 2);
66+
private readonly InputFlowControl _inputFlowControl;
6767
private readonly OutputFlowControl _outputFlowControl = new OutputFlowControl(Http2PeerSettings.DefaultInitialWindowSize);
6868

6969
private readonly Http2PeerSettings _serverSettings = new Http2PeerSettings();
@@ -96,6 +96,9 @@ public Http2Connection(Http2ConnectionContext context)
9696
_serverSettings.HeaderTableSize = (uint)http2Limits.HeaderTableSize;
9797
_hpackDecoder = new HPackDecoder(http2Limits.HeaderTableSize, http2Limits.MaxRequestHeaderFieldSize);
9898
_serverSettings.MaxHeaderListSize = (uint)httpLimits.MaxRequestHeadersTotalSize;
99+
_serverSettings.InitialWindowSize = (uint)http2Limits.InitialStreamWindowSize;
100+
var connectionWindow = (uint)http2Limits.InitialConnectionWindowSize;
101+
_inputFlowControl = new InputFlowControl(connectionWindow, connectionWindow / 2);
99102
}
100103

101104
public string ConnectionId => _context.ConnectionId;
@@ -183,6 +186,14 @@ public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> appl
183186
if (_state != Http2ConnectionState.Closed)
184187
{
185188
await _frameWriter.WriteSettingsAsync(_serverSettings.GetNonProtocolDefaults());
189+
// Inform the client that the connection window is larger than the default. It can't be lowered here,
190+
// It can only be lowered by not issuing window updates after data is received.
191+
var connectionWindow = _context.ServiceContext.ServerOptions.Limits.Http2.InitialConnectionWindowSize;
192+
var diff = connectionWindow - (int)Http2PeerSettings.DefaultInitialWindowSize;
193+
if (diff > 0)
194+
{
195+
await _frameWriter.WriteWindowUpdateAsync(0, diff);
196+
}
186197
}
187198

188199
while (_state != Http2ConnectionState.Closed)
@@ -541,6 +552,7 @@ private Task ProcessHeadersFrameAsync<TContext>(IHttpApplication<TContext> appli
541552
RemoteEndPoint = _context.RemoteEndPoint,
542553
StreamLifetimeHandler = this,
543554
ClientPeerSettings = _clientSettings,
555+
ServerPeerSettings = _serverSettings,
544556
FrameWriter = _frameWriter,
545557
ConnectionInputFlowControl = _inputFlowControl,
546558
ConnectionOutputFlowControl = _outputFlowControl,

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@ public Http2Stream(Http2StreamContext context)
3636
_context.StreamId,
3737
_context.FrameWriter,
3838
context.ConnectionInputFlowControl,
39-
Http2PeerSettings.DefaultInitialWindowSize,
40-
Http2PeerSettings.DefaultInitialWindowSize / 2);
39+
_context.ServerPeerSettings.InitialWindowSize,
40+
_context.ServerPeerSettings.InitialWindowSize / 2);
4141

4242
_outputFlowControl = new StreamOutputFlowControl(context.ConnectionOutputFlowControl, context.ClientPeerSettings.InitialWindowSize);
4343
_http2Output = new Http2OutputProducer(context.StreamId, context.FrameWriter, _outputFlowControl, context.TimeoutControl, context.MemoryPool);
4444

45-
RequestBodyPipe = CreateRequestBodyPipe();
45+
RequestBodyPipe = CreateRequestBodyPipe(_context.ServerPeerSettings.InitialWindowSize);
4646
Output = _http2Output;
4747
}
4848

@@ -446,14 +446,16 @@ private void AbortCore(Exception abortReason)
446446
_inputFlowControl.Abort();
447447
}
448448

449-
private Pipe CreateRequestBodyPipe()
449+
private Pipe CreateRequestBodyPipe(uint windowSize)
450450
=> new Pipe(new PipeOptions
451451
(
452452
pool: _context.MemoryPool,
453453
readerScheduler: ServiceContext.Scheduler,
454454
writerScheduler: PipeScheduler.Inline,
455-
pauseWriterThreshold: Http2PeerSettings.DefaultInitialWindowSize,
456-
resumeWriterThreshold: Http2PeerSettings.DefaultInitialWindowSize,
455+
// Never pause within the window range. Flow control will prevent more data from being added.
456+
// See the assert in OnDataAsync.
457+
pauseWriterThreshold: windowSize + 1,
458+
resumeWriterThreshold: windowSize + 1,
457459
useSynchronizationContext: false,
458460
minimumSegmentSize: KestrelMemoryPool.MinimumSegmentSize
459461
));

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public class Http2StreamContext : IHttpProtocolContext
2121
public IPEndPoint LocalEndPoint { get; set; }
2222
public IHttp2StreamLifetimeHandler StreamLifetimeHandler { get; set; }
2323
public Http2PeerSettings ClientPeerSettings { get; set; }
24+
public Http2PeerSettings ServerPeerSettings { get; set; }
2425
public Http2FrameWriter FrameWriter { get; set; }
2526
public InputFlowControl ConnectionInputFlowControl { get; set; }
2627
public OutputFlowControl ConnectionOutputFlowControl { get; set; }

0 commit comments

Comments
 (0)