Always cache headers and streams across frames (#754).
This commit is contained in:
parent
67ed6c5958
commit
925d8e0200
|
|
@ -174,19 +174,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
|
||||||
|
|
||||||
public void InitializeHeaders()
|
public void InitializeHeaders()
|
||||||
{
|
{
|
||||||
_frameHeaders = HttpComponentFactory.CreateHeaders(DateHeaderValueManager);
|
if (_frameHeaders == null)
|
||||||
RequestHeaders = _frameHeaders.RequestHeaders;
|
{
|
||||||
ResponseHeaders = _frameHeaders.ResponseHeaders;
|
_frameHeaders = new Headers(ServerOptions);
|
||||||
|
RequestHeaders = _frameHeaders.RequestHeaders;
|
||||||
|
ResponseHeaders = _frameHeaders.ResponseHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
_frameHeaders.Initialize(DateHeaderValueManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void InitializeStreams(MessageBody messageBody)
|
public void InitializeStreams(MessageBody messageBody)
|
||||||
{
|
{
|
||||||
_frameStreams = HttpComponentFactory.CreateStreams(this);
|
if (_frameStreams == null)
|
||||||
|
{
|
||||||
|
_frameStreams = new Streams(this);
|
||||||
|
RequestBody = _frameStreams.RequestBody;
|
||||||
|
ResponseBody = _frameStreams.ResponseBody;
|
||||||
|
DuplexStream = _frameStreams.DuplexStream;
|
||||||
|
}
|
||||||
|
|
||||||
RequestBody = _frameStreams.RequestBody.StartAcceptingReads(messageBody);
|
_frameStreams.RequestBody.StartAcceptingReads(messageBody);
|
||||||
ResponseBody = _frameStreams.ResponseBody.StartAcceptingWrites();
|
_frameStreams.ResponseBody.StartAcceptingWrites();
|
||||||
DuplexStream = _frameStreams.DuplexStream;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PauseStreams()
|
public void PauseStreams()
|
||||||
|
|
@ -209,7 +219,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
|
||||||
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
ResetComponents();
|
_frameHeaders?.Reset();
|
||||||
|
|
||||||
_onStarting = null;
|
_onStarting = null;
|
||||||
_onCompleted = null;
|
_onCompleted = null;
|
||||||
|
|
@ -246,26 +256,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
|
||||||
_abortedCts = null;
|
_abortedCts = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void ResetComponents()
|
|
||||||
{
|
|
||||||
var frameHeaders = Interlocked.Exchange(ref _frameHeaders, null);
|
|
||||||
if (frameHeaders != null)
|
|
||||||
{
|
|
||||||
RequestHeaders = null;
|
|
||||||
ResponseHeaders = null;
|
|
||||||
HttpComponentFactory.DisposeHeaders(frameHeaders);
|
|
||||||
}
|
|
||||||
|
|
||||||
var frameStreams = Interlocked.Exchange(ref _frameStreams, null);
|
|
||||||
if (frameStreams != null)
|
|
||||||
{
|
|
||||||
RequestBody = null;
|
|
||||||
ResponseBody = null;
|
|
||||||
DuplexStream = null;
|
|
||||||
HttpComponentFactory.DisposeStreams(frameStreams);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called once by Connection class to begin the RequestProcessingAsync loop.
|
/// Called once by Connection class to begin the RequestProcessingAsync loop.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
|
||||||
{
|
{
|
||||||
await TryProduceInvalidRequestResponse();
|
await TryProduceInvalidRequestResponse();
|
||||||
|
|
||||||
ResetComponents();
|
|
||||||
_abortedCts = null;
|
_abortedCts = null;
|
||||||
|
|
||||||
// If _requestAborted is set, the connection has already been closed.
|
// If _requestAborted is set, the connection has already been closed.
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Infrastructure;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Http
|
namespace Microsoft.AspNetCore.Server.Kestrel.Http
|
||||||
{
|
{
|
||||||
public class FrameRequestStream : Stream
|
class FrameRequestStream : Stream
|
||||||
{
|
{
|
||||||
private MessageBody _body;
|
private MessageBody _body;
|
||||||
private FrameStreamState _state;
|
private FrameStreamState _state;
|
||||||
|
|
@ -132,7 +132,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Stream StartAcceptingReads(MessageBody body)
|
public void StartAcceptingReads(MessageBody body)
|
||||||
{
|
{
|
||||||
// Only start if not aborted
|
// Only start if not aborted
|
||||||
if (_state == FrameStreamState.Closed)
|
if (_state == FrameStreamState.Closed)
|
||||||
|
|
@ -140,7 +140,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
|
||||||
_state = FrameStreamState.Open;
|
_state = FrameStreamState.Open;
|
||||||
_body = body;
|
_body = body;
|
||||||
}
|
}
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PauseAcceptingReads()
|
public void PauseAcceptingReads()
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
|
||||||
private IFrameControl _frameControl;
|
private IFrameControl _frameControl;
|
||||||
private FrameStreamState _state;
|
private FrameStreamState _state;
|
||||||
|
|
||||||
public FrameResponseStream()
|
public FrameResponseStream(IFrameControl frameControl)
|
||||||
{
|
{
|
||||||
|
_frameControl = frameControl;
|
||||||
_state = FrameStreamState.Closed;
|
_state = FrameStreamState.Closed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,15 +95,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
|
||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Stream StartAcceptingWrites()
|
public void StartAcceptingWrites()
|
||||||
{
|
{
|
||||||
// Only start if not aborted
|
// Only start if not aborted
|
||||||
if (_state == FrameStreamState.Closed)
|
if (_state == FrameStreamState.Closed)
|
||||||
{
|
{
|
||||||
_state = FrameStreamState.Open;
|
_state = FrameStreamState.Open;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PauseAcceptingWrites()
|
public void PauseAcceptingWrites()
|
||||||
|
|
@ -134,17 +133,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Initialize(IFrameControl frameControl)
|
|
||||||
{
|
|
||||||
_frameControl = frameControl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Uninitialize()
|
|
||||||
{
|
|
||||||
_frameControl = null;
|
|
||||||
_state = FrameStreamState.Closed;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task ValidateState(CancellationToken cancellationToken)
|
private Task ValidateState(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
switch (_state)
|
switch (_state)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
// 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.Text;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Http;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
|
||||||
|
{
|
||||||
|
class Headers
|
||||||
|
{
|
||||||
|
public static readonly byte[] BytesServer = Encoding.ASCII.GetBytes("\r\nServer: Kestrel");
|
||||||
|
|
||||||
|
private readonly KestrelServerOptions _options;
|
||||||
|
|
||||||
|
public Headers(KestrelServerOptions options)
|
||||||
|
{
|
||||||
|
_options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize(DateHeaderValueManager dateValueManager)
|
||||||
|
{
|
||||||
|
var dateHeaderValues = dateValueManager.GetDateHeaderValues();
|
||||||
|
ResponseHeaders.SetRawDate(dateHeaderValues.String, dateHeaderValues.Bytes);
|
||||||
|
|
||||||
|
if (_options.AddServerHeader)
|
||||||
|
{
|
||||||
|
ResponseHeaders.SetRawServer("Kestrel", BytesServer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrameRequestHeaders RequestHeaders { get; } = new FrameRequestHeaders();
|
||||||
|
public FrameResponseHeaders ResponseHeaders { get; } = new FrameResponseHeaders();
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
RequestHeaders.Reset();
|
||||||
|
ResponseHeaders.Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,128 +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.Collections.Concurrent;
|
|
||||||
using System.Text;
|
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Http;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
|
|
||||||
{
|
|
||||||
class HttpComponentFactory : IHttpComponentFactory
|
|
||||||
{
|
|
||||||
|
|
||||||
private ConcurrentQueue<Streams> _streamPool = new ConcurrentQueue<Streams>();
|
|
||||||
private ConcurrentQueue<Headers> _headerPool = new ConcurrentQueue<Headers>();
|
|
||||||
|
|
||||||
public KestrelServerOptions ServerOptions { get; set; }
|
|
||||||
|
|
||||||
public HttpComponentFactory(KestrelServerOptions serverOptions)
|
|
||||||
{
|
|
||||||
ServerOptions = serverOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Streams CreateStreams(IFrameControl frameControl)
|
|
||||||
{
|
|
||||||
Streams streams;
|
|
||||||
|
|
||||||
if (!_streamPool.TryDequeue(out streams))
|
|
||||||
{
|
|
||||||
streams = new Streams();
|
|
||||||
}
|
|
||||||
|
|
||||||
streams.Initialize(frameControl);
|
|
||||||
|
|
||||||
return streams;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DisposeStreams(Streams streams)
|
|
||||||
{
|
|
||||||
if (_streamPool.Count < ServerOptions.MaxPooledStreams)
|
|
||||||
{
|
|
||||||
streams.Uninitialize();
|
|
||||||
|
|
||||||
_streamPool.Enqueue(streams);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Headers CreateHeaders(DateHeaderValueManager dateValueManager)
|
|
||||||
{
|
|
||||||
Headers headers;
|
|
||||||
|
|
||||||
if (!_headerPool.TryDequeue(out headers))
|
|
||||||
{
|
|
||||||
headers = new Headers(ServerOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
headers.Initialize(dateValueManager);
|
|
||||||
|
|
||||||
return headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DisposeHeaders(Headers headers)
|
|
||||||
{
|
|
||||||
if (_headerPool.Count < ServerOptions.MaxPooledHeaders)
|
|
||||||
{
|
|
||||||
headers.Uninitialize();
|
|
||||||
|
|
||||||
_headerPool.Enqueue(headers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class Headers
|
|
||||||
{
|
|
||||||
public static readonly byte[] BytesServer = Encoding.ASCII.GetBytes("\r\nServer: Kestrel");
|
|
||||||
|
|
||||||
public readonly FrameRequestHeaders RequestHeaders = new FrameRequestHeaders();
|
|
||||||
public readonly FrameResponseHeaders ResponseHeaders = new FrameResponseHeaders();
|
|
||||||
|
|
||||||
private readonly KestrelServerOptions _options;
|
|
||||||
|
|
||||||
public Headers(KestrelServerOptions options)
|
|
||||||
{
|
|
||||||
_options = options;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Initialize(DateHeaderValueManager dateValueManager)
|
|
||||||
{
|
|
||||||
var dateHeaderValues = dateValueManager.GetDateHeaderValues();
|
|
||||||
|
|
||||||
ResponseHeaders.SetRawDate(dateHeaderValues.String, dateHeaderValues.Bytes);
|
|
||||||
|
|
||||||
if (_options.AddServerHeader)
|
|
||||||
{
|
|
||||||
ResponseHeaders.SetRawServer(Constants.ServerName, BytesServer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Uninitialize()
|
|
||||||
{
|
|
||||||
RequestHeaders.Reset();
|
|
||||||
ResponseHeaders.Reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class Streams
|
|
||||||
{
|
|
||||||
public readonly FrameRequestStream RequestBody;
|
|
||||||
public readonly FrameResponseStream ResponseBody;
|
|
||||||
public readonly FrameDuplexStream DuplexStream;
|
|
||||||
|
|
||||||
public Streams()
|
|
||||||
{
|
|
||||||
RequestBody = new FrameRequestStream();
|
|
||||||
ResponseBody = new FrameResponseStream();
|
|
||||||
DuplexStream = new FrameDuplexStream(RequestBody, ResponseBody);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Initialize(IFrameControl frameControl)
|
|
||||||
{
|
|
||||||
ResponseBody.Initialize(frameControl);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Uninitialize()
|
|
||||||
{
|
|
||||||
ResponseBody.Uninitialize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +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 Microsoft.AspNetCore.Server.Kestrel.Http;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
|
|
||||||
{
|
|
||||||
interface IHttpComponentFactory
|
|
||||||
{
|
|
||||||
KestrelServerOptions ServerOptions { get; set; }
|
|
||||||
|
|
||||||
Streams CreateStreams(IFrameControl frameControl);
|
|
||||||
|
|
||||||
void DisposeStreams(Streams streams);
|
|
||||||
|
|
||||||
Headers CreateHeaders(DateHeaderValueManager dateValueManager);
|
|
||||||
|
|
||||||
void DisposeHeaders(Headers headers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
// 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.Http;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
|
||||||
|
{
|
||||||
|
class Streams
|
||||||
|
{
|
||||||
|
public Streams(IFrameControl frameControl)
|
||||||
|
{
|
||||||
|
RequestBody = new FrameRequestStream();
|
||||||
|
ResponseBody = new FrameResponseStream(frameControl);
|
||||||
|
DuplexStream = new FrameDuplexStream(RequestBody, ResponseBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrameRequestStream RequestBody { get; }
|
||||||
|
public FrameResponseStream ResponseBody { get; }
|
||||||
|
public FrameDuplexStream DuplexStream { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -44,8 +44,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
_applicationLifetime = applicationLifetime;
|
_applicationLifetime = applicationLifetime;
|
||||||
_logger = loggerFactory.CreateLogger(typeof(KestrelServer).GetTypeInfo().Namespace);
|
_logger = loggerFactory.CreateLogger(typeof(KestrelServer).GetTypeInfo().Namespace);
|
||||||
Features = new FeatureCollection();
|
Features = new FeatureCollection();
|
||||||
var componentFactory = new HttpComponentFactory(Options);
|
|
||||||
Features.Set<IHttpComponentFactory>(componentFactory);
|
|
||||||
_serverAddresses = new ServerAddressesFeature();
|
_serverAddresses = new ServerAddressesFeature();
|
||||||
Features.Set<IServerAddressesFeature>(_serverAddresses);
|
Features.Set<IServerAddressesFeature>(_serverAddresses);
|
||||||
}
|
}
|
||||||
|
|
@ -65,7 +63,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var componentFactory = Features.Get<IHttpComponentFactory>();
|
|
||||||
var dateHeaderValueManager = new DateHeaderValueManager();
|
var dateHeaderValueManager = new DateHeaderValueManager();
|
||||||
var trace = new KestrelTrace(_logger);
|
var trace = new KestrelTrace(_logger);
|
||||||
var engine = new KestrelEngine(new ServiceContext
|
var engine = new KestrelEngine(new ServiceContext
|
||||||
|
|
@ -78,8 +75,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
Log = trace,
|
Log = trace,
|
||||||
ThreadPool = new LoggingThreadPool(trace),
|
ThreadPool = new LoggingThreadPool(trace),
|
||||||
DateHeaderValueManager = dateHeaderValueManager,
|
DateHeaderValueManager = dateHeaderValueManager,
|
||||||
ServerOptions = Options,
|
ServerOptions = Options
|
||||||
HttpComponentFactory = componentFactory
|
|
||||||
});
|
});
|
||||||
|
|
||||||
_disposables.Push(engine);
|
_disposables.Push(engine);
|
||||||
|
|
|
||||||
|
|
@ -12,24 +12,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
|
|
||||||
public IConnectionFilter ConnectionFilter { get; set; }
|
public IConnectionFilter ConnectionFilter { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets value that instructs <seealso cref="KestrelServer"/> whether it is safe to
|
|
||||||
/// pool the Request and Response <seealso cref="System.IO.Stream"/> objects
|
|
||||||
/// for another request after the Response's OnCompleted callback has fired.
|
|
||||||
/// When this values is greater than zero, it is not safe to retain references to feature components after this event has fired.
|
|
||||||
/// Value is zero by default.
|
|
||||||
/// </summary>
|
|
||||||
public int MaxPooledStreams { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets value that instructs <seealso cref="KestrelServer"/> whether it is safe to
|
|
||||||
/// pool the Request and Response headers
|
|
||||||
/// for another request after the Response's OnCompleted callback has fired.
|
|
||||||
/// When this values is greater than zero, it is not safe to retain references to feature components after this event has fired.
|
|
||||||
/// Value is zero by default.
|
|
||||||
/// </summary>
|
|
||||||
public int MaxPooledHeaders { get; set; }
|
|
||||||
|
|
||||||
public bool NoDelay { get; set; } = true;
|
public bool NoDelay { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Net;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Filter;
|
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Http;
|
using Microsoft.AspNetCore.Server.Kestrel.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Infrastructure;
|
using Microsoft.AspNetCore.Server.Kestrel.Infrastructure;
|
||||||
|
|
||||||
|
|
@ -25,7 +22,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
FrameFactory = context.FrameFactory;
|
FrameFactory = context.FrameFactory;
|
||||||
DateHeaderValueManager = context.DateHeaderValueManager;
|
DateHeaderValueManager = context.DateHeaderValueManager;
|
||||||
ServerOptions = context.ServerOptions;
|
ServerOptions = context.ServerOptions;
|
||||||
HttpComponentFactory = context.HttpComponentFactory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IApplicationLifetime AppLifetime { get; set; }
|
public IApplicationLifetime AppLifetime { get; set; }
|
||||||
|
|
@ -39,7 +35,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
public DateHeaderValueManager DateHeaderValueManager { get; set; }
|
public DateHeaderValueManager DateHeaderValueManager { get; set; }
|
||||||
|
|
||||||
public KestrelServerOptions ServerOptions { get; set; }
|
public KestrelServerOptions ServerOptions { get; set; }
|
||||||
|
|
||||||
internal IHttpComponentFactory HttpComponentFactory { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -170,13 +170,15 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(ConnectionFilterData))]
|
[MemberData(nameof(ConnectionFilterData))]
|
||||||
public async Task ReuseStreamsOn(ServiceContext testContext)
|
public async Task HeadersAndStreamsAreReused(ServiceContext testContext)
|
||||||
{
|
{
|
||||||
testContext.ServerOptions.MaxPooledStreams = 120;
|
|
||||||
|
|
||||||
var streamCount = 0;
|
var streamCount = 0;
|
||||||
|
var requestHeadersCount = 0;
|
||||||
|
var responseHeadersCount = 0;
|
||||||
var loopCount = 20;
|
var loopCount = 20;
|
||||||
Stream lastStream = null;
|
Stream lastStream = null;
|
||||||
|
IHeaderDictionary lastRequestHeaders = null;
|
||||||
|
IHeaderDictionary lastResponseHeaders = null;
|
||||||
|
|
||||||
using (var server = new TestServer(
|
using (var server = new TestServer(
|
||||||
context =>
|
context =>
|
||||||
|
|
@ -186,6 +188,16 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
lastStream = context.Request.Body;
|
lastStream = context.Request.Body;
|
||||||
streamCount++;
|
streamCount++;
|
||||||
}
|
}
|
||||||
|
if (context.Request.Headers != lastRequestHeaders)
|
||||||
|
{
|
||||||
|
lastRequestHeaders = context.Request.Headers;
|
||||||
|
requestHeadersCount++;
|
||||||
|
}
|
||||||
|
if (context.Response.Headers != lastResponseHeaders)
|
||||||
|
{
|
||||||
|
lastResponseHeaders = context.Response.Headers;
|
||||||
|
responseHeadersCount++;
|
||||||
|
}
|
||||||
context.Response.Headers.Clear();
|
context.Response.Headers.Clear();
|
||||||
return context.Request.Body.CopyToAsync(context.Response.Body);
|
return context.Request.Body.CopyToAsync(context.Response.Body);
|
||||||
},
|
},
|
||||||
|
|
@ -208,49 +220,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.Equal(1, streamCount);
|
Assert.Equal(1, streamCount);
|
||||||
}
|
Assert.Equal(1, requestHeadersCount);
|
||||||
}
|
Assert.Equal(1, responseHeadersCount);
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[MemberData(nameof(ConnectionFilterData))]
|
|
||||||
public async Task ReuseStreamsOff(ServiceContext testContext)
|
|
||||||
{
|
|
||||||
testContext.ServerOptions.MaxPooledStreams = 0;
|
|
||||||
|
|
||||||
var streamCount = 0;
|
|
||||||
var loopCount = 20;
|
|
||||||
Stream lastStream = null;
|
|
||||||
|
|
||||||
using (var server = new TestServer(
|
|
||||||
context =>
|
|
||||||
{
|
|
||||||
if (context.Request.Body != lastStream)
|
|
||||||
{
|
|
||||||
lastStream = context.Request.Body;
|
|
||||||
streamCount++;
|
|
||||||
}
|
|
||||||
context.Response.Headers.Clear();
|
|
||||||
return context.Request.Body.CopyToAsync(context.Response.Body);
|
|
||||||
},
|
|
||||||
testContext))
|
|
||||||
{
|
|
||||||
|
|
||||||
using (var connection = new TestConnection(server.Port))
|
|
||||||
{
|
|
||||||
var requestData =
|
|
||||||
Enumerable.Repeat("GET / HTTP/1.1\r\n", loopCount)
|
|
||||||
.Concat(new[] { "GET / HTTP/1.1\r\nConnection: close\r\n\r\nGoodbye" });
|
|
||||||
|
|
||||||
var responseData =
|
|
||||||
Enumerable.Repeat("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n", loopCount)
|
|
||||||
.Concat(new[] { "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\nGoodbye" });
|
|
||||||
|
|
||||||
await connection.SendEnd(requestData.ToArray());
|
|
||||||
|
|
||||||
await connection.ReceiveEnd(responseData.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.Equal(loopCount + 1, streamCount);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,6 @@ using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel;
|
using Microsoft.AspNetCore.Server.Kestrel;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Http;
|
using Microsoft.AspNetCore.Server.Kestrel.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Infrastructure;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -27,7 +25,6 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
DateHeaderValueManager = new DateHeaderValueManager(),
|
DateHeaderValueManager = new DateHeaderValueManager(),
|
||||||
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
|
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
|
||||||
ServerOptions = serverOptions,
|
ServerOptions = serverOptions,
|
||||||
HttpComponentFactory = new HttpComponentFactory(serverOptions)
|
|
||||||
};
|
};
|
||||||
var frame = new Frame<object>(application: null, context: connectionContext);
|
var frame = new Frame<object>(application: null, context: connectionContext);
|
||||||
frame.InitializeHeaders();
|
frame.InitializeHeaders();
|
||||||
|
|
@ -70,7 +67,6 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
DateHeaderValueManager = new DateHeaderValueManager(),
|
DateHeaderValueManager = new DateHeaderValueManager(),
|
||||||
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
|
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
|
||||||
ServerOptions = serverOptions,
|
ServerOptions = serverOptions,
|
||||||
HttpComponentFactory = new HttpComponentFactory(serverOptions)
|
|
||||||
};
|
};
|
||||||
var frame = new Frame<object>(application: null, context: connectionContext);
|
var frame = new Frame<object>(application: null, context: connectionContext);
|
||||||
frame.InitializeHeaders();
|
frame.InitializeHeaders();
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Http;
|
using Microsoft.AspNetCore.Server.Kestrel.Http;
|
||||||
|
using Microsoft.AspNetCore.Server.KestrelTests.TestHelpers;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.KestrelTests
|
namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
|
|
@ -14,42 +15,42 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanReadReturnsFalse()
|
public void CanReadReturnsFalse()
|
||||||
{
|
{
|
||||||
var stream = new FrameResponseStream();
|
var stream = new FrameResponseStream(new MockFrameControl());
|
||||||
Assert.False(stream.CanRead);
|
Assert.False(stream.CanRead);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanSeekReturnsFalse()
|
public void CanSeekReturnsFalse()
|
||||||
{
|
{
|
||||||
var stream = new FrameResponseStream();
|
var stream = new FrameResponseStream(new MockFrameControl());
|
||||||
Assert.False(stream.CanSeek);
|
Assert.False(stream.CanSeek);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanWriteReturnsTrue()
|
public void CanWriteReturnsTrue()
|
||||||
{
|
{
|
||||||
var stream = new FrameResponseStream();
|
var stream = new FrameResponseStream(new MockFrameControl());
|
||||||
Assert.True(stream.CanWrite);
|
Assert.True(stream.CanWrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ReadThrows()
|
public void ReadThrows()
|
||||||
{
|
{
|
||||||
var stream = new FrameResponseStream();
|
var stream = new FrameResponseStream(new MockFrameControl());
|
||||||
Assert.Throws<NotSupportedException>(() => stream.Read(new byte[1], 0, 1));
|
Assert.Throws<NotSupportedException>(() => stream.Read(new byte[1], 0, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ReadByteThrows()
|
public void ReadByteThrows()
|
||||||
{
|
{
|
||||||
var stream = new FrameResponseStream();
|
var stream = new FrameResponseStream(new MockFrameControl());
|
||||||
Assert.Throws<NotSupportedException>(() => stream.ReadByte());
|
Assert.Throws<NotSupportedException>(() => stream.ReadByte());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ReadAsyncThrows()
|
public async Task ReadAsyncThrows()
|
||||||
{
|
{
|
||||||
var stream = new FrameResponseStream();
|
var stream = new FrameResponseStream(new MockFrameControl());
|
||||||
await Assert.ThrowsAsync<NotSupportedException>(() => stream.ReadAsync(new byte[1], 0, 1));
|
await Assert.ThrowsAsync<NotSupportedException>(() => stream.ReadAsync(new byte[1], 0, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,7 +58,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void BeginReadThrows()
|
public void BeginReadThrows()
|
||||||
{
|
{
|
||||||
var stream = new FrameResponseStream();
|
var stream = new FrameResponseStream(new MockFrameControl());
|
||||||
Assert.Throws<NotSupportedException>(() => stream.BeginRead(new byte[1], 0, 1, null, null));
|
Assert.Throws<NotSupportedException>(() => stream.BeginRead(new byte[1], 0, 1, null, null));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -65,28 +66,28 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SeekThrows()
|
public void SeekThrows()
|
||||||
{
|
{
|
||||||
var stream = new FrameResponseStream();
|
var stream = new FrameResponseStream(new MockFrameControl());
|
||||||
Assert.Throws<NotSupportedException>(() => stream.Seek(0, SeekOrigin.Begin));
|
Assert.Throws<NotSupportedException>(() => stream.Seek(0, SeekOrigin.Begin));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void LengthThrows()
|
public void LengthThrows()
|
||||||
{
|
{
|
||||||
var stream = new FrameResponseStream();
|
var stream = new FrameResponseStream(new MockFrameControl());
|
||||||
Assert.Throws<NotSupportedException>(() => stream.Length);
|
Assert.Throws<NotSupportedException>(() => stream.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SetLengthThrows()
|
public void SetLengthThrows()
|
||||||
{
|
{
|
||||||
var stream = new FrameResponseStream();
|
var stream = new FrameResponseStream(new MockFrameControl());
|
||||||
Assert.Throws<NotSupportedException>(() => stream.SetLength(0));
|
Assert.Throws<NotSupportedException>(() => stream.SetLength(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void PositionThrows()
|
public void PositionThrows()
|
||||||
{
|
{
|
||||||
var stream = new FrameResponseStream();
|
var stream = new FrameResponseStream(new MockFrameControl());
|
||||||
Assert.Throws<NotSupportedException>(() => stream.Position);
|
Assert.Throws<NotSupportedException>(() => stream.Position);
|
||||||
Assert.Throws<NotSupportedException>(() => stream.Position = 0);
|
Assert.Throws<NotSupportedException>(() => stream.Position = 0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For("HTTP/1.0", new FrameRequestHeaders(), input.FrameContext);
|
var body = MessageBody.For("HTTP/1.0", new FrameRequestHeaders(), input.FrameContext);
|
||||||
var stream = new FrameRequestStream().StartAcceptingReads(body);
|
var stream = new FrameRequestStream();
|
||||||
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
input.Add("Hello", true);
|
input.Add("Hello", true);
|
||||||
|
|
||||||
|
|
@ -40,7 +41,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For("HTTP/1.0", new FrameRequestHeaders(), input.FrameContext);
|
var body = MessageBody.For("HTTP/1.0", new FrameRequestHeaders(), input.FrameContext);
|
||||||
var stream = new FrameRequestStream().StartAcceptingReads(body);
|
var stream = new FrameRequestStream();
|
||||||
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
input.Add("Hello", true);
|
input.Add("Hello", true);
|
||||||
|
|
||||||
|
|
@ -60,7 +62,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For("HTTP/1.0", new FrameRequestHeaders(), input.FrameContext);
|
var body = MessageBody.For("HTTP/1.0", new FrameRequestHeaders(), input.FrameContext);
|
||||||
var stream = new FrameRequestStream().StartAcceptingReads(body);
|
var stream = new FrameRequestStream();
|
||||||
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
// Input needs to be greater than 4032 bytes to allocate a block not backed by a slab.
|
// Input needs to be greater than 4032 bytes to allocate a block not backed by a slab.
|
||||||
var largeInput = new string('a', 8192);
|
var largeInput = new string('a', 8192);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
// 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.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Http;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.KestrelTests.TestHelpers
|
||||||
|
{
|
||||||
|
public class MockFrameControl : IFrameControl
|
||||||
|
{
|
||||||
|
public void Flush()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task FlushAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ProduceContinue()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(ArraySegment<byte> data)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task WriteAsync(ArraySegment<byte> data, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Server.Kestrel;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Filter;
|
using Microsoft.AspNetCore.Server.Kestrel.Filter;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Http;
|
using Microsoft.AspNetCore.Server.Kestrel.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Infrastructure;
|
using Microsoft.AspNetCore.Server.Kestrel.Infrastructure;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.KestrelTests
|
namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
{
|
{
|
||||||
|
|
@ -24,8 +23,6 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
|
|
||||||
ServerOptions = new KestrelServerOptions();
|
ServerOptions = new KestrelServerOptions();
|
||||||
ServerOptions.ShutdownTimeout = TimeSpan.FromSeconds(5);
|
ServerOptions.ShutdownTimeout = TimeSpan.FromSeconds(5);
|
||||||
|
|
||||||
HttpComponentFactory = new HttpComponentFactory(ServerOptions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestServiceContext(IConnectionFilter filter)
|
public TestServiceContext(IConnectionFilter filter)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue