Merge branch 'release/2.2'

This commit is contained in:
John Luo 2018-09-28 10:34:17 -07:00
commit ccaed5176a
70 changed files with 3922 additions and 1432 deletions

View File

@ -1,14 +1,201 @@
Copyright (c) .NET Foundation and Contributors
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
All rights reserved.
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
1. Definitions.
http://www.apache.org/licenses/LICENSE-2.0
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright (c) .NET Foundation and Contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
HttpParser = NullParser<Http1ParsingHandler>.Instance
};
var http1Connection = new Http1Connection(context: new Http1ConnectionContext
var http1Connection = new Http1Connection(context: new HttpConnectionContext
{
ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(),

View File

@ -33,7 +33,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
HttpParser = NullParser<Http1ParsingHandler>.Instance
};
var http1Connection = new Http1Connection(new Http1ConnectionContext
var http1Connection = new Http1Connection(new HttpConnectionContext
{
ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(),

View File

@ -108,7 +108,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
HttpParser = new HttpParser<Http1ParsingHandler>()
};
var http1Connection = new TestHttp1Connection(new Http1ConnectionContext
var http1Connection = new TestHttp1Connection(new HttpConnectionContext
{
ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(),

View File

@ -91,7 +91,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
HttpParser = new HttpParser<Http1ParsingHandler>()
};
var http1Connection = new Http1Connection(new Http1ConnectionContext
var http1Connection = new Http1Connection(new HttpConnectionContext
{
ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(),

View File

@ -1,6 +1,7 @@
// 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 Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Performance.Mocks
@ -11,15 +12,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance.Mocks
{
}
public void ResetTimeout(long ticks, TimeoutAction timeoutAction)
public void ResetTimeout(long ticks, TimeoutReason timeoutReason)
{
}
public void SetTimeout(long ticks, TimeoutAction timeoutAction)
public void SetTimeout(long ticks, TimeoutReason timeoutReason)
{
}
public void StartTimingReads()
public void StartTimingReads(MinDataRate minRate)
{
}
@ -39,7 +40,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance.Mocks
{
}
public void StartTimingWrite(long size)
public void StartTimingWrite(MinDataRate rate, long size)
{
}

View File

@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
HttpParser = new HttpParser<Http1ParsingHandler>()
};
var http1Connection = new Http1Connection(new Http1ConnectionContext
var http1Connection = new Http1Connection(new HttpConnectionContext
{
ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(),

View File

@ -184,7 +184,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
HttpParser = new HttpParser<Http1ParsingHandler>()
};
var http1Connection = new Http1Connection(new Http1ConnectionContext
var http1Connection = new Http1Connection(new HttpConnectionContext
{
ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(),

View File

@ -127,7 +127,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
HttpParser = new HttpParser<Http1ParsingHandler>()
};
var http1Connection = new TestHttp1Connection(new Http1ConnectionContext
var http1Connection = new TestHttp1Connection(new HttpConnectionContext
{
ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(),

View File

@ -3,7 +3,6 @@
using System.Collections.Generic;
using System.IO.Pipelines;
using System.Threading;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http.Features;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
[
{
"TypeId": "public class Microsoft.AspNetCore.Connections.DefaultConnectionContext : Microsoft.AspNetCore.Connections.ConnectionContext, System.IDisposable, Microsoft.AspNetCore.Connections.Features.IConnectionIdFeature, Microsoft.AspNetCore.Connections.Features.IConnectionItemsFeature, Microsoft.AspNetCore.Connections.Features.IConnectionTransportFeature, Microsoft.AspNetCore.Connections.Features.IConnectionUserFeature, Microsoft.AspNetCore.Connections.Features.IConnectionLifetimeFeature",
"MemberId": "public System.Void Abort()",
"Kind": "Removal"
}
]

View File

@ -584,4 +584,7 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
<data name="HPackErrorIntegerTooBig" xml:space="preserve">
<value>The decoded integer exceeds the maximum value of Int32.MaxValue.</value>
</data>
<data name="ConnectionAbortedByClient" xml:space="preserve">
<value>The client closed the connection.</value>
</data>
</root>

View File

@ -6,9 +6,7 @@ using System.Buffers;
using System.IO.Pipelines;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
using Microsoft.Extensions.Logging;
@ -84,12 +82,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
}
finally
{
Log.ConnectionStop(connectionContext.ConnectionId);
KestrelEventSource.Log.ConnectionStop(connectionContext);
connection.Complete();
_serviceContext.ConnectionManager.RemoveConnection(id);
Log.ConnectionStop(connectionContext.ConnectionId);
KestrelEventSource.Log.ConnectionStop(connectionContext);
}
}

View File

@ -21,8 +21,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private const byte ByteForwardSlash = (byte)'/';
private const string Asterisk = "*";
private readonly Http1ConnectionContext _context;
private readonly HttpConnectionContext _context;
private readonly IHttpParser<Http1ParsingHandler> _parser;
private readonly Http1OutputProducer _http1Output;
protected readonly long _keepAliveTicks;
private readonly long _requestHeadersTimeoutTicks;
@ -35,7 +36,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private int _remainingRequestHeadersBytesAllowed;
public Http1Connection(Http1ConnectionContext context)
public Http1Connection(HttpConnectionContext context)
: base(context)
{
_context = context;
@ -44,13 +45,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
_requestHeadersTimeoutTicks = ServerOptions.Limits.RequestHeadersTimeout.Ticks;
RequestBodyPipe = CreateRequestBodyPipe();
Output = new Http1OutputProducer(
_http1Output = new Http1OutputProducer(
_context.Transport.Output,
_context.ConnectionId,
_context.ConnectionContext,
_context.ServiceContext.Log,
_context.TimeoutControl,
_context.ConnectionFeatures.Get<IBytesWrittenFeature>());
this);
Output = _http1Output;
}
public PipeReader Input => _context.Transport.Input;
@ -60,6 +64,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public override bool IsUpgradableRequest => _upgradeAvailable;
protected override void OnRequestProcessingEnded()
{
Input.Complete();
TimeoutControl.StartDrainTimeout(MinResponseDataRate, ServerOptions.Limits.MaxResponseBufferSize);
// Prevent RequestAborted from firing. Free up unneeded feature references.
Reset();
_http1Output.Dispose();
}
public void OnInputOrOutputCompleted()
{
_http1Output.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient));
AbortRequest();
}
/// <summary>
/// Immediately kill the connection and poison the request body stream with an error.
/// </summary>
@ -70,11 +92,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
return;
}
// Abort output prior to calling OnIOCompleted() to give the transport the chance to complete the input
// with the correct error and message.
Output.Abort(abortReason);
_http1Output.Abort(abortReason);
OnInputOrOutputCompleted();
AbortRequest();
PoisonRequestBodyStream(abortReason);
}
@ -115,7 +135,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
break;
}
TimeoutControl.ResetTimeout(_requestHeadersTimeoutTicks, TimeoutAction.SendTimeoutResponse);
TimeoutControl.ResetTimeout(_requestHeadersTimeoutTicks, TimeoutReason.RequestHeaders);
_requestProcessingStatus = RequestProcessingStatus.ParsingRequestLine;
goto case RequestProcessingStatus.ParsingRequestLine;
@ -411,7 +431,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
protected override void OnRequestProcessingEnding()
{
Input.Complete();
}
protected override string CreateRequestId()
@ -424,7 +443,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
// Reset the features and timeout.
Reset();
TimeoutControl.SetTimeout(_keepAliveTicks, TimeoutAction.StopProcessingNextRequest);
TimeoutControl.SetTimeout(_keepAliveTicks, TimeoutReason.KeepAlive);
}
protected override bool BeginRead(out ValueTask<ReadResult> awaitable)

View File

@ -1,25 +0,0 @@
// 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.Buffers;
using System.IO.Pipelines;
using System.Net;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public class Http1ConnectionContext : IHttpProtocolContext
{
public string ConnectionId { get; set; }
public ServiceContext ServiceContext { get; set; }
public ConnectionContext ConnectionContext { get; set; }
public IFeatureCollection ConnectionFeatures { get; set; }
public MemoryPool<byte> MemoryPool { get; set; }
public IPEndPoint RemoteEndPoint { get; set; }
public IPEndPoint LocalEndPoint { get; set; }
public ITimeoutControl TimeoutControl { get; set; }
public IDuplexPipe Transport { get; set; }
}
}

View File

@ -18,6 +18,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private volatile bool _canceled;
private Task _pumpTask;
private bool _timingReads;
protected Http1MessageBody(Http1Connection context)
: base(context)
@ -175,7 +176,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
Log.RequestBodyNotEntirelyRead(_context.ConnectionIdFeature, _context.TraceIdentifier);
_context.TimeoutControl.SetTimeout(Constants.RequestBodyDrainTimeout.Ticks, TimeoutAction.AbortConnection);
_context.TimeoutControl.SetTimeout(Constants.RequestBodyDrainTimeout.Ticks, TimeoutReason.RequestBodyDrain);
try
{
@ -232,13 +233,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (!RequestUpgrade)
{
Log.RequestBodyStart(_context.ConnectionIdFeature, _context.TraceIdentifier);
_context.TimeoutControl.StartTimingReads();
// REVIEW: This makes it no longer effective to change the min rate after the app starts reading.
// Is this OK? Should we throw from the MinRequestBodyDataRate setter in this case?
var minRate = _context.MinRequestBodyDataRate;
if (minRate != null)
{
_timingReads = true;
_context.TimeoutControl.StartTimingReads(minRate);
}
}
}
private void TryPauseTimingReads()
{
if (!RequestUpgrade)
if (_timingReads)
{
_context.TimeoutControl.PauseTimingReads();
}
@ -246,7 +256,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private void TryResumeTimingReads()
{
if (!RequestUpgrade)
if (_timingReads)
{
_context.TimeoutControl.ResumeTimingReads();
}
@ -257,7 +267,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (!RequestUpgrade)
{
Log.RequestBodyDone(_context.ConnectionIdFeature, _context.TraceIdentifier);
_context.TimeoutControl.StopTimingReads();
if (_timingReads)
{
_context.TimeoutControl.StopTimingReads();
}
}
}

View File

@ -8,12 +8,12 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public class Http1OutputProducer : IHttpOutputProducer
public class Http1OutputProducer : IHttpOutputProducer, IHttpOutputAborter, IDisposable
{
private static readonly ReadOnlyMemory<byte> _continueBytes = new ReadOnlyMemory<byte>(Encoding.ASCII.GetBytes("HTTP/1.1 100 Continue\r\n\r\n"));
private static readonly byte[] _bytesHttpVersion11 = Encoding.ASCII.GetBytes("HTTP/1.1 ");
@ -22,10 +22,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private readonly string _connectionId;
private readonly ConnectionContext _connectionContext;
private readonly ITimeoutControl _timeoutControl;
private readonly IKestrelTrace _log;
private readonly IBytesWrittenFeature _transportBytesWrittenFeature;
private readonly StreamSafePipeFlusher _flusher;
private readonly IHttpMinResponseDataRateFeature _minResponseDataRateFeature;
private readonly TimingPipeFlusher _flusher;
// This locks access to to all of the below fields
private readonly object _contextLock = new object();
@ -33,24 +32,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private bool _completed = false;
private bool _aborted;
private long _unflushedBytes;
private long _totalBytesCommitted;
private readonly PipeWriter _pipeWriter;
public Http1OutputProducer(
PipeWriter pipeWriter,
string connectionId,
ConnectionContext connectionContext,
IKestrelTrace log,
ITimeoutControl timeoutControl,
IBytesWrittenFeature transportBytesWrittenFeature)
IHttpMinResponseDataRateFeature minResponseDataRateFeature)
{
_pipeWriter = pipeWriter;
_connectionId = connectionId;
_connectionContext = connectionContext;
_timeoutControl = timeoutControl;
_log = log;
_transportBytesWrittenFeature = transportBytesWrittenFeature;
_flusher = new StreamSafePipeFlusher(pipeWriter, timeoutControl);
_minResponseDataRateFeature = minResponseDataRateFeature;
_flusher = new TimingPipeFlusher(pipeWriter, timeoutControl);
}
public Task WriteDataAsync(ReadOnlySpan<byte> buffer, CancellationToken cancellationToken = default)
@ -85,7 +83,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
var buffer = _pipeWriter;
var bytesCommitted = callback(buffer, state);
_unflushedBytes += bytesCommitted;
_totalBytesCommitted += bytesCommitted;
}
return FlushAsync(cancellationToken);
@ -112,7 +109,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
writer.Commit();
_unflushedBytes += writer.BytesCommitted;
_totalBytesCommitted += writer.BytesCommitted;
}
}
@ -128,15 +124,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
_log.ConnectionDisconnect(_connectionId);
_completed = true;
_pipeWriter.Complete();
var unsentBytes = _totalBytesCommitted - _transportBytesWrittenFeature.TotalBytesWritten;
if (unsentBytes > 0)
{
// unsentBytes should never be over 64KB in the default configuration.
_timeoutControl.StartTimingWrite((int)Math.Min(unsentBytes, int.MaxValue));
_pipeWriter.OnReaderCompleted((ex, state) => ((ITimeoutControl)state).StopTimingWrite(), _timeoutControl);
}
}
}
@ -154,13 +141,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
_aborted = true;
_connectionContext.Abort(error);
if (!_completed)
{
_log.ConnectionDisconnect(_connectionId);
_completed = true;
_pipeWriter.Complete();
}
Dispose();
}
}
@ -186,14 +167,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
writer.Write(buffer);
_unflushedBytes += buffer.Length;
_totalBytesCommitted += buffer.Length;
}
writer.Commit();
var bytesWritten = _unflushedBytes;
_unflushedBytes = 0;
return _flusher.FlushAsync(bytesWritten, this, cancellationToken);
return _flusher.FlushAsync(
_minResponseDataRateFeature.MinDataRate,
bytesWritten,
this,
cancellationToken);
}
}
}

View File

@ -21,8 +21,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
// ReSharper disable AccessToModifiedClosure
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public abstract partial class HttpProtocol : IHttpResponseControl
@ -60,12 +58,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private long _responseBytesWritten;
private readonly IHttpProtocolContext _context;
private readonly HttpConnectionContext _context;
protected string _methodText = null;
private string _scheme = null;
public HttpProtocol(IHttpProtocolContext context)
public HttpProtocol(HttpConnectionContext context)
{
_context = context;
@ -412,7 +410,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
}
}
public void OnInputOrOutputCompleted()
protected void AbortRequest()
{
if (Interlocked.Exchange(ref _ioCompleted, 1) != 0)
{
@ -421,8 +419,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
_keepAlive = false;
Output.Dispose();
// Potentially calling user code. CancelRequestAbortedToken logs any exceptions.
ServiceContext.Scheduler.Schedule(state => ((HttpProtocol)state).CancelRequestAbortedToken(), this);
}
@ -476,13 +472,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
try
{
OnRequestProcessingEnding();
await TryProduceInvalidRequestResponse();
// Prevent RequestAborted from firing.
Reset();
Output.Dispose();
}
catch (Exception ex)
{

View File

@ -0,0 +1,12 @@
// 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 Microsoft.AspNetCore.Connections;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public interface IHttpOutputAborter
{
void Abort(ConnectionAbortedException abortReason);
}
}

View File

@ -5,20 +5,16 @@ using System;
using System.IO.Pipelines;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public interface IHttpOutputProducer : IDisposable
public interface IHttpOutputProducer
{
void Abort(ConnectionAbortedException abortReason);
Task WriteAsync<T>(Func<PipeWriter, T, long> callback, T state, CancellationToken cancellationToken);
Task FlushAsync(CancellationToken cancellationToken);
Task Write100ContinueAsync();
void WriteResponseHeaders(int statusCode, string ReasonPhrase, HttpResponseHeaders responseHeaders);
// The reason this is ReadOnlySpan and not ReadOnlyMemory is because writes are always
// synchronous. Flushing to get back pressure is the only time we truly go async but
// that's after the buffer is copied
// This takes ReadOnlySpan instead of ReadOnlyMemory because it always synchronously copies data before flushing.
Task WriteDataAsync(ReadOnlySpan<byte> data, CancellationToken cancellationToken);
Task WriteStreamSuffixAsync();
}

View File

@ -1,19 +0,0 @@
// 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.Buffers;
using System.Net;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public interface IHttpProtocolContext
{
string ConnectionId { get; set; }
ServiceContext ServiceContext { get; set; }
IFeatureCollection ConnectionFeatures { get; set; }
MemoryPool<byte> MemoryPool { get; set; }
IPEndPoint RemoteEndPoint { get; set; }
IPEndPoint LocalEndPoint { get; set; }
}
}

