Merge branch 'release/2.2'
This commit is contained in:
commit
ccaed5176a
207
LICENSE.txt
207
LICENSE.txt
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
|
|
@ -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>
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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": []
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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": []
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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>(),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,7 @@ namespace CodeGenerator
|
|||
"ITransportSchedulerFeature",
|
||||
"IConnectionLifetimeFeature",
|
||||
"IConnectionHeartbeatFeature",
|
||||
"IConnectionLifetimeNotificationFeature",
|
||||
"IBytesWrittenFeature",
|
||||
"IConnectionLifetimeNotificationFeature"
|
||||
};
|
||||
|
||||
var usings = $@"
|
||||
|
|
|
|||
Loading…
Reference in New Issue