View File

@ -23,7 +23,7 @@ using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
public class Http2Connection : ITimeoutControl, IHttp2StreamLifetimeHandler, IHttpHeadersHandler, IRequestProcessor
public class Http2Connection : IHttp2StreamLifetimeHandler, IHttpHeadersHandler, IRequestProcessor
{
private enum RequestHeaderParsingState
{
@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
private static readonly byte[] _trailersBytes = Encoding.ASCII.GetBytes("trailers");
private static readonly byte[] _connectBytes = Encoding.ASCII.GetBytes("CONNECT");
private readonly Http2ConnectionContext _context;
private readonly HttpConnectionContext _context;
private readonly Http2FrameWriter _frameWriter;
private readonly HPackDecoder _hpackDecoder;
private readonly InputFlowControl _inputFlowControl;
@ -84,13 +84,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
private readonly ConcurrentDictionary<int, Http2Stream> _streams = new ConcurrentDictionary<int, Http2Stream>();
public Http2Connection(Http2ConnectionContext context)
public Http2Connection(HttpConnectionContext context)
{
var httpLimits = context.ServiceContext.ServerOptions.Limits;
var http2Limits = httpLimits.Http2;
_context = context;
_frameWriter = new Http2FrameWriter(context.Transport.Output, context.ConnectionContext, _outputFlowControl, this, context.ConnectionId, context.ServiceContext.Log);
_frameWriter = new Http2FrameWriter(context.Transport.Output, context.ConnectionContext, _outputFlowControl, context.TimeoutControl, context.ConnectionId, context.ServiceContext.Log);
_serverSettings.MaxConcurrentStreams = (uint)http2Limits.MaxStreamsPerConnection;
_serverSettings.MaxFrameSize = (uint)http2Limits.MaxFrameSize;
_serverSettings.HeaderTableSize = (uint)http2Limits.HeaderTableSize;
@ -102,12 +102,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
}
public string ConnectionId => _context.ConnectionId;
public PipeReader Input => _context.Transport.Input;
public IKestrelTrace Log => _context.ServiceContext.Log;
public IFeatureCollection ConnectionFeatures => _context.ConnectionFeatures;
public KestrelServerOptions ServerOptions => _context.ServiceContext.ServerOptions;
internal Http2PeerSettings ServerSettings => _serverSettings;
@ -117,12 +115,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
if (_state != Http2ConnectionState.Closed)
{
_frameWriter.WriteGoAwayAsync(_highestOpenedStreamId, Http2ErrorCode.NO_ERROR);
UpdateState(Http2ConnectionState.Closed);
}
}
_frameWriter.Complete();
_frameWriter.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient));
}
public void Abort(ConnectionAbortedException ex)
@ -300,6 +297,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
await _streamsCompleted.Task;
_context.TimeoutControl.StartDrainTimeout(ServerOptions.Limits.MinResponseDataRate, ServerOptions.Limits.MaxResponseBufferSize);
_frameWriter.Complete();
}
catch
@ -556,7 +555,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
FrameWriter = _frameWriter,
ConnectionInputFlowControl = _inputFlowControl,
ConnectionOutputFlowControl = _outputFlowControl,
TimeoutControl = this,
TimeoutControl = _context.TimeoutControl,
});
_currentHeadersStream.Reset();
@ -1137,45 +1136,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
Log.Http2ConnectionClosed(_context.ConnectionId, _highestOpenedStreamId);
}
}
void ITimeoutControl.SetTimeout(long ticks, TimeoutAction timeoutAction)
{
}
void ITimeoutControl.ResetTimeout(long ticks, TimeoutAction timeoutAction)
{
}
void ITimeoutControl.CancelTimeout()
{
}
void ITimeoutControl.StartTimingReads()
{
}
void ITimeoutControl.PauseTimingReads()
{
}
void ITimeoutControl.ResumeTimingReads()
{
}
void ITimeoutControl.StopTimingReads()
{
}
void ITimeoutControl.BytesRead(long count)
{
}
void ITimeoutControl.StartTimingWrite(long size)
{
}
void ITimeoutControl.StopTimingWrite()
{
}
}
}

View File

@ -1,23 +0,0 @@
// 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.Buffers;
using System.IO.Pipelines;
using System.Net;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
public class Http2ConnectionContext
{
public string ConnectionId { get; set; }
public ConnectionContext ConnectionContext { get; set; }
public ServiceContext ServiceContext { get; set; }
public IFeatureCollection ConnectionFeatures { get; set; }
public MemoryPool<byte> MemoryPool { get; set; }
public IPEndPoint LocalEndPoint { get; set; }
public IPEndPoint RemoteEndPoint { get; set; }
public IDuplexPipe Transport { get; set; }
}
}

View File

@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
private readonly OutputFlowControl _connectionOutputFlowControl;
private readonly string _connectionId;
private readonly IKestrelTrace _log;
private readonly StreamSafePipeFlusher _flusher;
private readonly TimingPipeFlusher _flusher;
private bool _completed;
@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_connectionOutputFlowControl = connectionOutputFlowControl;
_connectionId = connectionId;
_log = log;
_flusher = new StreamSafePipeFlusher(_outputWriter, timeoutControl);
_flusher = new TimingPipeFlusher(_outputWriter, timeoutControl);
_outgoingFrame = new Http2Frame();
_headerEncodingBuffer = new byte[_maxFrameSize];
}
@ -99,7 +99,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
}
}
public Task FlushAsync(IHttpOutputProducer outputProducer, CancellationToken cancellationToken)
public Task FlushAsync(IHttpOutputAborter outputAborter, CancellationToken cancellationToken)
{
lock (_writeLock)
{
@ -108,7 +108,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
return Task.CompletedTask;
}
return _flusher.FlushAsync(0, outputProducer, cancellationToken);
return _flusher.FlushAsync(outputAborter, cancellationToken);
}
}

View File

@ -15,11 +15,11 @@ using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
public class Http2OutputProducer : IHttpOutputProducer
public class Http2OutputProducer : IHttpOutputProducer, IHttpOutputAborter
{
private readonly int _streamId;
private readonly Http2FrameWriter _frameWriter;
private readonly StreamSafePipeFlusher _flusher;
private readonly TimingPipeFlusher _flusher;
// This should only be accessed via the FrameWriter. The connection-level output flow control is protected by the
// FrameWriter's connection-level write lock.
@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_frameWriter = frameWriter;
_flowControl = flowControl;
_dataPipe = CreateDataPipe(pool);
_flusher = new StreamSafePipeFlusher(_dataPipe.Writer, timeoutControl);
_flusher = new TimingPipeFlusher(_dataPipe.Writer, timeoutControl);
_dataWriteProcessingTask = ProcessDataWrites();
}
@ -65,7 +65,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
// Complete with an exception to prevent an end of stream data frame from being sent without an
// explicit call to WriteStreamSuffixAsync. ConnectionAbortedExceptions are swallowed, so the
// message doesn't matter
_dataPipe.Writer.Complete(new ConnectionAbortedException());
_dataPipe.Writer.Complete(new OperationCanceledException());
}
_frameWriter.AbortPendingStreamDataWrites(_flowControl);
@ -100,7 +100,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
// If there's already been response data written to the stream, just wait for that. Any header
// should be in front of the data frames in the connection pipe. Trailers could change things.
return _flusher.FlushAsync(0, this, cancellationToken);
return _flusher.FlushAsync(this, cancellationToken);
}
else
{
@ -158,7 +158,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_startedWritingDataFrames = true;
_dataPipe.Writer.Write(data);
return _flusher.FlushAsync(data.Length, this, cancellationToken);
return _flusher.FlushAsync(this, cancellationToken);
}
}
@ -173,9 +173,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_completed = true;
// Even if there's no actual data, completing the writer gracefully sends an END_STREAM DATA frame.
_startedWritingDataFrames = true;
_dataPipe.Writer.Complete();
return _dataWriteProcessingTask;
}
@ -208,9 +205,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_dataPipe.Reader.AdvanceTo(readResult.Buffer.End);
} while (!readResult.IsCompleted);
}
catch (ConnectionAbortedException)
catch (OperationCanceledException)
{
// Writes should not throw for aborted connections.
// Writes should not throw for aborted streams/connections.
}
catch (Exception ex)
{

View File

@ -67,11 +67,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
try
{
_http2Output.Dispose();
RequestBodyPipe.Reader.Complete();
// The app can no longer read any more of the request body, so return any bytes that weren't read to the
// connection's flow-control window.
_inputFlowControl.Abort();
Reset();
}
finally
{
@ -435,9 +439,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
private void AbortCore(Exception abortReason)
{
// Call OnIOCompleted() which closes the output prior to poisoning the request body stream or pipe to
// Call _http2Output.Dispose() prior to poisoning the request body stream or pipe to
// ensure that an app that completes early due to the abort doesn't result in header frames being sent.
OnInputOrOutputCompleted();
_http2Output.Dispose();
AbortRequest();
// Unblock the request body.
PoisonRequestBodyStream(abortReason);

View File

@ -1,30 +1,18 @@
// 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.Buffers;
using System.Net;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
public class Http2StreamContext : IHttpProtocolContext
public class Http2StreamContext : HttpConnectionContext
{
public string ConnectionId { get; set; }
public int StreamId { get; set; }
public ServiceContext ServiceContext { get; set; }
public IFeatureCollection ConnectionFeatures { get; set; }
public MemoryPool<byte> MemoryPool { get; set; }
public IPEndPoint RemoteEndPoint { get; set; }
public IPEndPoint LocalEndPoint { get; set; }
public IHttp2StreamLifetimeHandler StreamLifetimeHandler { get; set; }
public Http2PeerSettings ClientPeerSettings { get; set; }
public Http2PeerSettings ServerPeerSettings { get; set; }
public Http2FrameWriter FrameWriter { get; set; }
public InputFlowControl ConnectionInputFlowControl { get; set; }
public OutputFlowControl ConnectionOutputFlowControl { get; set; }
public ITimeoutControl TimeoutControl { get; set; }
}
}

View File

@ -8,7 +8,6 @@ using System.Diagnostics;
using System.IO;
using System.IO.Pipelines;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
@ -24,12 +23,13 @@ using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{
public class HttpConnection : ITimeoutControl, IConnectionTimeoutFeature
public class HttpConnection : ITimeoutHandler
{
private static readonly ReadOnlyMemory<byte> Http2Id = new[] { (byte)'h', (byte)'2' };
private readonly HttpConnectionContext _context;
private readonly ISystemClock _systemClock;
private readonly TimeoutControl _timeoutControl;
private IList<IAdaptedConnection> _adaptedConnections;
private IDuplexPipe _adaptedTransport;
@ -39,33 +39,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
private IRequestProcessor _requestProcessor;
private Http1Connection _http1Connection;
private long _lastTimestamp;
private long _timeoutTimestamp = long.MaxValue;
private TimeoutAction _timeoutAction;
private readonly object _readTimingLock = new object();
private bool _readTimingEnabled;
private bool _readTimingPauseRequested;
private long _readTimingElapsedTicks;
private long _readTimingBytesRead;
private readonly object _writeTimingLock = new object();
private int _writeTimingWrites;
private long _writeTimingTimeoutTimestamp;
public HttpConnection(HttpConnectionContext context)
{
_context = context;
_systemClock = _context.ServiceContext.SystemClock;
_timeoutControl = new TimeoutControl(this);
}
// For testing
internal HttpProtocol Http1Connection => _http1Connection;
internal IDebugger Debugger { get; set; } = DebuggerWrapper.Singleton;
// For testing
internal bool RequestTimedOut { get; private set; }
public string ConnectionId => _context.ConnectionId;
public IPEndPoint LocalEndPoint => _context.LocalEndPoint;
public IPEndPoint RemoteEndPoint => _context.RemoteEndPoint;
@ -131,9 +116,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
using (connectionLifetimeNotificationFeature?.ConnectionClosedRequested.Register(state => ((HttpConnection)state).StopProcessingNextRequest(), this))
{
_lastTimestamp = _context.ServiceContext.SystemClock.UtcNow.Ticks;
// Ensure TimeoutControl._lastTimestamp is intialized before anything that could set timeouts runs.
_timeoutControl.Initialize(_systemClock.UtcNow);
_context.ConnectionFeatures.Set<IConnectionTimeoutFeature>(this);
_context.ConnectionFeatures.Set<IConnectionTimeoutFeature>(_timeoutControl);
if (adaptedPipeline != null)
{
@ -149,18 +135,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
// Ensure that the connection hasn't already been stopped.
if (_protocolSelectionState == ProtocolSelectionState.Initializing)
{
var derivedContext = CreateDerivedContext(_adaptedTransport);
switch (SelectProtocol())
{
case HttpProtocols.Http1:
// _http1Connection must be initialized before adding the connection to the connection manager
requestProcessor = _http1Connection = CreateHttp1Connection(_adaptedTransport);
requestProcessor = _http1Connection = new Http1Connection(derivedContext);
_protocolSelectionState = ProtocolSelectionState.Selected;
break;
case HttpProtocols.Http2:
// _http2Connection must be initialized before yielding control to the transport thread,
// to prevent a race condition where _http2Connection.Abort() is called just as
// _http2Connection is about to be initialized.
requestProcessor = CreateHttp2Connection(_adaptedTransport);
requestProcessor = new Http2Connection(derivedContext);
_protocolSelectionState = ProtocolSelectionState.Selected;
break;
case HttpProtocols.None:
@ -210,39 +198,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
// For testing only
internal void Initialize(IDuplexPipe transport)
{
_requestProcessor = _http1Connection = CreateHttp1Connection(transport);
_requestProcessor = _http1Connection = new Http1Connection(CreateDerivedContext(transport));
_protocolSelectionState = ProtocolSelectionState.Selected;
}
private Http1Connection CreateHttp1Connection(IDuplexPipe transport)
private HttpConnectionContext CreateDerivedContext(IDuplexPipe transport)
{
return new Http1Connection(new Http1ConnectionContext
return new HttpConnectionContext
{
ConnectionId = _context.ConnectionId,
ConnectionFeatures = _context.ConnectionFeatures,
MemoryPool = MemoryPool,
LocalEndPoint = LocalEndPoint,
RemoteEndPoint = RemoteEndPoint,
MemoryPool = _context.MemoryPool,
LocalEndPoint = _context.LocalEndPoint,
RemoteEndPoint = _context.RemoteEndPoint,
ServiceContext = _context.ServiceContext,
ConnectionContext = _context.ConnectionContext,
TimeoutControl = this,
TimeoutControl = _timeoutControl,
Transport = transport
});
}
private Http2Connection CreateHttp2Connection(IDuplexPipe transport)
{
return new Http2Connection(new Http2ConnectionContext
{
ConnectionId = _context.ConnectionId,
ConnectionContext = _context.ConnectionContext,
ServiceContext = _context.ServiceContext,
ConnectionFeatures = _context.ConnectionFeatures,
MemoryPool = MemoryPool,
LocalEndPoint = LocalEndPoint,
RemoteEndPoint = RemoteEndPoint,
Transport = transport
});
};
}
private void StopProcessingNextRequest()
@ -378,11 +351,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
}
private void Tick()
{
Tick(_systemClock.UtcNow);
}
public void Tick(DateTimeOffset now)
{
if (_protocolSelectionState == ProtocolSelectionState.Aborted)
{
@ -391,246 +359,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
return;
}
var timestamp = now.Ticks;
CheckForTimeout(timestamp);
// HTTP/2 rate timeouts are not yet supported.
if (_http1Connection != null)
{
CheckForReadDataRateTimeout(timestamp);
CheckForWriteDataRateTimeout(timestamp);
}
Interlocked.Exchange(ref _lastTimestamp, timestamp);
}
private void CheckForTimeout(long timestamp)
{
// TODO: Use PlatformApis.VolatileRead equivalent again
if (timestamp > Interlocked.Read(ref _timeoutTimestamp))
{
if (!Debugger.IsAttached)
{
CancelTimeout();
switch (_timeoutAction)
{
case TimeoutAction.StopProcessingNextRequest:
// Http/2 keep-alive timeouts are not yet supported.
_http1Connection?.StopProcessingNextRequest();
break;
case TimeoutAction.SendTimeoutResponse:
// HTTP/2 timeout responses are not yet supported.
if (_http1Connection != null)
{
RequestTimedOut = true;
_http1Connection.SendTimeoutResponse();
}
break;
case TimeoutAction.AbortConnection:
// This is actually supported with HTTP/2!
Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedOutByServer));
break;
}
}
}
}
private void CheckForReadDataRateTimeout(long timestamp)
{
Debug.Assert(_http1Connection != null);
// The only time when both a timeout is set and the read data rate could be enforced is
// when draining the request body. Since there's already a (short) timeout set for draining,
// it's safe to not check the data rate at this point.
if (Interlocked.Read(ref _timeoutTimestamp) != long.MaxValue)
{
return;
}
lock (_readTimingLock)
{
if (_readTimingEnabled)
{
// Reference in local var to avoid torn reads in case the min rate is changed via IHttpMinRequestBodyDataRateFeature
var minRequestBodyDataRate = _http1Connection.MinRequestBodyDataRate;
_readTimingElapsedTicks += timestamp - _lastTimestamp;
if (minRequestBodyDataRate?.BytesPerSecond > 0 && _readTimingElapsedTicks > minRequestBodyDataRate.GracePeriod.Ticks)
{
var elapsedSeconds = (double)_readTimingElapsedTicks / TimeSpan.TicksPerSecond;
var rate = Interlocked.Read(ref _readTimingBytesRead) / elapsedSeconds;
if (rate < minRequestBodyDataRate.BytesPerSecond && !Debugger.IsAttached)
{
Log.RequestBodyMinimumDataRateNotSatisfied(_context.ConnectionId, _http1Connection.TraceIdentifier, minRequestBodyDataRate.BytesPerSecond);
RequestTimedOut = true;
_http1Connection.SendTimeoutResponse();
}
}
// PauseTimingReads() cannot just set _timingReads to false. It needs to go through at least one tick
// before pausing, otherwise _readTimingElapsed might never be updated if PauseTimingReads() is always
// called before the next tick.
if (_readTimingPauseRequested)
{
_readTimingEnabled = false;
_readTimingPauseRequested = false;
}
}
}
}
private void CheckForWriteDataRateTimeout(long timestamp)
{
Debug.Assert(_http1Connection != null);
lock (_writeTimingLock)
{
if (_writeTimingWrites > 0 && timestamp > _writeTimingTimeoutTimestamp && !Debugger.IsAttached)
{
RequestTimedOut = true;
Log.ResponseMinimumDataRateNotSatisfied(_http1Connection.ConnectionIdFeature, _http1Connection.TraceIdentifier);
Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied));
}
}
}
public void SetTimeout(long ticks, TimeoutAction timeoutAction)
{
Debug.Assert(_timeoutTimestamp == long.MaxValue, "Concurrent timeouts are not supported");
AssignTimeout(ticks, timeoutAction);
}
public void ResetTimeout(long ticks, TimeoutAction timeoutAction)
{
AssignTimeout(ticks, timeoutAction);
}
public void CancelTimeout()
{
Interlocked.Exchange(ref _timeoutTimestamp, long.MaxValue);
}
private void AssignTimeout(long ticks, TimeoutAction timeoutAction)
{
_timeoutAction = timeoutAction;
// Add Heartbeat.Interval since this can be called right before the next heartbeat.
Interlocked.Exchange(ref _timeoutTimestamp, _lastTimestamp + ticks + Heartbeat.Interval.Ticks);
}
public void StartTimingReads()
{
lock (_readTimingLock)
{
_readTimingElapsedTicks = 0;
_readTimingBytesRead = 0;
_readTimingEnabled = true;
}
}
public void StopTimingReads()
{
lock (_readTimingLock)
{
_readTimingEnabled = false;
}
}
public void PauseTimingReads()
{
lock (_readTimingLock)
{
_readTimingPauseRequested = true;
}
}
public void ResumeTimingReads()
{
lock (_readTimingLock)
{
_readTimingEnabled = true;
// In case pause and resume were both called between ticks
_readTimingPauseRequested = false;
}
}
public void BytesRead(long count)
{
Interlocked.Add(ref _readTimingBytesRead, count);
}
public void StartTimingWrite(long size)
{
Debug.Assert(_http1Connection != null);
lock (_writeTimingLock)
{
var minResponseDataRate = _http1Connection.MinResponseDataRate;
if (minResponseDataRate != null)
{
// Add Heartbeat.Interval since this can be called right before the next heartbeat.
var currentTimeUpperBound = _lastTimestamp + Heartbeat.Interval.Ticks;
var ticksToCompleteWriteAtMinRate = TimeSpan.FromSeconds(size / minResponseDataRate.BytesPerSecond).Ticks;
// If ticksToCompleteWriteAtMinRate is less than the configured grace period,
// allow that write to take up to the grace period to complete. Only add the grace period
// to the current time and not to any accumulated timeout.
var singleWriteTimeoutTimestamp = currentTimeUpperBound + Math.Max(
minResponseDataRate.GracePeriod.Ticks,
ticksToCompleteWriteAtMinRate);
// Don't penalize a connection for completing previous writes more quickly than required.
// We don't want to kill a connection when flushing the chunk terminator just because the previous
// chunk was large if the previous chunk was flushed quickly.
// Don't add any grace period to this accumulated timeout because the grace period could
// get accumulated repeatedly making the timeout for a bunch of consecutive small writes
// far too conservative.
var accumulatedWriteTimeoutTimestamp = _writeTimingTimeoutTimestamp + ticksToCompleteWriteAtMinRate;
_writeTimingTimeoutTimestamp = Math.Max(singleWriteTimeoutTimestamp, accumulatedWriteTimeoutTimestamp);
_writeTimingWrites++;
}
}
}
public void StopTimingWrite()
{
lock (_writeTimingLock)
{
_writeTimingWrites--;
}
}
void IConnectionTimeoutFeature.SetTimeout(TimeSpan timeSpan)
{
if (timeSpan < TimeSpan.Zero)
{
throw new ArgumentException(CoreStrings.PositiveFiniteTimeSpanRequired, nameof(timeSpan));
}
if (_timeoutTimestamp != long.MaxValue)
{
throw new InvalidOperationException(CoreStrings.ConcurrentTimeoutsNotSupported);
}
SetTimeout(timeSpan.Ticks, TimeoutAction.AbortConnection);
}
void IConnectionTimeoutFeature.ResetTimeout(TimeSpan timeSpan)
{
if (timeSpan < TimeSpan.Zero)
{
throw new ArgumentException(CoreStrings.PositiveFiniteTimeSpanRequired, nameof(timeSpan));
}
ResetTimeout(timeSpan.Ticks, TimeoutAction.AbortConnection);
_timeoutControl.Tick(_systemClock.UtcNow);
}
private void CloseUninitializedConnection(ConnectionAbortedException abortReason)
@ -643,6 +372,36 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
_adaptedTransport.Output.Complete();
}
public void OnTimeout(TimeoutReason reason)
{
// In the cases that don't log directly here, we expect the setter of the timeout to also be the input
// reader, so when the read is canceled or arborted, the reader should write the appropriate log.
switch (reason)
{
case TimeoutReason.KeepAlive:
_http1Connection.StopProcessingNextRequest();
break;
case TimeoutReason.RequestHeaders:
_http1Connection.SendTimeoutResponse();
break;
case TimeoutReason.ReadDataRate:
Log.RequestBodyMinimumDataRateNotSatisfied(_context.ConnectionId, _http1Connection.TraceIdentifier, _http1Connection.MinRequestBodyDataRate.BytesPerSecond);
_http1Connection.SendTimeoutResponse();
break;
case TimeoutReason.WriteDataRate:
Log.ResponseMinimumDataRateNotSatisfied(_http1Connection.ConnectionIdFeature, _http1Connection.TraceIdentifier);
Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied));
break;
case TimeoutReason.RequestBodyDrain:
case TimeoutReason.TimeoutFeature:
Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedOutByServer));
break;
default:
Debug.Assert(false, "Invalid TimeoutReason");
break;
}
}
private enum ProtocolSelectionState
{
Initializing,

View File

@ -8,6 +8,7 @@ using System.Net;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{
@ -22,6 +23,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public MemoryPool<byte> MemoryPool { get; set; }
public IPEndPoint LocalEndPoint { get; set; }
public IPEndPoint RemoteEndPoint { get; set; }
public ITimeoutControl TimeoutControl { get; set; }
public IDuplexPipe Transport { get; set; }
}
}

View File

@ -5,17 +5,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
{
public interface ITimeoutControl
{
void SetTimeout(long ticks, TimeoutAction timeoutAction);
void ResetTimeout(long ticks, TimeoutAction timeoutAction);
void SetTimeout(long ticks, TimeoutReason timeoutReason);
void ResetTimeout(long ticks, TimeoutReason timeoutReason);
void CancelTimeout();
void StartTimingReads();
void StartTimingReads(MinDataRate minRate);
void PauseTimingReads();
void ResumeTimingReads();
void StopTimingReads();
void BytesRead(long count);
void StartTimingWrite(long size);
void StartTimingWrite(MinDataRate minRate, long size);
void StopTimingWrite();
}
}

View File

@ -3,10 +3,8 @@
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
{
public enum TimeoutAction
public interface ITimeoutHandler
{
StopProcessingNextRequest,
SendTimeoutResponse,
AbortConnection,
void OnTimeout(TimeoutReason reason);
}
}

View File

@ -0,0 +1,15 @@
// 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.
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
{
public enum TimeoutReason
{
KeepAlive,
RequestHeaders,
ReadDataRate,
WriteDataRate,
RequestBodyDrain,
TimeoutFeature,
}
}

View File

@ -0,0 +1,247 @@
// 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;
using System.Diagnostics;
using System.Threading;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
{
public class TimeoutControl : ITimeoutControl, IConnectionTimeoutFeature
{
private readonly ITimeoutHandler _timeoutHandler;
private long _lastTimestamp;
private long _timeoutTimestamp = long.MaxValue;
private TimeoutReason _timeoutReason;
private readonly object _readTimingLock = new object();
private MinDataRate _minReadRate;
private bool _readTimingEnabled;
private bool _readTimingPauseRequested;
private long _readTimingElapsedTicks;
private long _readTimingBytesRead;
private readonly object _writeTimingLock = new object();
private int _writeTimingWrites;
private long _writeTimingTimeoutTimestamp;
public TimeoutControl(ITimeoutHandler timeoutHandler)
{
_timeoutHandler = timeoutHandler;
}
internal IDebugger Debugger { get; set; } = DebuggerWrapper.Singleton;
public void Initialize(DateTimeOffset now)
{
_lastTimestamp = now.Ticks;
}
public void Tick(DateTimeOffset now)
{
var timestamp = now.Ticks;
CheckForTimeout(timestamp);
CheckForReadDataRateTimeout(timestamp);
CheckForWriteDataRateTimeout(timestamp);
Interlocked.Exchange(ref _lastTimestamp, timestamp);
}
private void CheckForTimeout(long timestamp)
{
if (!Debugger.IsAttached)
{
if (timestamp > Interlocked.Read(ref _timeoutTimestamp))
{
CancelTimeout();
_timeoutHandler.OnTimeout(_timeoutReason);
}
}
}
private void CheckForReadDataRateTimeout(long timestamp)
{
// The only time when both a timeout is set and the read data rate could be enforced is
// when draining the request body. Since there's already a (short) timeout set for draining,
// it's safe to not check the data rate at this point.
if (Interlocked.Read(ref _timeoutTimestamp) != long.MaxValue)
{
return;
}
lock (_readTimingLock)
{
if (!_readTimingEnabled)
{
return;
}
_readTimingElapsedTicks += timestamp - _lastTimestamp;
if (_minReadRate.BytesPerSecond > 0 && _readTimingElapsedTicks > _minReadRate.GracePeriod.Ticks)
{
var elapsedSeconds = (double)_readTimingElapsedTicks / TimeSpan.TicksPerSecond;
var rate = Interlocked.Read(ref _readTimingBytesRead) / elapsedSeconds;
if (rate < _minReadRate.BytesPerSecond && !Debugger.IsAttached)
{
_timeoutHandler.OnTimeout(TimeoutReason.ReadDataRate);
}
}
// PauseTimingReads() cannot just set _timingReads to false. It needs to go through at least one tick
// before pausing, otherwise _readTimingElapsed might never be updated if PauseTimingReads() is always
// called before the next tick.
if (_readTimingPauseRequested)
{
_readTimingEnabled = false;
_readTimingPauseRequested = false;
}
}
}
private void CheckForWriteDataRateTimeout(long timestamp)
{
lock (_writeTimingLock)
{
if (_writeTimingWrites > 0 && timestamp > _writeTimingTimeoutTimestamp && !Debugger.IsAttached)
{
_timeoutHandler.OnTimeout(TimeoutReason.WriteDataRate);
}
}
}
public void SetTimeout(long ticks, TimeoutReason timeoutReason)
{
Debug.Assert(_timeoutTimestamp == long.MaxValue, "Concurrent timeouts are not supported");
AssignTimeout(ticks, timeoutReason);
}
public void ResetTimeout(long ticks, TimeoutReason timeoutReason)
{
AssignTimeout(ticks, timeoutReason);
}
public void CancelTimeout()
{
Interlocked.Exchange(ref _timeoutTimestamp, long.MaxValue);
}
private void AssignTimeout(long ticks, TimeoutReason timeoutReason)
{
_timeoutReason = timeoutReason;
// Add Heartbeat.Interval since this can be called right before the next heartbeat.
Interlocked.Exchange(ref _timeoutTimestamp, _lastTimestamp + ticks + Heartbeat.Interval.Ticks);
}
public void StartTimingReads(MinDataRate minRate)
{
lock (_readTimingLock)
{
_minReadRate = minRate;
_readTimingElapsedTicks = 0;
_readTimingBytesRead = 0;
_readTimingEnabled = true;
}
}
public void StopTimingReads()
{
lock (_readTimingLock)
{
_readTimingEnabled = false;
}
}
public void PauseTimingReads()
{
lock (_readTimingLock)
{
_readTimingPauseRequested = true;
}
}
public void ResumeTimingReads()
{
lock (_readTimingLock)
{
_readTimingEnabled = true;
// In case pause and resume were both called between ticks
_readTimingPauseRequested = false;
}
}
public void BytesRead(long count)
{
Interlocked.Add(ref _readTimingBytesRead, count);
}
public void StartTimingWrite(MinDataRate minRate, long size)
{
lock (_writeTimingLock)
{
// Add Heartbeat.Interval since this can be called right before the next heartbeat.
var currentTimeUpperBound = _lastTimestamp + Heartbeat.Interval.Ticks;
var ticksToCompleteWriteAtMinRate = TimeSpan.FromSeconds(size / minRate.BytesPerSecond).Ticks;
// If ticksToCompleteWriteAtMinRate is less than the configured grace period,
// allow that write to take up to the grace period to complete. Only add the grace period
// to the current time and not to any accumulated timeout.
var singleWriteTimeoutTimestamp = currentTimeUpperBound + Math.Max(
minRate.GracePeriod.Ticks,
ticksToCompleteWriteAtMinRate);
// Don't penalize a connection for completing previous writes more quickly than required.
// We don't want to kill a connection when flushing the chunk terminator just because the previous
// chunk was large if the previous chunk was flushed quickly.
// Don't add any grace period to this accumulated timeout because the grace period could
// get accumulated repeatedly making the timeout for a bunch of consecutive small writes
// far too conservative.
var accumulatedWriteTimeoutTimestamp = _writeTimingTimeoutTimestamp + ticksToCompleteWriteAtMinRate;
_writeTimingTimeoutTimestamp = Math.Max(singleWriteTimeoutTimestamp, accumulatedWriteTimeoutTimestamp);
_writeTimingWrites++;
}
}
public void StopTimingWrite()
{
lock (_writeTimingLock)
{
_writeTimingWrites--;
}
}
void IConnectionTimeoutFeature.SetTimeout(TimeSpan timeSpan)
{
if (timeSpan < TimeSpan.Zero)
{
throw new ArgumentException(CoreStrings.PositiveFiniteTimeSpanRequired, nameof(timeSpan));
}
if (_timeoutTimestamp != long.MaxValue)
{
throw new InvalidOperationException(CoreStrings.ConcurrentTimeoutsNotSupported);
}
SetTimeout(timeSpan.Ticks, TimeoutReason.TimeoutFeature);
}
void IConnectionTimeoutFeature.ResetTimeout(TimeSpan timeSpan)
{
if (timeSpan < TimeSpan.Zero)
{
throw new ArgumentException(CoreStrings.PositiveFiniteTimeSpanRequired, nameof(timeSpan));
}
ResetTimeout(timeSpan.Ticks, TimeoutReason.TimeoutFeature);
}
}
}

View File

@ -0,0 +1,24 @@
// 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.
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
{
public static class TimeoutControlExtensions
{
public static void StartDrainTimeout(this ITimeoutControl timeoutControl, MinDataRate minDataRate, long? maxResponseBufferSize)
{
// If maxResponseBufferSize has no value, there's no backpressure and we can't reasonably timeout draining.
if (minDataRate == null || maxResponseBufferSize == null)
{
return;
}
// With full backpressure and a connection adapter there could be 2 two pipes buffering.
// We already validate that the buffer size is positive.
// There's no reason to stop timing the write after the connection is closed.
var oneBufferSize = maxResponseBufferSize.Value;
var maxBufferedBytes = oneBufferSize < long.MaxValue / 2 ? oneBufferSize * 2 : long.MaxValue;
timeoutControl.StartTimingWrite(minDataRate, maxBufferedBytes);
}
}
}

View File

@ -12,9 +12,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
{
/// <summary>
/// This wraps PipeWriter.FlushAsync() in a way that allows multiple awaiters making it safe to call from publicly
/// exposed Stream implementations.
/// exposed Stream implementations while also tracking response data rate.
/// </summary>
public class StreamSafePipeFlusher
public class TimingPipeFlusher
{
private readonly PipeWriter _writer;
private readonly ITimeoutControl _timeoutControl;
@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
private Task _lastFlushTask = Task.CompletedTask;
public StreamSafePipeFlusher(
public TimingPipeFlusher(
PipeWriter writer,
ITimeoutControl timeoutControl)
{
@ -30,7 +30,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
_timeoutControl = timeoutControl;
}
public Task FlushAsync(long count = 0, IHttpOutputProducer outputProducer = null, CancellationToken cancellationToken = default)
public Task FlushAsync()
{
return FlushAsync(outputAborter: null, cancellationToken: default);
}
public Task FlushAsync(IHttpOutputAborter outputAborter, CancellationToken cancellationToken)
{
return FlushAsync(minRate: null, count: 0, outputAborter: outputAborter, cancellationToken: cancellationToken);
}
public Task FlushAsync(MinDataRate minRate, long count, IHttpOutputAborter outputAborter, CancellationToken cancellationToken)
{
var flushValueTask = _writer.FlushAsync(cancellationToken);
@ -51,13 +61,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
_lastFlushTask = flushValueTask.AsTask();
}
return TimeFlushAsync(count, outputProducer, cancellationToken);
return TimeFlushAsync(minRate, count, outputAborter, cancellationToken);
}
}
private async Task TimeFlushAsync(long count, IHttpOutputProducer outputProducer, CancellationToken cancellationToken)
private async Task TimeFlushAsync(MinDataRate minRate, long count, IHttpOutputAborter outputAborter, CancellationToken cancellationToken)
{
_timeoutControl.StartTimingWrite(count);
if (minRate != null)
{
_timeoutControl.StartTimingWrite(minRate, count);
}
try
{
@ -65,14 +78,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
}
catch (OperationCanceledException ex)
{
outputProducer.Abort(new ConnectionAbortedException(CoreStrings.ConnectionOrStreamAbortedByCancellationToken, ex));
outputAborter.Abort(new ConnectionAbortedException(CoreStrings.ConnectionOrStreamAbortedByCancellationToken, ex));
}
catch
{
// A canceled token is the only reason flush should ever throw.
}
_timeoutControl.StopTimingWrite();
if (minRate != null)
{
_timeoutControl.StopTimingWrite();
}
cancellationToken.ThrowIfCancellationRequested();
}

View File

@ -2184,6 +2184,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
internal static string FormatHPackErrorIntegerTooBig()
=> GetString("HPackErrorIntegerTooBig");
/// <summary>
/// The client closed the connection.
/// </summary>
internal static string ConnectionAbortedByClient
{
get => GetString("ConnectionAbortedByClient");
}
/// <summary>
/// The client closed the connection.
/// </summary>
internal static string FormatConnectionAbortedByClient()
=> GetString("ConnectionAbortedByClient");
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
{
"AssemblyIdentity": "Microsoft.AspNetCore.Server.Kestrel.Https, Version=2.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
"AssemblyIdentity": "Microsoft.AspNetCore.Server.Kestrel.Https, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
"Types": [
{
"Name": "Microsoft.AspNetCore.Hosting.ListenOptionsHttpsExtensions",
@ -10,6 +10,21 @@
"Sealed": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "UseHttps",
"Parameters": [
{
"Name": "listenOptions",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions"
}
],
"ReturnType": "Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "UseHttps",
@ -52,6 +67,149 @@
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "UseHttps",
"Parameters": [
{
"Name": "listenOptions",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions"
},
{
"Name": "fileName",
"Type": "System.String"
},
{
"Name": "password",
"Type": "System.String"
},
{
"Name": "configureOptions",
"Type": "System.Action<Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionAdapterOptions>"
}
],
"ReturnType": "Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "UseHttps",
"Parameters": [
{
"Name": "listenOptions",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions"
},
{
"Name": "storeName",
"Type": "System.Security.Cryptography.X509Certificates.StoreName"
},
{
"Name": "subject",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "UseHttps",
"Parameters": [
{
"Name": "listenOptions",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions"
},
{
"Name": "storeName",
"Type": "System.Security.Cryptography.X509Certificates.StoreName"
},
{
"Name": "subject",
"Type": "System.String"
},
{
"Name": "allowInvalid",
"Type": "System.Boolean"
}
],
"ReturnType": "Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "UseHttps",
"Parameters": [
{
"Name": "listenOptions",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions"
},
{
"Name": "storeName",
"Type": "System.Security.Cryptography.X509Certificates.StoreName"
},
{
"Name": "subject",
"Type": "System.String"
},
{
"Name": "allowInvalid",
"Type": "System.Boolean"
},
{
"Name": "location",
"Type": "System.Security.Cryptography.X509Certificates.StoreLocation"
}
],
"ReturnType": "Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "UseHttps",
"Parameters": [
{
"Name": "listenOptions",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions"
},
{
"Name": "storeName",
"Type": "System.Security.Cryptography.X509Certificates.StoreName"
},
{
"Name": "subject",
"Type": "System.String"
},
{
"Name": "allowInvalid",
"Type": "System.Boolean"
},
{
"Name": "location",
"Type": "System.Security.Cryptography.X509Certificates.StoreLocation"
},
{
"Name": "configureOptions",
"Type": "System.Action<Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionAdapterOptions>"
}
],
"ReturnType": "Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "UseHttps",
@ -71,6 +229,48 @@
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "UseHttps",
"Parameters": [
{
"Name": "listenOptions",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions"
},
{
"Name": "serverCertificate",
"Type": "System.Security.Cryptography.X509Certificates.X509Certificate2"
},
{
"Name": "configureOptions",
"Type": "System.Action<Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionAdapterOptions>"
}
],
"ReturnType": "Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "UseHttps",
"Parameters": [
{
"Name": "listenOptions",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions"
},
{
"Name": "configureOptions",
"Type": "System.Action<Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionAdapterOptions>"
}
],
"ReturnType": "Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "UseHttps",
@ -151,6 +351,27 @@
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_ServerCertificateSelector",
"Parameters": [],
"ReturnType": "System.Func<Microsoft.AspNetCore.Connections.ConnectionContext, System.String, System.Security.Cryptography.X509Certificates.X509Certificate2>",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_ServerCertificateSelector",
"Parameters": [
{
"Name": "value",
"Type": "System.Func<Microsoft.AspNetCore.Connections.ConnectionContext, System.String, System.Security.Cryptography.X509Certificates.X509Certificate2>"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_ClientCertificateMode",
@ -235,6 +456,27 @@
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_HandshakeTimeout",
"Parameters": [],
"ReturnType": "System.TimeSpan",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_HandshakeTimeout",
"Parameters": [
{
"Name": "value",
"Type": "System.TimeSpan"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",

View File

@ -1,13 +0,0 @@
// 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;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
{
public interface IBytesWrittenFeature
{
long TotalBytesWritten { get; }
}
}

View File

@ -21,8 +21,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
ITransportSchedulerFeature,
IConnectionLifetimeFeature,
IConnectionHeartbeatFeature,
IConnectionLifetimeNotificationFeature,
IBytesWrittenFeature
IConnectionLifetimeNotificationFeature
{
// NOTE: When feature interfaces are added to or removed from this TransportConnection class implementation,
// then the list of `features` in the generated code project MUST also be updated.
@ -101,7 +100,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
{
OnHeartbeat(action, state);
}
long IBytesWrittenFeature.TotalBytesWritten => TotalBytesWritten;
}
}

View File

@ -22,7 +22,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
private static readonly Type IConnectionLifetimeFeatureType = typeof(IConnectionLifetimeFeature);
private static readonly Type IConnectionHeartbeatFeatureType = typeof(IConnectionHeartbeatFeature);
private static readonly Type IConnectionLifetimeNotificationFeatureType = typeof(IConnectionLifetimeNotificationFeature);
private static readonly Type IBytesWrittenFeatureType = typeof(IBytesWrittenFeature);
private object _currentIHttpConnectionFeature;
private object _currentIConnectionIdFeature;
@ -34,7 +33,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
private object _currentIConnectionLifetimeFeature;
private object _currentIConnectionHeartbeatFeature;
private object _currentIConnectionLifetimeNotificationFeature;
private object _currentIBytesWrittenFeature;
private int _featureRevision;
@ -52,7 +50,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
_currentIConnectionLifetimeFeature = this;
_currentIConnectionHeartbeatFeature = this;
_currentIConnectionLifetimeNotificationFeature = this;
_currentIBytesWrittenFeature = this;
}
@ -148,10 +145,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
{
feature = _currentIConnectionLifetimeNotificationFeature;
}
else if (key == IBytesWrittenFeatureType)
{
feature = _currentIBytesWrittenFeature;
}
else if (MaybeExtra != null)
{
feature = ExtraFeatureGet(key);
@ -204,10 +197,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
{
_currentIConnectionLifetimeNotificationFeature = value;
}
else if (key == IBytesWrittenFeatureType)
{
_currentIBytesWrittenFeature = value;
}
else
{
ExtraFeatureSet(key, value);
@ -258,10 +247,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
{
feature = (TFeature)_currentIConnectionLifetimeNotificationFeature;
}
else if (typeof(TFeature) == typeof(IBytesWrittenFeature))
{
feature = (TFeature)_currentIBytesWrittenFeature;
}
else if (MaybeExtra != null)
{
feature = (TFeature)(ExtraFeatureGet(typeof(TFeature)));
@ -313,10 +298,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
{
_currentIConnectionLifetimeNotificationFeature = feature;
}
else if (typeof(TFeature) == typeof(IBytesWrittenFeature))
{
_currentIBytesWrittenFeature = feature;
}
else
{
ExtraFeatureSet(typeof(TFeature), feature);
@ -365,10 +346,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
{
yield return new KeyValuePair<Type, object>(IConnectionLifetimeNotificationFeatureType, _currentIConnectionLifetimeNotificationFeature);
}
if (_currentIBytesWrittenFeature != null)
{
yield return new KeyValuePair<Type, object>(IBytesWrittenFeatureType, _currentIBytesWrittenFeature);
}
if (MaybeExtra != null)
{

View File

@ -38,7 +38,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
public virtual MemoryPool<byte> MemoryPool { get; }
public virtual PipeScheduler InputWriterScheduler { get; }
public virtual PipeScheduler OutputReaderScheduler { get; }
public virtual long TotalBytesWritten { get; }
public override IDuplexPipe Transport { get; set; }
public IDuplexPipe Application { get; set; }

View File

@ -1,4 +1,4 @@
{
"AssemblyIdentity": "Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions, Version=2.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
"AssemblyIdentity": "Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
"Types": []
}

View File

@ -54,8 +54,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
public override PipeScheduler InputWriterScheduler => Thread;
public override PipeScheduler OutputReaderScheduler => Thread;
public override long TotalBytesWritten => OutputConsumer?.TotalBytesWritten ?? 0;
public async Task Start()
{
try
@ -98,7 +96,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
finally
{
// Now, complete the input so that no more reads can happen
Input.Complete(inputError ?? _abortReason ?? new ConnectionAbortedException());
Input.Complete(inputError ?? _abortReason ?? new ConnectionAbortedException("The libuv transport's send loop completed gracefully."));
Output.Complete(outputError);
// Make sure it isn't possible for a paused read to resume reading after calling uv_close

View File

@ -17,8 +17,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
private readonly ILibuvTrace _log;
private readonly PipeReader _pipe;
private long _totalBytesWritten;
public LibuvOutputConsumer(
PipeReader pipe,
LibuvThread thread,
@ -33,8 +31,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
_log = log;
}
public long TotalBytesWritten => Interlocked.Read(ref _totalBytesWritten);
public async Task WriteOutputAsync()
{
var pool = _thread.WriteReqPool;
@ -66,10 +62,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
var writeResult = await writeReq.WriteAsync(_socket, buffer);
// This is not interlocked because there could be a concurrent writer.
// Instead it's to prevent read tearing on 32-bit systems.
Interlocked.Add(ref _totalBytesWritten, buffer.Length);
LogWriteInfo(writeResult.Status, writeResult.Error);
if (writeResult.Error != null)

View File

@ -1,5 +1,5 @@
{
"AssemblyIdentity": "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv, Version=2.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
"AssemblyIdentity": "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
"Types": [
{
"Name": "Microsoft.AspNetCore.Hosting.WebHostBuilderLibuvExtensions",
@ -47,185 +47,6 @@
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.LibuvTransport",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [
"Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.ITransport"
],
"Members": [
{
"Kind": "Method",
"Name": "get_Libuv",
"Parameters": [],
"ReturnType": "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking.LibuvFunctions",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_TransportContext",
"Parameters": [],
"ReturnType": "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.LibuvTransportContext",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_Threads",
"Parameters": [],
"ReturnType": "System.Collections.Generic.List<Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.LibuvThread>",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_AppLifetime",
"Parameters": [],
"ReturnType": "Microsoft.AspNetCore.Hosting.IApplicationLifetime",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_Log",
"Parameters": [],
"ReturnType": "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.ILibuvTrace",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_TransportOptions",
"Parameters": [],
"ReturnType": "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.LibuvTransportOptions",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "StopAsync",
"Parameters": [],
"ReturnType": "System.Threading.Tasks.Task",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.ITransport",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "BindAsync",
"Parameters": [],
"ReturnType": "System.Threading.Tasks.Task",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.ITransport",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "UnbindAsync",
"Parameters": [],
"ReturnType": "System.Threading.Tasks.Task",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.ITransport",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "context",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.LibuvTransportContext"
},
{
"Name": "endPointInformation",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.IEndPointInformation"
}
],
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "uv",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking.LibuvFunctions"
},
{
"Name": "context",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.LibuvTransportContext"
},
{
"Name": "endPointInformation",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.IEndPointInformation"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.LibuvTransportFactory",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [
"Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.ITransportFactory"
],
"Members": [
{
"Kind": "Method",
"Name": "Create",
"Parameters": [
{
"Name": "endPointInformation",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.IEndPointInformation"
},
{
"Name": "handler",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.IConnectionHandler"
}
],
"ReturnType": "Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.ITransport",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.ITransportFactory",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "options",
"Type": "Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.LibuvTransportOptions>"
},
{
"Name": "applicationLifetime",
"Type": "Microsoft.AspNetCore.Hosting.IApplicationLifetime"
},
{
"Name": "loggerFactory",
"Type": "Microsoft.Extensions.Logging.ILoggerFactory"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.LibuvTransportOptions",
"Visibility": "Public",

View File

@ -1,10 +0,0 @@
[
{
"TypeId": "public class Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.LibuvTransport : Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.ITransport",
"Kind": "Removal"
},
{
"TypeId": "public class Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.LibuvTransportFactory : Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.ITransportFactory",
"Kind": "Removal"
}
]

View File

@ -32,7 +32,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
private readonly object _shutdownLock = new object();
private volatile bool _socketDisposed;
private volatile Exception _shutdownReason;
private long _totalBytesWritten;
internal SocketConnection(Socket socket, MemoryPool<byte> memoryPool, PipeScheduler scheduler, ISocketsTrace trace)
{
@ -68,7 +67,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
public override MemoryPool<byte> MemoryPool { get; }
public override PipeScheduler InputWriterScheduler => _scheduler;
public override PipeScheduler OutputReaderScheduler => _scheduler;
public override long TotalBytesWritten => Interlocked.Read(ref _totalBytesWritten);
public async Task StartAsync()
{
@ -264,10 +262,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
await _sender.SendAsync(buffer);
}
// This is not interlocked because there could be a concurrent writer.
// Instead it's to prevent read tearing on 32-bit systems.
Interlocked.Add(ref _totalBytesWritten, buffer.Length);
Output.AdvanceTo(end);
if (isCompleted)
@ -294,7 +288,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
// shutdownReason should only be null if the output was completed gracefully, so no one should ever
// ever observe the nondescript ConnectionAbortedException except for connection middleware attempting
// to half close the connection which is currently unsupported.
_shutdownReason = shutdownReason ?? new ConnectionAbortedException();
_shutdownReason = shutdownReason ?? new ConnectionAbortedException("The Socket transport's send loop completed gracefully.");
_trace.ConnectionWriteFin(ConnectionId);

View File

@ -1,2 +1,140 @@
{
"AssemblyIdentity": "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
"Types": [
{
"Name": "Microsoft.AspNetCore.Hosting.WebHostBuilderSocketExtensions",
"Visibility": "Public",
"Kind": "Class",
"Abstract": true,
"Static": true,
"Sealed": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "UseSockets",
"Parameters": [
{
"Name": "hostBuilder",
"Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
}
],
"ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "UseSockets",
"Parameters": [
{
"Name": "hostBuilder",
"Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
},
{
"Name": "configureOptions",
"Type": "System.Action<Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportOptions>"
}
],
"ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportFactory",
"Visibility": "Public",
"Kind": "Class",
"Sealed": true,
"ImplementedInterfaces": [
"Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.ITransportFactory"
],
"Members": [
{
"Kind": "Method",
"Name": "Create",
"Parameters": [
{
"Name": "endPointInformation",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.IEndPointInformation"
},
{
"Name": "dispatcher",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.IConnectionDispatcher"
}
],
"ReturnType": "Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.ITransport",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.ITransportFactory",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "options",
"Type": "Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportOptions>"
},
{
"Name": "applicationLifetime",
"Type": "Microsoft.AspNetCore.Hosting.IApplicationLifetime"
},
{
"Name": "loggerFactory",
"Type": "Microsoft.Extensions.Logging.ILoggerFactory"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportOptions",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "get_IOQueueCount",
"Parameters": [],
"ReturnType": "System.Int32",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_IOQueueCount",
"Parameters": [
{
"Name": "value",
"Type": "System.Int32"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
}
]
}

View File

@ -1,5 +1,5 @@
{
"AssemblyIdentity": "Microsoft.AspNetCore.Server.Kestrel, Version=2.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
"AssemblyIdentity": "Microsoft.AspNetCore.Server.Kestrel, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
"Types": [
{
"Name": "Microsoft.AspNetCore.Hosting.WebHostBuilderKestrelExtensions",
@ -43,6 +43,25 @@
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "UseKestrel",
"Parameters": [
{
"Name": "hostBuilder",
"Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
},
{
"Name": "configureOptions",
"Type": "System.Action<Microsoft.AspNetCore.Hosting.WebHostBuilderContext, Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions>"
}
],
"ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []

View File

@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
private readonly IDuplexPipe _application;
private readonly TestHttp1Connection _http1Connection;
private readonly ServiceContext _serviceContext;
private readonly Http1ConnectionContext _http1ConnectionContext;
private readonly HttpConnectionContext _http1ConnectionContext;
private readonly MemoryPool<byte> _pipelineFactory;
private SequencePosition _consumed;
private SequencePosition _examined;
@ -52,11 +52,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var connectionFeatures = new FeatureCollection();
connectionFeatures.Set(Mock.Of<IConnectionLifetimeFeature>());
connectionFeatures.Set(Mock.Of<IBytesWrittenFeature>());
_serviceContext = new TestServiceContext();
_timeoutControl = new Mock<ITimeoutControl>();
_http1ConnectionContext = new Http1ConnectionContext
_http1ConnectionContext = new HttpConnectionContext
{
ServiceContext = _serviceContext,
ConnectionContext = Mock.Of<ConnectionContext>(),
@ -425,7 +424,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
_transport.Input.AdvanceTo(_consumed, _examined);
var expectedRequestHeadersTimeout = _serviceContext.ServerOptions.Limits.RequestHeadersTimeout.Ticks;
_timeoutControl.Verify(cc => cc.ResetTimeout(expectedRequestHeadersTimeout, TimeoutAction.SendTimeoutResponse));
_timeoutControl.Verify(cc => cc.ResetTimeout(expectedRequestHeadersTimeout, TimeoutReason.RequestHeaders));
}
[Fact]
@ -542,7 +541,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var requestProcessingTask = _http1Connection.ProcessRequestsAsync<object>(null);
var expectedKeepAliveTimeout = _serviceContext.ServerOptions.Limits.KeepAliveTimeout.Ticks;
_timeoutControl.Verify(cc => cc.SetTimeout(expectedKeepAliveTimeout, TimeoutAction.StopProcessingNextRequest));
_timeoutControl.Verify(cc => cc.SetTimeout(expectedKeepAliveTimeout, TimeoutReason.KeepAlive));
_http1Connection.StopProcessingNextRequest();
_application.Output.Complete();

View File

@ -1,592 +1,49 @@
// 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.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO.Pipelines;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
using Microsoft.AspNetCore.Testing;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
public class HttpConnectionTests : IDisposable
public class HttpConnectionTests
{
private readonly MemoryPool<byte> _memoryPool;
private readonly HttpConnectionContext _httpConnectionContext;
private readonly HttpConnection _httpConnection;
public HttpConnectionTests()
[Fact]
public async Task WriteDataRateTimeoutAbortsConnection()
{
_memoryPool = KestrelMemoryPool.Create();
var options = new PipeOptions(_memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
var pair = DuplexPipe.CreateConnectionPair(options, options);
var mockConnectionContext = new Mock<ConnectionContext>();
var connectionFeatures = new FeatureCollection();
connectionFeatures.Set(Mock.Of<IConnectionLifetimeFeature>());
connectionFeatures.Set(Mock.Of<IBytesWrittenFeature>());
_httpConnectionContext = new HttpConnectionContext
var httpConnectionContext = new HttpConnectionContext
{
ConnectionId = "0123456789",
ConnectionContext = Mock.Of<ConnectionContext>(),
ConnectionAdapters = new List<IConnectionAdapter>(),
ConnectionFeatures = connectionFeatures,
MemoryPool = _memoryPool,
Transport = pair.Transport,
ServiceContext = new TestServiceContext
{
SystemClock = new SystemClock()
}
ConnectionContext = mockConnectionContext.Object,
Transport = new DuplexPipe(Mock.Of<PipeReader>(), Mock.Of<PipeWriter>()),
ServiceContext = new TestServiceContext()
};
_httpConnection = new HttpConnection(_httpConnectionContext);
}
var httpConnection = new HttpConnection(httpConnectionContext);
public void Dispose()
{
_memoryPool.Dispose();
}
[Fact]
public void DoesNotTimeOutWhenDebuggerIsAttached()
{
var mockDebugger = new Mock<IDebugger>();
mockDebugger.SetupGet(g => g.IsAttached).Returns(true);
_httpConnection.Debugger = mockDebugger.Object;
_httpConnection.Initialize(_httpConnectionContext.Transport);
var now = DateTimeOffset.Now;
_httpConnection.Tick(now);
_httpConnection.SetTimeout(1, TimeoutAction.SendTimeoutResponse);
_httpConnection.Tick(now.AddTicks(2).Add(Heartbeat.Interval));
Assert.False(_httpConnection.RequestTimedOut);
}
[Fact]
public void DoesNotTimeOutWhenRequestBodyDoesNotSatisfyMinimumDataRateButDebuggerIsAttached()
{
var mockDebugger = new Mock<IDebugger>();
mockDebugger.SetupGet(g => g.IsAttached).Returns(true);
_httpConnection.Debugger = mockDebugger.Object;
var bytesPerSecond = 100;
var mockLogger = new Mock<IKestrelTrace>();
mockLogger.Setup(l => l.RequestBodyMinimumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<double>())).Throws(new InvalidOperationException("Should not log"));
TickBodyWithMinimumDataRate(mockLogger.Object, bytesPerSecond);
Assert.False(_httpConnection.RequestTimedOut);
}
[Fact]
public void TimesOutWhenRequestBodyDoesNotSatisfyMinimumDataRate()
{
var bytesPerSecond = 100;
var mockLogger = new Mock<IKestrelTrace>();
TickBodyWithMinimumDataRate(mockLogger.Object, bytesPerSecond);
// Timed out
Assert.True(_httpConnection.RequestTimedOut);
mockLogger.Verify(logger =>
logger.RequestBodyMinimumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), bytesPerSecond), Times.Once);
}
private void TickBodyWithMinimumDataRate(IKestrelTrace logger, int bytesPerSecond)
{
var gracePeriod = TimeSpan.FromSeconds(5);
_httpConnectionContext.ServiceContext.ServerOptions.Limits.MinRequestBodyDataRate =
new MinDataRate(bytesPerSecond: bytesPerSecond, gracePeriod: gracePeriod);
_httpConnectionContext.ServiceContext.Log = logger;
_httpConnection.Initialize(_httpConnectionContext.Transport);
_httpConnection.Http1Connection.Reset();
// Initialize timestamp
var now = DateTimeOffset.UtcNow;
_httpConnection.Tick(now);
_httpConnection.StartTimingReads();
// Tick after grace period w/ low data rate
now += gracePeriod + TimeSpan.FromSeconds(1);
_httpConnection.BytesRead(1);
_httpConnection.Tick(now);
}
[Fact]
public void RequestBodyMinimumDataRateNotEnforcedDuringGracePeriod()
{
var bytesPerSecond = 100;
var gracePeriod = TimeSpan.FromSeconds(2);
_httpConnectionContext.ServiceContext.ServerOptions.Limits.MinRequestBodyDataRate =
new MinDataRate(bytesPerSecond: bytesPerSecond, gracePeriod: gracePeriod);
var mockLogger = new Mock<IKestrelTrace>();
_httpConnectionContext.ServiceContext.Log = mockLogger.Object;
_httpConnection.Initialize(_httpConnectionContext.Transport);
_httpConnection.Http1Connection.Reset();
// Initialize timestamp
var now = DateTimeOffset.UtcNow;
_httpConnection.Tick(now);
_httpConnection.StartTimingReads();
// Tick during grace period w/ low data rate
now += TimeSpan.FromSeconds(1);
_httpConnection.BytesRead(10);
_httpConnection.Tick(now);
// Not timed out
Assert.False(_httpConnection.RequestTimedOut);
mockLogger.Verify(logger =>
logger.RequestBodyMinimumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), bytesPerSecond), Times.Never);
// Tick after grace period w/ low data rate
now += TimeSpan.FromSeconds(2);
_httpConnection.BytesRead(10);
_httpConnection.Tick(now);
// Timed out
Assert.True(_httpConnection.RequestTimedOut);
mockLogger.Verify(logger =>
logger.RequestBodyMinimumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), bytesPerSecond), Times.Once);
}
[Fact]
public void RequestBodyDataRateIsAveragedOverTimeSpentReadingRequestBody()
{
var bytesPerSecond = 100;
var gracePeriod = TimeSpan.FromSeconds(2);
_httpConnectionContext.ServiceContext.ServerOptions.Limits.MinRequestBodyDataRate =
new MinDataRate(bytesPerSecond: bytesPerSecond, gracePeriod: gracePeriod);
var mockLogger = new Mock<IKestrelTrace>();
_httpConnectionContext.ServiceContext.Log = mockLogger.Object;
_httpConnection.Initialize(_httpConnectionContext.Transport);
_httpConnection.Http1Connection.Reset();
// Initialize timestamp
var now = DateTimeOffset.UtcNow;
_httpConnection.Tick(now);
_httpConnection.StartTimingReads();
// Set base data rate to 200 bytes/second
now += gracePeriod;
_httpConnection.BytesRead(400);
_httpConnection.Tick(now);
// Data rate: 200 bytes/second
now += TimeSpan.FromSeconds(1);
_httpConnection.BytesRead(200);
_httpConnection.Tick(now);
// Not timed out
Assert.False(_httpConnection.RequestTimedOut);
mockLogger.Verify(logger =>
logger.RequestBodyMinimumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), bytesPerSecond), Times.Never);
// Data rate: 150 bytes/second
now += TimeSpan.FromSeconds(1);
_httpConnection.BytesRead(0);
_httpConnection.Tick(now);
// Not timed out
Assert.False(_httpConnection.RequestTimedOut);
mockLogger.Verify(logger =>
logger.RequestBodyMinimumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), bytesPerSecond), Times.Never);
// Data rate: 120 bytes/second
now += TimeSpan.FromSeconds(1);
_httpConnection.BytesRead(0);
_httpConnection.Tick(now);
// Not timed out
Assert.False(_httpConnection.RequestTimedOut);
mockLogger.Verify(logger =>
logger.RequestBodyMinimumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), bytesPerSecond), Times.Never);
// Data rate: 100 bytes/second
now += TimeSpan.FromSeconds(1);
_httpConnection.BytesRead(0);
_httpConnection.Tick(now);
// Not timed out
Assert.False(_httpConnection.RequestTimedOut);
mockLogger.Verify(logger =>
logger.RequestBodyMinimumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), bytesPerSecond), Times.Never);
// Data rate: ~85 bytes/second
now += TimeSpan.FromSeconds(1);
_httpConnection.BytesRead(0);
_httpConnection.Tick(now);
// Timed out
Assert.True(_httpConnection.RequestTimedOut);
mockLogger.Verify(logger =>
logger.RequestBodyMinimumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), bytesPerSecond), Times.Once);
}
[Fact]
public void RequestBodyDataRateNotComputedOnPausedTime()
{
var systemClock = new MockSystemClock();
_httpConnectionContext.ServiceContext.ServerOptions.Limits.MinRequestBodyDataRate =
new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2));
_httpConnectionContext.ServiceContext.SystemClock = systemClock;
var mockLogger = new Mock<IKestrelTrace>();
_httpConnectionContext.ServiceContext.Log = mockLogger.Object;
_httpConnection.Initialize(_httpConnectionContext.Transport);
_httpConnection.Http1Connection.Reset();
// Initialize timestamp
_httpConnection.Tick(systemClock.UtcNow);
_httpConnection.StartTimingReads();
// Tick at 3s, expected counted time is 3s, expected data rate is 200 bytes/second
systemClock.UtcNow += TimeSpan.FromSeconds(3);
_httpConnection.BytesRead(600);
_httpConnection.Tick(systemClock.UtcNow);
// Pause at 3.5s
systemClock.UtcNow += TimeSpan.FromSeconds(0.5);
_httpConnection.PauseTimingReads();
// Tick at 4s, expected counted time is 4s (first tick after pause goes through), expected data rate is 150 bytes/second
systemClock.UtcNow += TimeSpan.FromSeconds(0.5);
_httpConnection.Tick(systemClock.UtcNow);
// Tick at 6s, expected counted time is 4s, expected data rate is 150 bytes/second
systemClock.UtcNow += TimeSpan.FromSeconds(2);
_httpConnection.Tick(systemClock.UtcNow);
// Not timed out
Assert.False(_httpConnection.RequestTimedOut);
mockLogger.Verify(
logger => logger.RequestBodyMinimumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<double>()),
Times.Never);
// Resume at 6.5s
systemClock.UtcNow += TimeSpan.FromSeconds(0.5);
_httpConnection.ResumeTimingReads();
// Tick at 9s, expected counted time is 6s, expected data rate is 100 bytes/second
systemClock.UtcNow += TimeSpan.FromSeconds(1.5);
_httpConnection.Tick(systemClock.UtcNow);
// Not timed out
Assert.False(_httpConnection.RequestTimedOut);
mockLogger.Verify(
logger => logger.RequestBodyMinimumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<double>()),
Times.Never);
// Tick at 10s, expected counted time is 7s, expected data rate drops below 100 bytes/second
systemClock.UtcNow += TimeSpan.FromSeconds(1);
_httpConnection.Tick(systemClock.UtcNow);
// Timed out
Assert.True(_httpConnection.RequestTimedOut);
mockLogger.Verify(
logger => logger.RequestBodyMinimumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<double>()),
Times.Once);
}
[Fact]
public void ReadTimingNotPausedWhenResumeCalledBeforeNextTick()
{
var systemClock = new MockSystemClock();
_httpConnectionContext.ServiceContext.ServerOptions.Limits.MinRequestBodyDataRate =
new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2));
_httpConnectionContext.ServiceContext.SystemClock = systemClock;
var mockLogger = new Mock<IKestrelTrace>();
_httpConnectionContext.ServiceContext.Log = mockLogger.Object;
_httpConnection.Initialize(_httpConnectionContext.Transport);
_httpConnection.Http1Connection.Reset();
// Initialize timestamp
_httpConnection.Tick(systemClock.UtcNow);
_httpConnection.StartTimingReads();
// Tick at 2s, expected counted time is 2s, expected data rate is 100 bytes/second
systemClock.UtcNow += TimeSpan.FromSeconds(2);
_httpConnection.BytesRead(200);
_httpConnection.Tick(systemClock.UtcNow);
// Not timed out
Assert.False(_httpConnection.RequestTimedOut);
mockLogger.Verify(
logger => logger.RequestBodyMinimumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<double>()),
Times.Never);
// Pause at 2.25s
systemClock.UtcNow += TimeSpan.FromSeconds(0.25);
_httpConnection.PauseTimingReads();
// Resume at 2.5s
systemClock.UtcNow += TimeSpan.FromSeconds(0.25);
_httpConnection.ResumeTimingReads();
// Tick at 3s, expected counted time is 3s, expected data rate is 100 bytes/second
systemClock.UtcNow += TimeSpan.FromSeconds(0.5);
_httpConnection.BytesRead(100);
_httpConnection.Tick(systemClock.UtcNow);
// Not timed out
Assert.False(_httpConnection.RequestTimedOut);
mockLogger.Verify(
logger => logger.RequestBodyMinimumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<double>()),
Times.Never);
// Tick at 4s, expected counted time is 4s, expected data rate drops below 100 bytes/second
systemClock.UtcNow += TimeSpan.FromSeconds(1);
_httpConnection.Tick(systemClock.UtcNow);
// Timed out
Assert.True(_httpConnection.RequestTimedOut);
mockLogger.Verify(
logger => logger.RequestBodyMinimumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<double>()),
Times.Once);
}
[Fact]
public void ReadTimingNotEnforcedWhenTimeoutIsSet()
{
var systemClock = new MockSystemClock();
var timeout = TimeSpan.FromSeconds(5);
_httpConnectionContext.ServiceContext.ServerOptions.Limits.MinRequestBodyDataRate =
new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2));
_httpConnectionContext.ServiceContext.SystemClock = systemClock;
var mockLogger = new Mock<IKestrelTrace>();
_httpConnectionContext.ServiceContext.Log = mockLogger.Object;
_httpConnection.Initialize(_httpConnectionContext.Transport);
_httpConnection.Http1Connection.Reset();
var startTime = systemClock.UtcNow;
// Initialize timestamp
_httpConnection.Tick(startTime);
_httpConnection.StartTimingReads();
_httpConnection.SetTimeout(timeout.Ticks, TimeoutAction.StopProcessingNextRequest);
// Tick beyond grace period with low data rate
systemClock.UtcNow += TimeSpan.FromSeconds(3);
_httpConnection.BytesRead(1);
_httpConnection.Tick(systemClock.UtcNow);
// Not timed out
Assert.False(_httpConnection.RequestTimedOut);
// Tick just past timeout period, adjusted by Heartbeat.Interval
systemClock.UtcNow = startTime + timeout + Heartbeat.Interval + TimeSpan.FromTicks(1);
_httpConnection.Tick(systemClock.UtcNow);
// Timed out
Assert.True(_httpConnection.RequestTimedOut);
}
[Fact]
public async Task WriteTimingAbortsConnectionWhenWriteDoesNotCompleteWithMinimumDataRate()
{
var systemClock = new MockSystemClock();
var aborted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
_httpConnectionContext.ServiceContext.ServerOptions.Limits.MinResponseDataRate =
new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2));
_httpConnectionContext.ServiceContext.SystemClock = systemClock;
var mockLogger = new Mock<IKestrelTrace>();
_httpConnectionContext.ServiceContext.Log = mockLogger.Object;
_httpConnection.Initialize(_httpConnectionContext.Transport);
_httpConnection.Http1Connection.Reset();
_httpConnection.Http1Connection.RequestAborted.Register(() =>
httpConnection.Initialize(httpConnectionContext.Transport);
httpConnection.Http1Connection.Reset();
httpConnection.Http1Connection.RequestAborted.Register(() =>
{
aborted.SetResult(null);
});
// Initialize timestamp
_httpConnection.Tick(systemClock.UtcNow);
httpConnection.OnTimeout(TimeoutReason.WriteDataRate);
// Should complete within 4 seconds, but the timeout is adjusted by adding Heartbeat.Interval
_httpConnection.StartTimingWrite(400);
mockConnectionContext
.Verify(c => c.Abort(It.Is<ConnectionAbortedException>(ex => ex.Message == CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)),
Times.Once);
// Tick just past 4s plus Heartbeat.Interval
systemClock.UtcNow += TimeSpan.FromSeconds(4) + Heartbeat.Interval + TimeSpan.FromTicks(1);
_httpConnection.Tick(systemClock.UtcNow);
Assert.True(_httpConnection.RequestTimedOut);
await aborted.Task.DefaultTimeout();
}
[Fact]
public async Task WriteTimingAbortsConnectionWhenSmallWriteDoesNotCompleteWithinGracePeriod()
{
var systemClock = new MockSystemClock();
var minResponseDataRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(5));
var aborted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
_httpConnectionContext.ServiceContext.ServerOptions.Limits.MinResponseDataRate = minResponseDataRate;
_httpConnectionContext.ServiceContext.SystemClock = systemClock;
var mockLogger = new Mock<IKestrelTrace>();
_httpConnectionContext.ServiceContext.Log = mockLogger.Object;
_httpConnection.Initialize(_httpConnectionContext.Transport);
_httpConnection.Http1Connection.Reset();
_httpConnection.Http1Connection.RequestAborted.Register(() =>
{
aborted.SetResult(null);
});
// Initialize timestamp
var startTime = systemClock.UtcNow;
_httpConnection.Tick(startTime);
// Should complete within 1 second, but the timeout is adjusted by adding Heartbeat.Interval
_httpConnection.StartTimingWrite(100);
// Tick just past 1s plus Heartbeat.Interval
systemClock.UtcNow += TimeSpan.FromSeconds(1) + Heartbeat.Interval + TimeSpan.FromTicks(1);
_httpConnection.Tick(systemClock.UtcNow);
// Still within grace period, not timed out
Assert.False(_httpConnection.RequestTimedOut);
// Tick just past grace period (adjusted by Heartbeat.Interval)
systemClock.UtcNow = startTime + minResponseDataRate.GracePeriod + Heartbeat.Interval + TimeSpan.FromTicks(1);
_httpConnection.Tick(systemClock.UtcNow);
Assert.True(_httpConnection.RequestTimedOut);
await aborted.Task.DefaultTimeout();
}
[Fact]
public async Task WriteTimingTimeoutPushedOnConcurrentWrite()
{
var systemClock = new MockSystemClock();
var aborted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
_httpConnectionContext.ServiceContext.ServerOptions.Limits.MinResponseDataRate =
new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2));
_httpConnectionContext.ServiceContext.SystemClock = systemClock;
var mockLogger = new Mock<IKestrelTrace>();
_httpConnectionContext.ServiceContext.Log = mockLogger.Object;
_httpConnection.Initialize(_httpConnectionContext.Transport);
_httpConnection.Http1Connection.Reset();
_httpConnection.Http1Connection.RequestAborted.Register(() =>
{
aborted.SetResult(null);
});
// Initialize timestamp
_httpConnection.Tick(systemClock.UtcNow);
// Should complete within 5 seconds, but the timeout is adjusted by adding Heartbeat.Interval
_httpConnection.StartTimingWrite(500);
// Start a concurrent write after 3 seconds, which should complete within 3 seconds (adjusted by Heartbeat.Interval)
_httpConnection.StartTimingWrite(300);
// Tick just past 5s plus Heartbeat.Interval, when the first write should have completed
systemClock.UtcNow += TimeSpan.FromSeconds(5) + Heartbeat.Interval + TimeSpan.FromTicks(1);
_httpConnection.Tick(systemClock.UtcNow);
// Not timed out because the timeout was pushed by the second write
Assert.False(_httpConnection.RequestTimedOut);
// Complete the first write, this should have no effect on the timeout
_httpConnection.StopTimingWrite();
// Tick just past +3s, when the second write should have completed
systemClock.UtcNow += TimeSpan.FromSeconds(3) + TimeSpan.FromTicks(1);
_httpConnection.Tick(systemClock.UtcNow);
Assert.True(_httpConnection.RequestTimedOut);
await aborted.Task.DefaultTimeout();
}
[Fact]
public async Task WriteTimingAbortsConnectionWhenRepeatedSmallWritesDoNotCompleteWithMinimumDataRate()
{
var systemClock = new MockSystemClock();
var minResponseDataRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(5));
var numWrites = 5;
var writeSize = 100;
var aborted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
_httpConnectionContext.ServiceContext.ServerOptions.Limits.MinResponseDataRate = minResponseDataRate;
_httpConnectionContext.ServiceContext.SystemClock = systemClock;
var mockLogger = new Mock<IKestrelTrace>();
_httpConnectionContext.ServiceContext.Log = mockLogger.Object;
_httpConnection.Initialize(_httpConnectionContext.Transport);
_httpConnection.Http1Connection.Reset();
_httpConnection.Http1Connection.RequestAborted.Register(() =>
{
aborted.SetResult(null);
});
// Initialize timestamp
var startTime = systemClock.UtcNow;
_httpConnection.Tick(startTime);
// 5 consecutive 100 byte writes.
for (var i = 0; i < numWrites - 1; i++)
{
_httpConnection.StartTimingWrite(writeSize);
_httpConnection.StopTimingWrite();
}
// Stall the last write.
_httpConnection.StartTimingWrite(writeSize);
// Move the clock forward Heartbeat.Interval + MinDataRate.GracePeriod + 4 seconds.
// The grace period should only be added for the first write. The subsequent 4 100 byte writes should add 1 second each to the timeout given the 100 byte/s min rate.
systemClock.UtcNow += Heartbeat.Interval + minResponseDataRate.GracePeriod + TimeSpan.FromSeconds((numWrites - 1) * writeSize / minResponseDataRate.BytesPerSecond);
_httpConnection.Tick(systemClock.UtcNow);
Assert.False(_httpConnection.RequestTimedOut);
// On more tick forward triggers the timeout.
systemClock.UtcNow += TimeSpan.FromTicks(1);
_httpConnection.Tick(systemClock.UtcNow);
Assert.True(_httpConnection.RequestTimedOut);
await aborted.Task.TimeoutAfter(TimeSpan.FromSeconds(10));
}
}
}

View File

@ -23,9 +23,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
private readonly IDuplexPipe _application;
private readonly TestHttp1Connection _http1Connection;
private readonly ServiceContext _serviceContext;
private readonly Http1ConnectionContext _http1ConnectionContext;
private readonly HttpConnectionContext _http1ConnectionContext;
private readonly MemoryPool<byte> _memoryPool;
private Mock<ITimeoutControl> _timeoutControl;
private readonly IFeatureCollection _collection;
@ -39,13 +38,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
_application = pair.Application;
_serviceContext = new TestServiceContext();
_timeoutControl = new Mock<ITimeoutControl>();
_http1ConnectionContext = new Http1ConnectionContext
_http1ConnectionContext = new HttpConnectionContext
{
ServiceContext = _serviceContext,
ConnectionFeatures = new FeatureCollection(),
MemoryPool = _memoryPool,
TimeoutControl = _timeoutControl.Object,
TimeoutControl = Mock.Of<ITimeoutControl>(),
Transport = pair.Transport
};

View File

@ -2,12 +2,12 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Globalization;
using System.IO.Pipelines;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
using Microsoft.AspNetCore.Testing;
@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
var options = new PipeOptions(memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
var pair = DuplexPipe.CreateConnectionPair(options, options);
var http1ConnectionContext = new Http1ConnectionContext
var http1ConnectionContext = new HttpConnectionContext
{
ServiceContext = new TestServiceContext(),
ConnectionFeatures = new FeatureCollection(),

View File

@ -10,6 +10,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
@ -422,7 +423,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var options = new PipeOptions(pool: memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
var pair = DuplexPipe.CreateConnectionPair(options, options);
var transport = pair.Transport;
var http1ConnectionContext = new Http1ConnectionContext
var http1ConnectionContext = new HttpConnectionContext
{
ServiceContext = new TestServiceContext(),
ConnectionFeatures = new FeatureCollection(),
@ -747,9 +748,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
.Callback(() => produceContinueCalled = true);
input.Http1Connection.HttpResponseControl = mockHttpResponseControl.Object;
var minReadRate = input.Http1Connection.MinRequestBodyDataRate;
var mockTimeoutControl = new Mock<ITimeoutControl>();
mockTimeoutControl
.Setup(timeoutControl => timeoutControl.StartTimingReads())
.Setup(timeoutControl => timeoutControl.StartTimingReads(minReadRate))
.Callback(() => startTimingReadsCalledAfterProduceContinue = produceContinueCalled);
input.Http1ConnectionContext.TimeoutControl = mockTimeoutControl.Object;
@ -773,6 +775,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
using (var input = new TestInput())
{
var minReadRate = input.Http1Connection.MinRequestBodyDataRate;
var mockTimeoutControl = new Mock<ITimeoutControl>();
input.Http1ConnectionContext.TimeoutControl = mockTimeoutControl.Object;
@ -786,7 +789,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.Equal(0, await body.ReadAsync(new ArraySegment<byte>(new byte[1])));
mockTimeoutControl.Verify(timeoutControl => timeoutControl.StartTimingReads(), Times.Never);
mockTimeoutControl.Verify(timeoutControl => timeoutControl.StartTimingReads(minReadRate), Times.Never);
mockTimeoutControl.Verify(timeoutControl => timeoutControl.StopTimingReads(), Times.Never);
// Due to the limits set on HttpProtocol.RequestBodyPipe, backpressure should be triggered on every

View File

@ -4,10 +4,9 @@
using System;
using System.Buffers;
using System.IO.Pipelines;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
@ -96,7 +95,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
connectionContext,
serviceContext.Log,
Mock.Of<ITimeoutControl>(),
Mock.Of<IBytesWrittenFeature>());
Mock.Of<IHttpMinResponseDataRateFeature>());
return socketOutput;
}

View File

@ -9,6 +9,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
@ -31,9 +32,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var connectionFeatures = new FeatureCollection();
connectionFeatures.Set(Mock.Of<IConnectionLifetimeFeature>());
connectionFeatures.Set(Mock.Of<IBytesWrittenFeature>());
Http1ConnectionContext = new Http1ConnectionContext
Http1ConnectionContext = new HttpConnectionContext
{
ServiceContext = new TestServiceContext(),
ConnectionContext = Mock.Of<ConnectionContext>(),
@ -45,13 +45,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Http1Connection = new Http1Connection(Http1ConnectionContext);
Http1Connection.HttpResponseControl = Mock.Of<IHttpResponseControl>();
Http1Connection.Reset();
}
public IDuplexPipe Transport { get; }
public IDuplexPipe Application { get; }
public Http1ConnectionContext Http1ConnectionContext { get; }
public HttpConnectionContext Http1ConnectionContext { get; }
public Http1Connection Http1Connection { get; set; }

View File

@ -0,0 +1,405 @@
// 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;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Testing;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
public class TimeoutControlTests
{
private readonly Mock<ITimeoutHandler> _mockTimeoutHandler;
private readonly TimeoutControl _timeoutControl;
public TimeoutControlTests()
{
_mockTimeoutHandler = new Mock<ITimeoutHandler>();
_timeoutControl = new TimeoutControl(_mockTimeoutHandler.Object);
}
[Fact]
public void DoesNotTimeOutWhenDebuggerIsAttached()
{
var mockDebugger = new Mock<IDebugger>();
mockDebugger.SetupGet(g => g.IsAttached).Returns(true);
_timeoutControl.Debugger = mockDebugger.Object;
var now = DateTimeOffset.Now;
_timeoutControl.Tick(now);
_timeoutControl.SetTimeout(1, TimeoutReason.RequestHeaders);
_timeoutControl.Tick(now.AddTicks(2).Add(Heartbeat.Interval));
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
}
[Fact]
public void DoesNotTimeOutWhenRequestBodyDoesNotSatisfyMinimumDataRateButDebuggerIsAttached()
{
var mockDebugger = new Mock<IDebugger>();
mockDebugger.SetupGet(g => g.IsAttached).Returns(true);
_timeoutControl.Debugger = mockDebugger.Object;
TickBodyWithMinimumDataRate(bytesPerSecond: 100);
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
}
[Fact]
public void TimesOutWhenRequestBodyDoesNotSatisfyMinimumDataRate()
{
TickBodyWithMinimumDataRate(bytesPerSecond: 100);
// Timed out
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Once);
}
[Fact]
public void RequestBodyMinimumDataRateNotEnforcedDuringGracePeriod()
{
var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2));
// Initialize timestamp
var now = DateTimeOffset.UtcNow;
_timeoutControl.Tick(now);
_timeoutControl.StartTimingReads(minRate);
// Tick during grace period w/ low data rate
now += TimeSpan.FromSeconds(1);
_timeoutControl.BytesRead(10);
_timeoutControl.Tick(now);
// Not timed out
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
// Tick after grace period w/ low data rate
now += TimeSpan.FromSeconds(2);
_timeoutControl.BytesRead(10);
_timeoutControl.Tick(now);
// Timed out
_mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.ReadDataRate), Times.Once);
}
[Fact]
public void RequestBodyDataRateIsAveragedOverTimeSpentReadingRequestBody()
{
var gracePeriod = TimeSpan.FromSeconds(2);
var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: gracePeriod);
// Initialize timestamp
var now = DateTimeOffset.UtcNow;
_timeoutControl.Tick(now);
_timeoutControl.StartTimingReads(minRate);
// Set base data rate to 200 bytes/second
now += gracePeriod;
_timeoutControl.BytesRead(400);
_timeoutControl.Tick(now);
// Data rate: 200 bytes/second
now += TimeSpan.FromSeconds(1);
_timeoutControl.BytesRead(200);
_timeoutControl.Tick(now);
// Not timed out
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
// Data rate: 150 bytes/second
now += TimeSpan.FromSeconds(1);
_timeoutControl.BytesRead(0);
_timeoutControl.Tick(now);
// Not timed out
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
// Data rate: 120 bytes/second
now += TimeSpan.FromSeconds(1);
_timeoutControl.BytesRead(0);
_timeoutControl.Tick(now);
// Not timed out
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
// Data rate: 100 bytes/second
now += TimeSpan.FromSeconds(1);
_timeoutControl.BytesRead(0);
_timeoutControl.Tick(now);
// Not timed out
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
// Data rate: ~85 bytes/second
now += TimeSpan.FromSeconds(1);
_timeoutControl.BytesRead(0);
_timeoutControl.Tick(now);
// Timed out
_mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.ReadDataRate), Times.Once);
}
[Fact]
public void RequestBodyDataRateNotComputedOnPausedTime()
{
var systemClock = new MockSystemClock();
var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2));
// Initialize timestamp
_timeoutControl.Tick(systemClock.UtcNow);
_timeoutControl.StartTimingReads(minRate);
// Tick at 3s, expected counted time is 3s, expected data rate is 200 bytes/second
systemClock.UtcNow += TimeSpan.FromSeconds(3);
_timeoutControl.BytesRead(600);
_timeoutControl.Tick(systemClock.UtcNow);
// Pause at 3.5s
systemClock.UtcNow += TimeSpan.FromSeconds(0.5);
_timeoutControl.PauseTimingReads();
// Tick at 4s, expected counted time is 4s (first tick after pause goes through), expected data rate is 150 bytes/second
systemClock.UtcNow += TimeSpan.FromSeconds(0.5);
_timeoutControl.Tick(systemClock.UtcNow);
// Tick at 6s, expected counted time is 4s, expected data rate is 150 bytes/second
systemClock.UtcNow += TimeSpan.FromSeconds(2);
_timeoutControl.Tick(systemClock.UtcNow);
// Not timed out
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
// Resume at 6.5s
systemClock.UtcNow += TimeSpan.FromSeconds(0.5);
_timeoutControl.ResumeTimingReads();
// Tick at 9s, expected counted time is 6s, expected data rate is 100 bytes/second
systemClock.UtcNow += TimeSpan.FromSeconds(1.5);
_timeoutControl.Tick(systemClock.UtcNow);
// Not timed out
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
// Tick at 10s, expected counted time is 7s, expected data rate drops below 100 bytes/second
systemClock.UtcNow += TimeSpan.FromSeconds(1);
_timeoutControl.Tick(systemClock.UtcNow);
// Timed out
_mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.ReadDataRate), Times.Once);
}
[Fact]
public void ReadTimingNotPausedWhenResumeCalledBeforeNextTick()
{
var systemClock = new MockSystemClock();
var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2));
// Initialize timestamp
_timeoutControl.Tick(systemClock.UtcNow);
_timeoutControl.StartTimingReads(minRate);
// Tick at 2s, expected counted time is 2s, expected data rate is 100 bytes/second
systemClock.UtcNow += TimeSpan.FromSeconds(2);
_timeoutControl.BytesRead(200);
_timeoutControl.Tick(systemClock.UtcNow);
// Not timed out
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
// Pause at 2.25s
systemClock.UtcNow += TimeSpan.FromSeconds(0.25);
_timeoutControl.PauseTimingReads();
// Resume at 2.5s
systemClock.UtcNow += TimeSpan.FromSeconds(0.25);
_timeoutControl.ResumeTimingReads();
// Tick at 3s, expected counted time is 3s, expected data rate is 100 bytes/second
systemClock.UtcNow += TimeSpan.FromSeconds(0.5);
_timeoutControl.BytesRead(100);
_timeoutControl.Tick(systemClock.UtcNow);
// Not timed out
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
// Tick at 4s, expected counted time is 4s, expected data rate drops below 100 bytes/second
systemClock.UtcNow += TimeSpan.FromSeconds(1);
_timeoutControl.Tick(systemClock.UtcNow);
// Timed out
_mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.ReadDataRate), Times.Once);
}
[Fact]
public void ReadTimingNotEnforcedWhenTimeoutIsSet()
{
var systemClock = new MockSystemClock();
var timeout = TimeSpan.FromSeconds(5);
var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2));
var startTime = systemClock.UtcNow;
// Initialize timestamp
_timeoutControl.Tick(startTime);
_timeoutControl.StartTimingReads(minRate);
_timeoutControl.SetTimeout(timeout.Ticks, TimeoutReason.RequestBodyDrain);
// Tick beyond grace period with low data rate
systemClock.UtcNow += TimeSpan.FromSeconds(3);
_timeoutControl.BytesRead(1);
_timeoutControl.Tick(systemClock.UtcNow);
// Not timed out
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
// Tick just past timeout period, adjusted by Heartbeat.Interval
systemClock.UtcNow = startTime + timeout + Heartbeat.Interval + TimeSpan.FromTicks(1);
_timeoutControl.Tick(systemClock.UtcNow);
// Timed out
_mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.RequestBodyDrain), Times.Once);
}
[Fact]
public void WriteTimingAbortsConnectionWhenWriteDoesNotCompleteWithMinimumDataRate()
{
var systemClock = new MockSystemClock();
var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2));
// Initialize timestamp
_timeoutControl.Tick(systemClock.UtcNow);
// Should complete within 4 seconds, but the timeout is adjusted by adding Heartbeat.Interval
_timeoutControl.StartTimingWrite(minRate, 400);
// Tick just past 4s plus Heartbeat.Interval
systemClock.UtcNow += TimeSpan.FromSeconds(4) + Heartbeat.Interval + TimeSpan.FromTicks(1);
_timeoutControl.Tick(systemClock.UtcNow);
_mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Once);
}
[Fact]
public void WriteTimingAbortsConnectionWhenSmallWriteDoesNotCompleteWithinGracePeriod()
{
var systemClock = new MockSystemClock();
var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(5));
// Initialize timestamp
var startTime = systemClock.UtcNow;
_timeoutControl.Tick(startTime);
// Should complete within 1 second, but the timeout is adjusted by adding Heartbeat.Interval
_timeoutControl.StartTimingWrite(minRate, 100);
// Tick just past 1s plus Heartbeat.Interval
systemClock.UtcNow += TimeSpan.FromSeconds(1) + Heartbeat.Interval + TimeSpan.FromTicks(1);
_timeoutControl.Tick(systemClock.UtcNow);
// Still within grace period, not timed out
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
// Tick just past grace period (adjusted by Heartbeat.Interval)
systemClock.UtcNow = startTime + minRate.GracePeriod + Heartbeat.Interval + TimeSpan.FromTicks(1);
_timeoutControl.Tick(systemClock.UtcNow);
_mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Once);
}
[Fact]
public void WriteTimingTimeoutPushedOnConcurrentWrite()
{
var systemClock = new MockSystemClock();
var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2));
// Initialize timestamp
_timeoutControl.Tick(systemClock.UtcNow);
// Should complete within 5 seconds, but the timeout is adjusted by adding Heartbeat.Interval
_timeoutControl.StartTimingWrite(minRate, 500);
// Start a concurrent write after 3 seconds, which should complete within 3 seconds (adjusted by Heartbeat.Interval)
_timeoutControl.StartTimingWrite(minRate, 300);
// Tick just past 5s plus Heartbeat.Interval, when the first write should have completed
systemClock.UtcNow += TimeSpan.FromSeconds(5) + Heartbeat.Interval + TimeSpan.FromTicks(1);
_timeoutControl.Tick(systemClock.UtcNow);
// Not timed out because the timeout was pushed by the second write
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
// Complete the first write, this should have no effect on the timeout
_timeoutControl.StopTimingWrite();
// Tick just past +3s, when the second write should have completed
systemClock.UtcNow += TimeSpan.FromSeconds(3) + TimeSpan.FromTicks(1);
_timeoutControl.Tick(systemClock.UtcNow);
_mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Once);
}
[Fact]
public void WriteTimingAbortsConnectionWhenRepeatedSmallWritesDoNotCompleteWithMinimumDataRate()
{
var systemClock = new MockSystemClock();
var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(5));
var numWrites = 5;
var writeSize = 100;
// Initialize timestamp
var startTime = systemClock.UtcNow;
_timeoutControl.Tick(startTime);
// 5 consecutive 100 byte writes.
for (var i = 0; i < numWrites - 1; i++)
{
_timeoutControl.StartTimingWrite(minRate, writeSize);
_timeoutControl.StopTimingWrite();
}
// Stall the last write.
_timeoutControl.StartTimingWrite(minRate, writeSize);
// Move the clock forward Heartbeat.Interval + MinDataRate.GracePeriod + 4 seconds.
// The grace period should only be added for the first write. The subsequent 4 100 byte writes should add 1 second each to the timeout given the 100 byte/s min rate.
systemClock.UtcNow += Heartbeat.Interval + minRate.GracePeriod + TimeSpan.FromSeconds((numWrites - 1) * writeSize / minRate.BytesPerSecond);
_timeoutControl.Tick(systemClock.UtcNow);
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
// On more tick forward triggers the timeout.
systemClock.UtcNow += TimeSpan.FromTicks(1);
_timeoutControl.Tick(systemClock.UtcNow);
_mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Once);
}
private void TickBodyWithMinimumDataRate(int bytesPerSecond)
{
var gracePeriod = TimeSpan.FromSeconds(5);
var minRate = new MinDataRate(bytesPerSecond, gracePeriod);
// Initialize timestamp
var now = DateTimeOffset.UtcNow;
_timeoutControl.Tick(now);
_timeoutControl.StartTimingReads(minRate);
// Tick after grace period w/ low data rate
now += gracePeriod + TimeSpan.FromSeconds(1);
_timeoutControl.BytesRead(1);
_timeoutControl.Tick(now);
}
}
}

View File

@ -16,7 +16,7 @@ using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{
public class ConnectionAdapterTests : LoggedTest
public class ConnectionAdapterTests : TestApplicationErrorLoggerLoggedTest
{
[Fact]
public async Task CanReadAndWriteWithRewritingConnectionAdapter()
@ -164,6 +164,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
await connection.WaitForConnectionClose();
}
}
Assert.Contains(TestApplicationErrorLogger.Messages, m => m.Message.Contains($"Uncaught exception from the {nameof(IConnectionAdapter.OnConnectionAsync)} method of an {nameof(IConnectionAdapter)}."));
}
[Fact]
@ -220,7 +222,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
public async Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context)
{
await Task.Delay(100);
await Task.Yield();
return new AdaptedConnection(new RewritingStream(context.ConnectionStream));
}
}

View File

@ -13,9 +13,11 @@ using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
@ -2874,6 +2876,34 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.GOAWAY, streamId: 0, headersStreamId: 1));
}
[Fact]
public async Task GOAWAY_Received_ConnectionClosedWhenResponseNotDrainedAtMinimumDataRate()
{
var mockSystemClock = new MockSystemClock();
var limits = _connectionContext.ServiceContext.ServerOptions.Limits;
var timeoutControl = _connectionContext.TimeoutControl;
_timeoutControl.Initialize(mockSystemClock.UtcNow);
await InitializeConnectionAsync(_noopApplication);
await SendGoAwayAsync();
await WaitForConnectionStopAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false);
mockSystemClock.UtcNow +=
Heartbeat.Interval +
TimeSpan.FromSeconds(limits.MaxResponseBufferSize.Value * 2 / limits.MinResponseDataRate.BytesPerSecond);
_timeoutControl.Tick(mockSystemClock.UtcNow);
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
mockSystemClock.UtcNow += TimeSpan.FromTicks(1);
_timeoutControl.Tick(mockSystemClock.UtcNow);
_mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Once);
}
[Fact]
public async Task WINDOW_UPDATE_Received_StreamIdEven_ConnectionError()
{
@ -3482,20 +3512,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
_pair.Application.Output.Complete(new ConnectionResetException(string.Empty));
var result = await _pair.Application.Input.ReadAsync();
Assert.True(result.IsCompleted);
await WaitForConnectionStopAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false);
Assert.DoesNotContain(TestApplicationErrorLogger.Messages, m => m.Exception is ConnectionResetException);
}
[Fact]
public async Task OnInputOrOutputCompletedSendsFinalGOAWAY()
public async Task OnInputOrOutputCompletedCompletesOutput()
{
await InitializeConnectionAsync(_noopApplication);
_connection.OnInputOrOutputCompleted();
await _closedStateReached.Task.DefaultTimeout();
VerifyGoAway(await ReceiveFrameAsync(), 0, Http2ErrorCode.NO_ERROR);
var result = await _pair.Application.Input.ReadAsync().AsTask().DefaultTimeout();
Assert.True(result.IsCompleted);
Assert.True(result.Buffer.IsEmpty);
}
[Fact]
@ -3655,7 +3686,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StartStreamAsync(3, _browserRequestHeaders, endStream: false);
await WaitForConnectionStopAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
var result = await _pair.Application.Input.ReadAsync().AsTask().DefaultTimeout();
Assert.True(result.IsCompleted);
Assert.True(result.Buffer.IsEmpty);
}
[Fact]

View File

@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack;
@ -55,6 +56,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
protected readonly HPackDecoder _hpackDecoder;
private readonly byte[] _headerEncodingBuffer = new byte[Http2PeerSettings.MinAllowedMaxFrameSize];
protected readonly Mock<ITimeoutHandler> _mockTimeoutHandler = new Mock<ITimeoutHandler>();
protected readonly TimeoutControl _timeoutControl;
protected readonly ConcurrentDictionary<int, TaskCompletionSource<object>> _runningStreams = new ConcurrentDictionary<int, TaskCompletionSource<object>>();
protected readonly Dictionary<string, string> _receivedHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
protected readonly Dictionary<string, string> _decodedHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
@ -77,7 +81,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
protected readonly RequestDelegate _echoHost;
protected readonly RequestDelegate _echoPath;
protected Http2ConnectionContext _connectionContext;
protected HttpConnectionContext _connectionContext;
protected Http2Connection _connection;
protected Task _connectionTask;
@ -101,6 +105,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
_pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions);
_hpackDecoder = new HPackDecoder((int)_clientSettings.HeaderTableSize, MaxRequestHeaderFieldSize);
_timeoutControl = new TimeoutControl(_mockTimeoutHandler.Object);
_noopApplication = context => Task.CompletedTask;
@ -282,13 +287,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
.Setup(m => m.Http2ConnectionClosed(It.IsAny<string>(), It.IsAny<int>()))
.Callback(() => _closedStateReached.SetResult(null));
_connectionContext = new Http2ConnectionContext
_connectionContext = new HttpConnectionContext
{
ConnectionContext = Mock.Of<ConnectionContext>(),
ConnectionFeatures = new FeatureCollection(),
ServiceContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object),
MemoryPool = _memoryPool,
Transport = _pair.Transport
Transport = _pair.Transport,
TimeoutControl = _timeoutControl
};
_connection = new Http2Connection(_connectionContext);

View File

@ -100,20 +100,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
using (var server = CreateServer(testContext))
using (var connection = server.CreateConnection())
{
// When the in-memory connection is aborted, the input PipeWriter is completed behind the scenes
// so eventually connection.Send() throws an InvalidOperationException.
await Assert.ThrowsAsync<InvalidOperationException>(async () =>
foreach (var ch in "POST / HTTP/1.1\r\nHost:\r\n\r\n")
{
foreach (var ch in "POST / HTTP/1.1\r\nHost:\r\n\r\n")
{
await connection.Send(ch.ToString());
await connection.Send(ch.ToString());
testContext.MockSystemClock.UtcNow += ShortDelay;
heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow);
}
});
testContext.MockSystemClock.UtcNow += ShortDelay;
heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow);
}
await ReceiveTimeoutResponse(connection, testContext);
await connection.WaitForConnectionClose();
}
}

View File

@ -0,0 +1,80 @@
// 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;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
public class ResponseDrainingTests : TestApplicationErrorLoggerLoggedTest
{
public static TheoryData<ListenOptions> ConnectionAdapterData => new TheoryData<ListenOptions>
{
new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)),
new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
{
ConnectionAdapters = { new PassThroughConnectionAdapter() }
}
};
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task ConnectionClosedWhenResponseNotDrainedAtMinimumDataRate(ListenOptions listenOptions)
{
var testContext = new TestServiceContext(LoggerFactory);
var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager);
var minRate = new MinDataRate(16384, TimeSpan.FromSeconds(2));
using (var server = new TestServer(context =>
{
context.Features.Get<IHttpMinResponseDataRateFeature>().MinDataRate = minRate;
return Task.CompletedTask;
}, testContext, listenOptions))
{
using (var connection = server.CreateConnection())
{
var transportConnection = connection.TransportConnection;
var outputBufferedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
transportConnection.Output.OnWriterCompleted((ex, state) =>
{
((TaskCompletionSource<object>)state).SetResult(null);
}, outputBufferedTcs);
await connection.Send(
"GET / HTTP/1.1",
"Host:",
"Connection: close",
"",
"");
// Wait for the drain timeout to be set.
await outputBufferedTcs.Task.DefaultTimeout();
testContext.MockSystemClock.UtcNow +=
Heartbeat.Interval +
TimeSpan.FromSeconds(testContext.ServerOptions.Limits.MaxResponseBufferSize.Value * 2 / minRate.BytesPerSecond);
heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow);
Assert.Null(transportConnection.AbortReason);
testContext.MockSystemClock.UtcNow += TimeSpan.FromTicks(1);
heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow);
Assert.NotNull(transportConnection.AbortReason);
Assert.Equal(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied, transportConnection.AbortReason.Message);
Assert.Single(TestApplicationErrorLogger.Messages, w => w.EventId.Id == 28 && w.LogLevel == LogLevel.Information);
}
}
}
}
}

View File

@ -107,17 +107,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
serviceContext.ServerOptions.Limits.MaxResponseBufferSize = 5;
var cts = new CancellationTokenSource();
var appTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var writeReturnedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var writeStartedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
using (var server = new TestServer(async context =>
{
try
{
await context.Response.WriteAsync("hello", cts.Token).DefaultTimeout();
writeReturnedTcs.TrySetResult(null);
var task = context.Response.WriteAsync("world", cts.Token);
Assert.False(task.IsCompleted);
writeStartedTcs.TrySetResult(null);
await task.DefaultTimeout();
}
catch (Exception ex)
@ -127,7 +129,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
finally
{
appTcs.TrySetResult(null);
writeReturnedTcs.TrySetCanceled();
writeStartedTcs.TrySetCanceled();
}
}, serviceContext))
{
@ -146,7 +148,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
"5",
"hello");
await writeReturnedTcs.Task.DefaultTimeout();
await writeStartedTcs.Task.DefaultTimeout();
cts.Cancel();

View File

@ -9,31 +9,32 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTrans
{
public class InMemoryConnection : StreamBackedTestConnection
{
private readonly InMemoryTransportConnection _transportConnection;
public InMemoryConnection(InMemoryTransportConnection transportConnection)
: base(new RawStream(transportConnection.Output, transportConnection.Input))
{
_transportConnection = transportConnection;
TransportConnection = transportConnection;
}
public InMemoryTransportConnection TransportConnection { get; }
public override void Reset()
{
_transportConnection.Input.Complete(new ConnectionResetException(string.Empty));
_transportConnection.OnClosed();
TransportConnection.Input.Complete(new ConnectionResetException(string.Empty));
TransportConnection.OnClosed();
}
public override void ShutdownSend()
{
_transportConnection.Input.Complete();
_transportConnection.OnClosed();
TransportConnection.Input.Complete();
TransportConnection.OnClosed();
}
public override void Dispose()
{
_transportConnection.Input.Complete();
_transportConnection.Output.Complete();
_transportConnection.OnClosed();
TransportConnection.Input.Complete();
TransportConnection.Output.Complete();
TransportConnection.OnClosed();
base.Dispose();
}
}

View File

@ -31,9 +31,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTrans
public override PipeScheduler InputWriterScheduler => PipeScheduler.ThreadPool;
public override PipeScheduler OutputReaderScheduler => PipeScheduler.ThreadPool;
public ConnectionAbortedException AbortReason { get; private set; }
public override void Abort(ConnectionAbortedException abortReason)
{
Input.Complete(abortReason);
AbortReason = abortReason;
}
public void OnClosed()

View File

@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
@ -735,9 +736,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
var connectionFeatures = new FeatureCollection();
connectionFeatures.Set(Mock.Of<IConnectionLifetimeFeature>());
connectionFeatures.Set(Mock.Of<IBytesWrittenFeature>());
var http1Connection = new Http1Connection(new Http1ConnectionContext
var http1Connection = new Http1Connection(new HttpConnectionContext
{
ServiceContext = serviceContext,
ConnectionContext = Mock.Of<ConnectionContext>(),

View File

@ -2,13 +2,14 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
namespace Microsoft.AspNetCore.Testing
{
public class TestHttp1Connection : Http1Connection
{
public TestHttp1Connection(Http1ConnectionContext context)
public TestHttp1Connection(HttpConnectionContext context)
: base(context)
{
}

View File

@ -20,8 +20,7 @@ namespace CodeGenerator
"ITransportSchedulerFeature",
"IConnectionLifetimeFeature",
"IConnectionHeartbeatFeature",
"IConnectionLifetimeNotificationFeature",
"IBytesWrittenFeature",
"IConnectionLifetimeNotificationFeature"
};
var usings = $@"