Merge branch 'benaadams/pool-headers' into dev

This commit is contained in:
Stephen Halter 2016-02-17 15:39:00 -08:00
commit 87bd60746b
25 changed files with 1742 additions and 2018 deletions

View File

@ -61,7 +61,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
} }
// Don't initialize _frame until SocketInput and SocketOutput are set to their final values. // Don't initialize _frame until SocketInput and SocketOutput are set to their final values.
if (ConnectionFilter == null) if (ServerInformation.ConnectionFilter == null)
{ {
SocketInput = _rawSocketInput; SocketInput = _rawSocketInput;
SocketOutput = _rawSocketOutput; SocketOutput = _rawSocketOutput;
@ -81,7 +81,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
try try
{ {
ConnectionFilter.OnConnectionAsync(_filterContext).ContinueWith((task, state) => ServerInformation.ConnectionFilter.OnConnectionAsync(_filterContext).ContinueWith((task, state) =>
{ {
var connection = (Connection)state; var connection = (Connection)state;

View File

@ -34,8 +34,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
private static readonly byte[] _bytesHttpVersion1_1 = Encoding.ASCII.GetBytes("HTTP/1.1 "); private static readonly byte[] _bytesHttpVersion1_1 = Encoding.ASCII.GetBytes("HTTP/1.1 ");
private static readonly byte[] _bytesContentLengthZero = Encoding.ASCII.GetBytes("\r\nContent-Length: 0"); private static readonly byte[] _bytesContentLengthZero = Encoding.ASCII.GetBytes("\r\nContent-Length: 0");
private static readonly byte[] _bytesSpace = Encoding.ASCII.GetBytes(" "); private static readonly byte[] _bytesSpace = Encoding.ASCII.GetBytes(" ");
private static readonly byte[] _bytesServer = Encoding.ASCII.GetBytes("\r\nServer: Kestrel");
private static readonly byte[] _bytesDate = Encoding.ASCII.GetBytes("Date: ");
private static readonly byte[] _bytesEndHeaders = Encoding.ASCII.GetBytes("\r\n\r\n"); private static readonly byte[] _bytesEndHeaders = Encoding.ASCII.GetBytes("\r\n\r\n");
private static Vector<byte> _vectorCRs = new Vector<byte>((byte)'\r'); private static Vector<byte> _vectorCRs = new Vector<byte>((byte)'\r');
@ -46,8 +44,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
private readonly object _onStartingSync = new Object(); private readonly object _onStartingSync = new Object();
private readonly object _onCompletedSync = new Object(); private readonly object _onCompletedSync = new Object();
protected readonly FrameRequestHeaders _requestHeaders = new FrameRequestHeaders();
private readonly FrameResponseHeaders _responseHeaders = new FrameResponseHeaders(); protected bool _poolingPermitted = true;
private Headers _frameHeaders;
private Streams _frameStreams;
protected List<KeyValuePair<Func<object, Task>, object>> _onStarting; protected List<KeyValuePair<Func<object, Task>, object>> _onStarting;
@ -60,10 +60,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
protected CancellationTokenSource _abortedCts; protected CancellationTokenSource _abortedCts;
protected CancellationToken? _manuallySetRequestAbortToken; protected CancellationToken? _manuallySetRequestAbortToken;
internal FrameRequestStream _requestBody;
internal FrameResponseStream _responseBody;
internal FrameDuplexStream _duplexStream;
protected bool _responseStarted; protected bool _responseStarted;
protected bool _keepAlive; protected bool _keepAlive;
private bool _autoChunk; private bool _autoChunk;
@ -92,12 +88,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
_localEndPoint = localEndPoint; _localEndPoint = localEndPoint;
_prepareRequest = prepareRequest; _prepareRequest = prepareRequest;
_pathBase = context.ServerAddress.PathBase; _pathBase = context.ServerAddress.PathBase;
if (ReuseStreams)
{
_requestBody = new FrameRequestStream();
_responseBody = new FrameResponseStream(this);
_duplexStream = new FrameDuplexStream(_requestBody, _responseBody);
}
FrameControl = this; FrameControl = this;
Reset(); Reset();
@ -197,8 +187,48 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
get { return _responseStarted; } get { return _responseStarted; }
} }
protected FrameRequestHeaders FrameRequestHeaders => _frameHeaders.RequestHeaders;
public Frame InitializeHeaders()
{
_frameHeaders = HttpComponentFactory.CreateHeaders(DateHeaderValueManager);
RequestHeaders = _frameHeaders.RequestHeaders;
ResponseHeaders = _frameHeaders.ResponseHeaders;
return this;
}
public void InitializeStreams(MessageBody messageBody)
{
_frameStreams = HttpComponentFactory.CreateStreams(this);
RequestBody = _frameStreams.RequestBody.StartAcceptingReads(messageBody);
ResponseBody = _frameStreams.ResponseBody.StartAcceptingWrites();
DuplexStream = _frameStreams.DuplexStream;
}
public void PauseStreams()
{
_frameStreams.RequestBody.PauseAcceptingReads();
_frameStreams.ResponseBody.PauseAcceptingWrites();
}
public void ResumeStreams()
{
_frameStreams.RequestBody.ResumeAcceptingReads();
_frameStreams.ResponseBody.ResumeAcceptingWrites();
}
public void StopStreams()
{
_frameStreams.RequestBody.StopAcceptingReads();
_frameStreams.ResponseBody.StopAcceptingWrites();
}
public void Reset() public void Reset()
{ {
ResetComponents(poolingPermitted: true);
_onStarting = null; _onStarting = null;
_onCompleted = null; _onCompleted = null;
@ -207,8 +237,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
_autoChunk = false; _autoChunk = false;
_applicationException = null; _applicationException = null;
_requestHeaders.Reset();
ResetResponseHeaders();
ResetFeatureCollection(); ResetFeatureCollection();
Scheme = null; Scheme = null;
@ -218,13 +246,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
Path = null; Path = null;
QueryString = null; QueryString = null;
_httpVersion = HttpVersionType.Unknown; _httpVersion = HttpVersionType.Unknown;
RequestHeaders = _requestHeaders;
RequestBody = null;
StatusCode = 200; StatusCode = 200;
ReasonPhrase = null; ReasonPhrase = null;
ResponseHeaders = _responseHeaders;
ResponseBody = null;
DuplexStream = null;
var httpConnectionFeature = this as IHttpConnectionFeature; var httpConnectionFeature = this as IHttpConnectionFeature;
httpConnectionFeature.RemoteIpAddress = _remoteEndPoint?.Address; httpConnectionFeature.RemoteIpAddress = _remoteEndPoint?.Address;
@ -239,15 +262,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
_abortedCts = null; _abortedCts = null;
} }
public void ResetResponseHeaders() protected void ResetComponents(bool poolingPermitted)
{ {
_responseHeaders.Reset(); if (_frameHeaders != null)
_responseHeaders.SetRawDate( {
DateHeaderValueManager.GetDateHeaderValue(), var frameHeaders = _frameHeaders;
DateHeaderValueManager.GetDateHeaderValueBytes()); _frameHeaders = null;
_responseHeaders.SetRawServer(
"Kestrel", RequestHeaders = null;
_bytesServer); ResponseHeaders = null;
HttpComponentFactory.DisposeHeaders(frameHeaders, poolingPermitted);
}
if (_frameStreams != null)
{
var frameStreams = _frameStreams;
_frameStreams = null;
RequestBody = null;
ResponseBody = null;
DuplexStream = null;
HttpComponentFactory.DisposeStreams(frameStreams, poolingPermitted);
}
} }
/// <summary> /// <summary>
@ -292,8 +328,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
{ {
_requestProcessingStopping = true; _requestProcessingStopping = true;
_requestBody?.Abort(); _frameStreams?.RequestBody.Abort();
_responseBody?.Abort(); _frameStreams?.ResponseBody.Abort();
try try
{ {
@ -560,8 +596,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
StatusCode = 500; StatusCode = 500;
ReasonPhrase = null; ReasonPhrase = null;
ResetResponseHeaders(); var responseHeaders = _frameHeaders.ResponseHeaders;
_responseHeaders.SetRawContentLength("0", _bytesContentLengthZero); responseHeaders.Reset();
responseHeaders.SetRawDate(
DateHeaderValueManager.GetDateHeaderValue(),
DateHeaderValueManager.GetDateHeaderValueBytes());
responseHeaders.SetRawServer(
"Kestrel",
Headers.BytesServer);
responseHeaders.SetRawContentLength("0", _bytesContentLengthZero);
ResponseHeaders = responseHeaders;
} }
} }
@ -614,10 +659,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
byte[] statusBytes, byte[] statusBytes,
bool appCompleted) bool appCompleted)
{ {
var responseHeaders = _frameHeaders.ResponseHeaders;
responseHeaders.SetReadOnly();
var end = SocketOutput.ProducingStart(); var end = SocketOutput.ProducingStart();
if (_keepAlive) if (_keepAlive)
{ {
foreach (var connectionValue in _responseHeaders.HeaderConnection) foreach (var connectionValue in responseHeaders.HeaderConnection)
{ {
if (connectionValue.IndexOf("close", StringComparison.OrdinalIgnoreCase) != -1) if (connectionValue.IndexOf("close", StringComparison.OrdinalIgnoreCase) != -1)
{ {
@ -626,7 +674,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
} }
} }
if (_keepAlive && !_responseHeaders.HasTransferEncoding && !_responseHeaders.HasContentLength) if (_keepAlive && !responseHeaders.HasTransferEncoding && !responseHeaders.HasContentLength)
{ {
if (appCompleted) if (appCompleted)
{ {
@ -636,7 +684,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
{ {
// Since the app has completed and we are only now generating // Since the app has completed and we are only now generating
// the headers we can safely set the Content-Length to 0. // the headers we can safely set the Content-Length to 0.
_responseHeaders.SetRawContentLength("0", _bytesContentLengthZero); responseHeaders.SetRawContentLength("0", _bytesContentLengthZero);
} }
} }
else else
@ -644,7 +692,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
if (_httpVersion == HttpVersionType.Http1_1) if (_httpVersion == HttpVersionType.Http1_1)
{ {
_autoChunk = true; _autoChunk = true;
_responseHeaders.SetRawTransferEncoding("chunked", _bytesTransferEncodingChunked); responseHeaders.SetRawTransferEncoding("chunked", _bytesTransferEncodingChunked);
} }
else else
{ {
@ -653,18 +701,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
} }
} }
if (_keepAlive == false && _responseHeaders.HasConnection == false && _httpVersion == HttpVersionType.Http1_1) if (_keepAlive == false && responseHeaders.HasConnection == false && _httpVersion == HttpVersionType.Http1_1)
{ {
_responseHeaders.SetRawConnection("close", _bytesConnectionClose); responseHeaders.SetRawConnection("close", _bytesConnectionClose);
} }
else if (_keepAlive && _responseHeaders.HasConnection == false && _httpVersion == HttpVersionType.Http1_0) else if (_keepAlive && responseHeaders.HasConnection == false && _httpVersion == HttpVersionType.Http1_0)
{ {
_responseHeaders.SetRawConnection("keep-alive", _bytesConnectionKeepAlive); responseHeaders.SetRawConnection("keep-alive", _bytesConnectionKeepAlive);
} }
end.CopyFrom(_httpVersion == HttpVersionType.Http1_1 ? _bytesHttpVersion1_1 : _bytesHttpVersion1_0); end.CopyFrom(_httpVersion == HttpVersionType.Http1_1 ? _bytesHttpVersion1_1 : _bytesHttpVersion1_0);
end.CopyFrom(statusBytes); end.CopyFrom(statusBytes);
_responseHeaders.CopyTo(ref end); responseHeaders.CopyTo(ref end);
end.CopyFrom(_bytesEndHeaders, 0, _bytesEndHeaders.Length); end.CopyFrom(_bytesEndHeaders, 0, _bytesEndHeaders.Length);
SocketOutput.ProducingComplete(end); SocketOutput.ProducingComplete(end);

View File

@ -12,6 +12,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
{ {
public abstract class FrameHeaders : IHeaderDictionary public abstract class FrameHeaders : IHeaderDictionary
{ {
protected bool _isReadOnly;
protected Dictionary<string, StringValues> MaybeUnknown; protected Dictionary<string, StringValues> MaybeUnknown;
protected Dictionary<string, StringValues> Unknown => MaybeUnknown ?? (MaybeUnknown = new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase)); protected Dictionary<string, StringValues> Unknown => MaybeUnknown ?? (MaybeUnknown = new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase));
@ -26,6 +27,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
} }
set set
{ {
if (_isReadOnly)
{
ThrowReadOnlyException();
}
SetValueFast(key, value); SetValueFast(key, value);
} }
} }
@ -38,20 +43,50 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
} }
set set
{ {
if (_isReadOnly)
{
ThrowReadOnlyException();
}
SetValueFast(key, value); SetValueFast(key, value);
} }
} }
protected void ThrowReadOnlyException()
{
throw new InvalidOperationException("Headers are readonly, reponse has already started.");
}
protected void ThrowArgumentException()
{
throw new ArgumentException();
}
protected void ThrowKeyNotFoundException()
{
throw new KeyNotFoundException();
}
protected void ThrowDuplicateKeyException()
{
throw new ArgumentException("An item with the same key has already been added.");
}
int ICollection<KeyValuePair<string, StringValues>>.Count => GetCountFast(); int ICollection<KeyValuePair<string, StringValues>>.Count => GetCountFast();
bool ICollection<KeyValuePair<string, StringValues>>.IsReadOnly => false; bool ICollection<KeyValuePair<string, StringValues>>.IsReadOnly => _isReadOnly;
ICollection<string> IDictionary<string, StringValues>.Keys => ((IDictionary<string, StringValues>)this).Select(pair => pair.Key).ToList(); ICollection<string> IDictionary<string, StringValues>.Keys => ((IDictionary<string, StringValues>)this).Select(pair => pair.Key).ToList();
ICollection<StringValues> IDictionary<string, StringValues>.Values => ((IDictionary<string, StringValues>)this).Select(pair => pair.Value).ToList(); ICollection<StringValues> IDictionary<string, StringValues>.Values => ((IDictionary<string, StringValues>)this).Select(pair => pair.Value).ToList();
public void SetReadOnly()
{
_isReadOnly = true;
}
public void Reset() public void Reset()
{ {
_isReadOnly = false;
ClearFast(); ClearFast();
} }
@ -105,16 +140,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
void ICollection<KeyValuePair<string, StringValues>>.Add(KeyValuePair<string, StringValues> item) void ICollection<KeyValuePair<string, StringValues>>.Add(KeyValuePair<string, StringValues> item)
{ {
if (_isReadOnly)
{
ThrowReadOnlyException();
}
AddValueFast(item.Key, item.Value); AddValueFast(item.Key, item.Value);
} }
void IDictionary<string, StringValues>.Add(string key, StringValues value) void IDictionary<string, StringValues>.Add(string key, StringValues value)
{ {
if (_isReadOnly)
{
ThrowReadOnlyException();
}
AddValueFast(key, value); AddValueFast(key, value);
} }
void ICollection<KeyValuePair<string, StringValues>>.Clear() void ICollection<KeyValuePair<string, StringValues>>.Clear()
{ {
if (_isReadOnly)
{
ThrowReadOnlyException();
}
ClearFast(); ClearFast();
} }
@ -158,6 +205,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
bool IDictionary<string, StringValues>.Remove(string key) bool IDictionary<string, StringValues>.Remove(string key)
{ {
if (_isReadOnly)
{
ThrowReadOnlyException();
}
return RemoveFast(key); return RemoveFast(key);
} }

View File

@ -52,7 +52,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
await SocketInput; await SocketInput;
} }
while (!_requestProcessingStopping && !TakeMessageHeaders(SocketInput, _requestHeaders)) InitializeHeaders();
while (!_requestProcessingStopping && !TakeMessageHeaders(SocketInput, FrameRequestHeaders))
{ {
if (SocketInput.RemoteIntakeFin) if (SocketInput.RemoteIntakeFin)
{ {
@ -63,20 +65,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
if (!_requestProcessingStopping) if (!_requestProcessingStopping)
{ {
var messageBody = MessageBody.For(HttpVersion, _requestHeaders, this); var messageBody = MessageBody.For(HttpVersion, FrameRequestHeaders, this);
_keepAlive = messageBody.RequestKeepAlive; _keepAlive = messageBody.RequestKeepAlive;
// _duplexStream may be null if flag switched while running InitializeStreams(messageBody);
if (!ReuseStreams || _duplexStream == null)
{
_requestBody = new FrameRequestStream();
_responseBody = new FrameResponseStream(this);
_duplexStream = new FrameDuplexStream(_requestBody, _responseBody);
}
RequestBody = _requestBody.StartAcceptingReads(messageBody);
ResponseBody = _responseBody.StartAcceptingWrites();
DuplexStream = _duplexStream;
_abortedCts = null; _abortedCts = null;
_manuallySetRequestAbortToken = null; _manuallySetRequestAbortToken = null;
@ -101,8 +93,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
await FireOnStarting(); await FireOnStarting();
} }
_requestBody.PauseAcceptingReads(); PauseStreams();
_responseBody.PauseAcceptingWrites();
if (_onCompleted != null) if (_onCompleted != null)
{ {
@ -114,23 +105,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
// If _requestAbort is set, the connection has already been closed. // If _requestAbort is set, the connection has already been closed.
if (Volatile.Read(ref _requestAborted) == 0) if (Volatile.Read(ref _requestAborted) == 0)
{ {
_responseBody.ResumeAcceptingWrites(); ResumeStreams();
await ProduceEnd(); await ProduceEnd();
if (_keepAlive) if (_keepAlive)
{ {
_requestBody.ResumeAcceptingReads();
// Finish reading the request body in case the app did not. // Finish reading the request body in case the app did not.
await messageBody.Consume(); await messageBody.Consume();
} }
} }
_requestBody.StopAcceptingReads(); StopStreams();
_responseBody.StopAcceptingWrites();
} }
if (!_keepAlive) if (!_keepAlive)
{ {
ResetComponents(poolingPermitted: true);
return; return;
} }
} }
@ -140,12 +131,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
} }
catch (Exception ex) catch (Exception ex)
{ {
// Error occurred, do not return components to pool
_poolingPermitted = false;
Log.LogWarning(0, ex, "Connection processing ended abnormally"); Log.LogWarning(0, ex, "Connection processing ended abnormally");
} }
finally finally
{ {
try try
{ {
ResetComponents(poolingPermitted: _poolingPermitted);
_abortedCts = null; _abortedCts = null;
// If _requestAborted is set, the connection has already been closed. // If _requestAborted is set, the connection has already been closed.

View File

@ -11,12 +11,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
{ {
class FrameResponseStream : Stream class FrameResponseStream : Stream
{ {
private readonly FrameContext _context; private FrameContext _context;
private FrameStreamState _state; private FrameStreamState _state;
public FrameResponseStream(FrameContext context) public FrameResponseStream()
{ {
_context = context;
_state = FrameStreamState.Closed; _state = FrameStreamState.Closed;
} }
@ -125,6 +124,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
} }
} }
public void Initialize(FrameContext context)
{
_context = context;
}
public void Uninitialize()
{
_context = null;
_state = FrameStreamState.Closed;
}
private Task ValidateState(CancellationToken cancellationToken) private Task ValidateState(CancellationToken cancellationToken)
{ {
switch (_state) switch (_state)

View File

@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
{ {
var socket = new UvTcpHandle(Log); var socket = new UvTcpHandle(Log);
socket.Init(Thread.Loop, Thread.QueueCloseHandle); socket.Init(Thread.Loop, Thread.QueueCloseHandle);
socket.NoDelay(NoDelay); socket.NoDelay(ServerInformation.NoDelay);
socket.Bind(ServerAddress); socket.Bind(ServerAddress);
socket.Listen(Constants.ListenBacklog, (stream, status, error, state) => ConnectionCallback(stream, status, error, state), this); socket.Listen(Constants.ListenBacklog, (stream, status, error, state) => ConnectionCallback(stream, status, error, state), this);
return socket; return socket;
@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
try try
{ {
acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle); acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle);
acceptSocket.NoDelay(NoDelay); acceptSocket.NoDelay(ServerInformation.NoDelay);
listenSocket.Accept(acceptSocket); listenSocket.Accept(acceptSocket);
DispatchConnection(acceptSocket); DispatchConnection(acceptSocket);

View File

@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
{ {
var socket = new UvTcpHandle(Log); var socket = new UvTcpHandle(Log);
socket.Init(Thread.Loop, Thread.QueueCloseHandle); socket.Init(Thread.Loop, Thread.QueueCloseHandle);
socket.NoDelay(NoDelay); socket.NoDelay(ServerInformation.NoDelay);
socket.Bind(ServerAddress); socket.Bind(ServerAddress);
socket.Listen(Constants.ListenBacklog, (stream, status, error, state) => ConnectionCallback(stream, status, error, state), this); socket.Listen(Constants.ListenBacklog, (stream, status, error, state) => ConnectionCallback(stream, status, error, state), this);
return socket; return socket;
@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
try try
{ {
acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle); acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle);
acceptSocket.NoDelay(NoDelay); acceptSocket.NoDelay(ServerInformation.NoDelay);
listenSocket.Accept(acceptSocket); listenSocket.Accept(acceptSocket);
DispatchConnection(acceptSocket); DispatchConnection(acceptSocket);

View File

@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
{ {
var acceptSocket = new UvTcpHandle(Log); var acceptSocket = new UvTcpHandle(Log);
acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle); acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle);
acceptSocket.NoDelay(NoDelay); acceptSocket.NoDelay(ServerInformation.NoDelay);
return acceptSocket; return acceptSocket;
} }
} }

View File

@ -12,17 +12,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel
bool NoDelay { get; set; } bool NoDelay { get; set; }
/// <summary> /// <summary>
/// Gets or sets a flag that instructs <seealso cref="KestrelServer"/> whether it is safe to /// Gets or sets values that instruct <seealso cref="KestrelServer"/> whether it is safe to
/// reuse the Request and Response <seealso cref="System.IO.Stream"/> objects /// pool the Request and Response <seealso cref="System.IO.Stream"/> objects, Headers etc
/// for another request after the Response's OnCompleted callback has fired. /// for another request after the Response's OnCompleted callback has fired.
/// When this is set to true it is not safe to retain references to these streams after this event has fired. /// When these values are greater than zero it is not safe to retain references to feature components after this event has fired.
/// It is false by default. /// They are zero by default.
/// </summary> /// </summary>
/// <remarks> KestrelServerPoolingParameters PoolingParameters { get; }
/// When this is set to true it is not safe to retain references to these streams after this event has fired.
/// It is false by default.
/// </remarks>
bool ReuseStreams { get; set; }
IConnectionFilter ConnectionFilter { get; set; } IConnectionFilter ConnectionFilter { get; set; }
} }

View File

@ -0,0 +1,123 @@
// 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 IKestrelServerInformation ServerInformation { get; set; }
public HttpComponentFactory(IKestrelServerInformation serverInformation)
{
ServerInformation = serverInformation;
}
public Streams CreateStreams(FrameContext owner)
{
Streams streams;
if (!_streamPool.TryDequeue(out streams))
{
streams = new Streams();
}
streams.Initialize(owner);
return streams;
}
public void DisposeStreams(Streams streams, bool poolingPermitted)
{
if (poolingPermitted && _streamPool.Count < ServerInformation.PoolingParameters.MaxPooledStreams)
{
streams.Uninitialize();
_streamPool.Enqueue(streams);
}
}
public Headers CreateHeaders(DateHeaderValueManager dateValueManager)
{
Headers headers;
if (!_headerPool.TryDequeue(out headers))
{
headers = new Headers();
}
headers.Initialize(dateValueManager);
return headers;
}
public void DisposeHeaders(Headers headers, bool poolingPermitted)
{
if (poolingPermitted && _headerPool.Count < ServerInformation.PoolingParameters.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();
public Headers()
{
RequestHeaders = new FrameRequestHeaders();
ResponseHeaders = new FrameResponseHeaders();
}
public void Initialize(DateHeaderValueManager dateValueManager)
{
ResponseHeaders.SetRawDate(
dateValueManager.GetDateHeaderValue(),
dateValueManager.GetDateHeaderValueBytes());
ResponseHeaders.SetRawServer("Kestrel", 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(FrameContext renter)
{
ResponseBody.Initialize(renter);
}
public void Uninitialize()
{
ResponseBody.Uninitialize();
}
}
}

View File

@ -0,0 +1,20 @@
// 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
{
IKestrelServerInformation ServerInformation { get; set; }
Streams CreateStreams(FrameContext owner);
void DisposeStreams(Streams streams, bool poolingPermitted);
Headers CreateHeaders(DateHeaderValueManager dateValueManager);
void DisposeHeaders(Headers headers, bool poolingPermitted);
}
}

View File

@ -54,6 +54,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel
try try
{ {
var information = (KestrelServerInformation)Features.Get<IKestrelServerInformation>(); var information = (KestrelServerInformation)Features.Get<IKestrelServerInformation>();
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
@ -66,9 +67,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel
Log = trace, Log = trace,
ThreadPool = new LoggingThreadPool(trace), ThreadPool = new LoggingThreadPool(trace),
DateHeaderValueManager = dateHeaderValueManager, DateHeaderValueManager = dateHeaderValueManager,
ConnectionFilter = information.ConnectionFilter, ServerInformation = information,
NoDelay = information.NoDelay, HttpComponentFactory = componentFactory
ReuseStreams = information.ReuseStreams
}); });
_disposables.Push(engine); _disposables.Push(engine);

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel
Addresses = GetAddresses(configuration); Addresses = GetAddresses(configuration);
ThreadCount = GetThreadCount(configuration); ThreadCount = GetThreadCount(configuration);
NoDelay = GetNoDelay(configuration); NoDelay = GetNoDelay(configuration);
ReuseStreams = GetReuseStreams(configuration); PoolingParameters = new KestrelServerPoolingParameters(configuration);
} }
public ICollection<string> Addresses { get; } public ICollection<string> Addresses { get; }
@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel
public bool NoDelay { get; set; } public bool NoDelay { get; set; }
public bool ReuseStreams { get; set; } public KestrelServerPoolingParameters PoolingParameters { get; }
public IConnectionFilter ConnectionFilter { get; set; } public IConnectionFilter ConnectionFilter { get; set; }
@ -110,18 +110,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel
return true; return true;
} }
private static bool GetReuseStreams(IConfiguration configuration)
{
var reuseStreamsString = configuration["kestrel.reuseStreams"];
bool reuseStreams;
if (bool.TryParse(reuseStreamsString, out reuseStreams))
{
return reuseStreams;
}
return false;
}
} }
} }

View File

@ -0,0 +1,43 @@
// 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.Globalization;
using Microsoft.Extensions.Configuration;
namespace Microsoft.AspNetCore.Server.Kestrel
{
public class KestrelServerPoolingParameters
{
public KestrelServerPoolingParameters(IConfiguration configuration)
{
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
MaxPooledStreams = GetPooledCount(configuration["kestrel.maxPooledStreams"]);
MaxPooledHeaders = GetPooledCount(configuration["kestrel.maxPooledHeaders"]);
}
public int MaxPooledStreams { get; set; }
public int MaxPooledHeaders { get; set; }
private static int GetPooledCount(string countString)
{
if (string.IsNullOrEmpty(countString))
{
return 0;
}
int count;
if (int.TryParse(countString, NumberStyles.Integer, CultureInfo.InvariantCulture, out count))
{
return count;
}
return 0;
}
}
}

View File

@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Features; using Microsoft.AspNetCore.Server.Features;
using Microsoft.AspNetCore.Server.Kestrel.Infrastructure;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -28,8 +29,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel
public IServer CreateServer(IConfiguration configuration) public IServer CreateServer(IConfiguration configuration)
{ {
var information = new KestrelServerInformation(configuration); var information = new KestrelServerInformation(configuration);
var componentFactory = new HttpComponentFactory(information);
var serverFeatures = new FeatureCollection(); var serverFeatures = new FeatureCollection();
serverFeatures.Set<IKestrelServerInformation>(information); serverFeatures.Set<IKestrelServerInformation>(information);
serverFeatures.Set<IHttpComponentFactory>(componentFactory);
serverFeatures.Set<IServerAddressesFeature>(information); serverFeatures.Set<IServerAddressesFeature>(information);
return new KestrelServer(serverFeatures, _appLifetime, _loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel")); return new KestrelServer(serverFeatures, _appLifetime, _loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel"));
} }

View File

@ -24,9 +24,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel
ThreadPool = context.ThreadPool; ThreadPool = context.ThreadPool;
FrameFactory = context.FrameFactory; FrameFactory = context.FrameFactory;
DateHeaderValueManager = context.DateHeaderValueManager; DateHeaderValueManager = context.DateHeaderValueManager;
ConnectionFilter = context.ConnectionFilter; ServerInformation = context.ServerInformation;
NoDelay = context.NoDelay; HttpComponentFactory = context.HttpComponentFactory;
ReuseStreams = context.ReuseStreams;
} }
public IApplicationLifetime AppLifetime { get; set; } public IApplicationLifetime AppLifetime { get; set; }
@ -39,10 +38,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel
public DateHeaderValueManager DateHeaderValueManager { get; set; } public DateHeaderValueManager DateHeaderValueManager { get; set; }
public IConnectionFilter ConnectionFilter { get; set; } public IKestrelServerInformation ServerInformation { get; set; }
public bool NoDelay { get; set; } internal IHttpComponentFactory HttpComponentFactory { get; set; }
public bool ReuseStreams { get; set; }
} }
} }

View File

@ -132,7 +132,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
[ConditionalFact] [ConditionalFact]
[FrameworkSkipCondition(RuntimeFrameworks.Mono, SkipReason = "Test hangs after execution on Mono.")] [FrameworkSkipCondition(RuntimeFrameworks.Mono, SkipReason = "Test hangs after execution on Mono.")]
public async Task RequestPathIsNormalized() public void RequestPathIsNormalized()
{ {
var port = PortManager.GetPort(); var port = PortManager.GetPort();
var config = new ConfigurationBuilder().AddInMemoryCollection( var config = new ConfigurationBuilder().AddInMemoryCollection(

View File

@ -36,10 +36,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
public async Task CanReadAndWriteWithRewritingConnectionFilter() public async Task CanReadAndWriteWithRewritingConnectionFilter()
{ {
var filter = new RewritingConnectionFilter(); var filter = new RewritingConnectionFilter();
var serviceContext = new TestServiceContext() var serviceContext = new TestServiceContext(filter);
{
ConnectionFilter = filter
};
var sendString = "POST / HTTP/1.0\r\n\r\nHello World?"; var sendString = "POST / HTTP/1.0\r\n\r\nHello World?";
using (var server = new TestServer(App, serviceContext)) using (var server = new TestServer(App, serviceContext))
@ -62,10 +60,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[FrameworkSkipCondition(RuntimeFrameworks.Mono, SkipReason = "Test hangs after execution on Mono.")] [FrameworkSkipCondition(RuntimeFrameworks.Mono, SkipReason = "Test hangs after execution on Mono.")]
public async Task CanReadAndWriteWithAsyncConnectionFilter() public async Task CanReadAndWriteWithAsyncConnectionFilter()
{ {
var serviceContext = new TestServiceContext() var serviceContext = new TestServiceContext(new AsyncConnectionFilter());
{
ConnectionFilter = new AsyncConnectionFilter()
};
using (var server = new TestServer(App, serviceContext)) using (var server = new TestServer(App, serviceContext))
{ {
@ -87,10 +82,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[FrameworkSkipCondition(RuntimeFrameworks.Mono, SkipReason = "Test hangs after execution on Mono.")] [FrameworkSkipCondition(RuntimeFrameworks.Mono, SkipReason = "Test hangs after execution on Mono.")]
public async Task ThrowingSynchronousConnectionFilterDoesNotCrashServer() public async Task ThrowingSynchronousConnectionFilterDoesNotCrashServer()
{ {
var serviceContext = new TestServiceContext() var serviceContext = new TestServiceContext(new ThrowingConnectionFilter());
{
ConnectionFilter = new ThrowingConnectionFilter()
};
using (var server = new TestServer(App, serviceContext)) using (var server = new TestServer(App, serviceContext))
{ {

View File

@ -35,10 +35,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
new TestServiceContext() new TestServiceContext()
}, },
{ {
new TestServiceContext new TestServiceContext(new PassThroughConnectionFilter())
{
ConnectionFilter = new PassThroughConnectionFilter()
}
} }
}; };
} }
@ -188,7 +185,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[MemberData(nameof(ConnectionFilterData))] [MemberData(nameof(ConnectionFilterData))]
public async Task ReuseStreamsOn(ServiceContext testContext) public async Task ReuseStreamsOn(ServiceContext testContext)
{ {
testContext.ReuseStreams = true; testContext.ServerInformation.PoolingParameters.MaxPooledStreams = 120;
var streamCount = 0; var streamCount = 0;
var loopCount = 20; var loopCount = 20;
@ -231,7 +228,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[MemberData(nameof(ConnectionFilterData))] [MemberData(nameof(ConnectionFilterData))]
public async Task ReuseStreamsOff(ServiceContext testContext) public async Task ReuseStreamsOff(ServiceContext testContext)
{ {
testContext.ReuseStreams = false; testContext.ServerInformation.PoolingParameters.MaxPooledStreams = 0;
var streamCount = 0; var streamCount = 0;
var loopCount = 20; var loopCount = 20;

View File

@ -5,6 +5,8 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
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;
@ -15,12 +17,18 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[Fact] [Fact]
public void InitialDictionaryContainsServerAndDate() public void InitialDictionaryContainsServerAndDate()
{ {
var configuration = new ConfigurationBuilder().Build();
var serverInformation = new KestrelServerInformation(configuration);
var connectionContext = new ConnectionContext var connectionContext = new ConnectionContext
{ {
DateHeaderValueManager = new DateHeaderValueManager(), DateHeaderValueManager = new DateHeaderValueManager(),
ServerAddress = ServerAddress.FromUrl("http://localhost:5000") ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
}; ServerInformation = serverInformation,
var frame = new Frame<object>(application: null, context: connectionContext); HttpComponentFactory = new HttpComponentFactory(serverInformation)
};
var frame = new Frame<object>(application: null, context: connectionContext)
.InitializeHeaders();
IDictionary<string, StringValues> headers = frame.ResponseHeaders; IDictionary<string, StringValues> headers = frame.ResponseHeaders;
Assert.Equal(2, headers.Count); Assert.Equal(2, headers.Count);
@ -43,13 +51,18 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[Fact] [Fact]
public void InitialEntriesCanBeCleared() public void InitialEntriesCanBeCleared()
{ {
var configuration = new ConfigurationBuilder().Build();
var serverInformation = new KestrelServerInformation(configuration);
var connectionContext = new ConnectionContext var connectionContext = new ConnectionContext
{ {
DateHeaderValueManager = new DateHeaderValueManager(), DateHeaderValueManager = new DateHeaderValueManager(),
ServerAddress = ServerAddress.FromUrl("http://localhost:5000") ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
ServerInformation = serverInformation,
HttpComponentFactory = new HttpComponentFactory(serverInformation)
}; };
var frame = new Frame<object>(application: null, context: connectionContext); var frame = new Frame<object>(application: null, context: connectionContext)
.InitializeHeaders();
Assert.True(frame.ResponseHeaders.Count > 0); Assert.True(frame.ResponseHeaders.Count > 0);
frame.ResponseHeaders.Clear(); frame.ResponseHeaders.Clear();

View File

@ -59,13 +59,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
handler.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; handler.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
#endif #endif
var serverAddress = $"https://localhost:{TestServer.GetNextPort()}/"; var serverAddress = $"https://localhost:{TestServer.GetNextPort()}/";
var serviceContext = new TestServiceContext() var serviceContext = new TestServiceContext(new HttpsConnectionFilter(
{
ConnectionFilter = new HttpsConnectionFilter(
new HttpsConnectionFilterOptions new HttpsConnectionFilterOptions
{ ServerCertificate = new X509Certificate2(@"TestResources/testCert.pfx", "testPassword")}, { ServerCertificate = new X509Certificate2(@"TestResources/testCert.pfx", "testPassword") },
new NoOpConnectionFilter()) new NoOpConnectionFilter())
}; );
using (var server = new TestServer(App, serviceContext, serverAddress)) using (var server = new TestServer(App, serviceContext, serverAddress))
{ {
@ -108,16 +106,14 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
#endif #endif
var serverAddress = $"https://localhost:{TestServer.GetNextPort()}/"; var serverAddress = $"https://localhost:{TestServer.GetNextPort()}/";
var serviceContext = new TestServiceContext() var serviceContext = new TestServiceContext(new HttpsConnectionFilter(
{
ConnectionFilter = new HttpsConnectionFilter(
new HttpsConnectionFilterOptions new HttpsConnectionFilterOptions
{ {
ServerCertificate = new X509Certificate2(@"TestResources/testCert.pfx", "testPassword"), ServerCertificate = new X509Certificate2(@"TestResources/testCert.pfx", "testPassword"),
ClientCertificateMode = ClientCertificateMode.RequireCertificate ClientCertificateMode = ClientCertificateMode.RequireCertificate
}, },
new NoOpConnectionFilter()) new NoOpConnectionFilter())
}; );
using (var server = new TestServer(App, serviceContext, serverAddress)) using (var server = new TestServer(App, serviceContext, serverAddress))
{ {
@ -157,16 +153,14 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
#endif #endif
var serverAddress = $"https://localhost:{TestServer.GetNextPort()}/"; var serverAddress = $"https://localhost:{TestServer.GetNextPort()}/";
var serviceContext = new TestServiceContext() var serviceContext = new TestServiceContext(new HttpsConnectionFilter(
{ new HttpsConnectionFilterOptions
ConnectionFilter = new HttpsConnectionFilter( {
new HttpsConnectionFilterOptions ServerCertificate = new X509Certificate2(@"TestResources/testCert.pfx", "testPassword"),
{ ClientCertificateMode = ClientCertificateMode.AllowCertificate
ServerCertificate = new X509Certificate2(@"TestResources/testCert.pfx", "testPassword"), },
ClientCertificateMode = ClientCertificateMode.AllowCertificate new NoOpConnectionFilter())
}, );
new NoOpConnectionFilter())
};
RequestDelegate app = context => RequestDelegate app = context =>
{ {
@ -209,17 +203,15 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
#endif #endif
var serverAddress = $"https://localhost:{TestServer.GetNextPort()}/"; var serverAddress = $"https://localhost:{TestServer.GetNextPort()}/";
var serviceContext = new TestServiceContext() var serviceContext = new TestServiceContext(new HttpsConnectionFilter(
{ new HttpsConnectionFilterOptions
ConnectionFilter = new HttpsConnectionFilter( {
new HttpsConnectionFilterOptions ServerCertificate = new X509Certificate2(@"TestResources/testCert.pfx", "testPassword"),
{ ClientCertificateMode = ClientCertificateMode.RequireCertificate,
ServerCertificate = new X509Certificate2(@"TestResources/testCert.pfx", "testPassword"), ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => true
ClientCertificateMode = ClientCertificateMode.RequireCertificate, },
ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => true new NoOpConnectionFilter())
}, );
new NoOpConnectionFilter())
};
RequestDelegate app = context => RequestDelegate app = context =>
{ {
@ -281,15 +273,14 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
#endif #endif
var serverAddress = $"https://localhost:{TestServer.GetNextPort()}/"; var serverAddress = $"https://localhost:{TestServer.GetNextPort()}/";
var serviceContext = new TestServiceContext() var serviceContext = new TestServiceContext(
{ new HttpsConnectionFilter(
ConnectionFilter = new HttpsConnectionFilter(
new HttpsConnectionFilterOptions new HttpsConnectionFilterOptions
{ {
ServerCertificate = new X509Certificate2(@"TestResources/testCert.pfx", "testPassword") ServerCertificate = new X509Certificate2(@"TestResources/testCert.pfx", "testPassword")
}, },
new NoOpConnectionFilter()) new NoOpConnectionFilter())
}; );
RequestDelegate app = context => context.Response.WriteAsync(context.Request.Scheme); RequestDelegate app = context => context.Response.WriteAsync(context.Request.Scheme);

View File

@ -86,19 +86,19 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
} }
[Theory] [Theory]
[InlineData(null, false)] [InlineData(null, 0)]
[InlineData("", false)] [InlineData("", 0)]
[InlineData("false", false)] [InlineData("0", 0)]
[InlineData("False", false)] [InlineData("00", 0)]
[InlineData("true", true)] [InlineData("0.0", 0)]
[InlineData("True", true)] [InlineData("1", 1)]
[InlineData("Foo", false)] [InlineData("16", 16)]
[InlineData("FooBar", false)] [InlineData("1000", 1000)]
public void SetReuseStreamsUsingConfiguration(string input, bool expected) public void SetMaxPooledStreamsUsingConfiguration(string input, int expected)
{ {
var values = new Dictionary<string, string> var values = new Dictionary<string, string>
{ {
{ "kestrel.reuseStreams", input } { "kestrel.maxPooledStreams", input }
}; };
var configuration = new ConfigurationBuilder() var configuration = new ConfigurationBuilder()
@ -107,7 +107,33 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var information = new KestrelServerInformation(configuration); var information = new KestrelServerInformation(configuration);
Assert.Equal(expected, information.ReuseStreams); Assert.Equal(expected, information.PoolingParameters.MaxPooledStreams);
}
[Theory]
[InlineData(null, 0)]
[InlineData("", 0)]
[InlineData("0", 0)]
[InlineData("00", 0)]
[InlineData("0.0", 0)]
[InlineData("1", 1)]
[InlineData("16", 16)]
[InlineData("1000", 1000)]
public void SetMaxPooledHeadersUsingConfiguration(string input, int expected)
{
var values = new Dictionary<string, string>
{
{ "kestrel.maxPooledHeaders", input }
};
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(values)
.Build();
var information = new KestrelServerInformation(configuration);
Assert.Equal(expected, information.PoolingParameters.MaxPooledHeaders);
} }
private static int Clamp(int value, int min, int max) private static int Clamp(int value, int min, int max)

View File

@ -3,8 +3,10 @@
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel; using Microsoft.AspNetCore.Server.Kestrel;
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
{ {
@ -18,6 +20,16 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
Log = new TestKestrelTrace(); Log = new TestKestrelTrace();
ThreadPool = new LoggingThreadPool(Log); ThreadPool = new LoggingThreadPool(Log);
DateHeaderValueManager = new TestDateHeaderValueManager(); DateHeaderValueManager = new TestDateHeaderValueManager();
var configuration = new ConfigurationBuilder().Build();
ServerInformation = new KestrelServerInformation(configuration);
HttpComponentFactory = new HttpComponentFactory(ServerInformation);
}
public TestServiceContext(IConnectionFilter filter)
: base()
{
ServerInformation.ConnectionFilter = filter;
} }
public RequestDelegate App public RequestDelegate App

View File

@ -216,10 +216,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
: "")} : "")}
private long _bits = 0; private long _bits = 0;
{Each(loop.Headers, header => @" private HeaderReferences _headers;
private StringValues _" + header.Identifier + ";")}
{Each(loop.Headers.Where(header => header.EnhancedSetter), header => @"
private byte[] _raw" + header.Identifier + ";")}
{Each(loop.Headers, header => $@" {Each(loop.Headers, header => $@"
public StringValues Header{header.Identifier} public StringValues Header{header.Identifier}
{{ {{
@ -227,23 +224,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
{{ {{
if ({header.TestBit()}) if ({header.TestBit()})
{{ {{
return _{header.Identifier}; return _headers._{header.Identifier};
}} }}
return StringValues.Empty; return StringValues.Empty;
}} }}
set set
{{ {{
{header.SetBit()}; {header.SetBit()};
_{header.Identifier} = value; {(header.EnhancedSetter == false ? "" : $@" _headers._{header.Identifier} = value; {(header.EnhancedSetter == false ? "" : $@"
_raw{header.Identifier} = null;")} _headers._raw{header.Identifier} = null;")}
}} }}
}}")} }}")}
{Each(loop.Headers.Where(header => header.EnhancedSetter), header => $@" {Each(loop.Headers.Where(header => header.EnhancedSetter), header => $@"
public void SetRaw{header.Identifier}(StringValues value, byte[] raw) public void SetRaw{header.Identifier}(StringValues value, byte[] raw)
{{ {{
{header.SetBit()}; {header.SetBit()};
_{header.Identifier} = value; _headers._{header.Identifier} = value;
_raw{header.Identifier} = raw; _headers._raw{header.Identifier} = raw;
}}")} }}")}
protected override int GetCountFast() protected override int GetCountFast()
{{ {{
@ -259,11 +256,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
{{ {{
if ({header.TestBit()}) if ({header.TestBit()})
{{ {{
return _{header.Identifier}; return _headers._{header.Identifier};
}} }}
else else
{{ {{
throw new System.Collections.Generic.KeyNotFoundException(); ThrowKeyNotFoundException();
}} }}
}} }}
")}}} ")}}}
@ -271,7 +268,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
")}}} ")}}}
if (MaybeUnknown == null) if (MaybeUnknown == null)
{{ {{
throw new System.Collections.Generic.KeyNotFoundException(); ThrowKeyNotFoundException();
}} }}
return MaybeUnknown[key]; return MaybeUnknown[key];
}} }}
@ -285,7 +282,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
{{ {{
if ({header.TestBit()}) if ({header.TestBit()})
{{ {{
value = _{header.Identifier}; value = _headers._{header.Identifier};
return true; return true;
}} }}
else else
@ -309,8 +306,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
if (""{header.Name}"".Equals(key, StringComparison.OrdinalIgnoreCase)) if (""{header.Name}"".Equals(key, StringComparison.OrdinalIgnoreCase))
{{ {{
{header.SetBit()}; {header.SetBit()};
_{header.Identifier} = value;{(header.EnhancedSetter == false ? "" : $@" _headers._{header.Identifier} = value;{(header.EnhancedSetter == false ? "" : $@"
_raw{header.Identifier} = null;")} _headers._raw{header.Identifier} = null;")}
return; return;
}} }}
")}}} ")}}}
@ -328,11 +325,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
{{ {{
if ({header.TestBit()}) if ({header.TestBit()})
{{ {{
throw new ArgumentException(""An item with the same key has already been added.""); ThrowDuplicateKeyException();
}} }}
{header.SetBit()}; {header.SetBit()};
_{header.Identifier} = value;{(header.EnhancedSetter == false ? "" : $@" _headers._{header.Identifier} = value;{(header.EnhancedSetter == false ? "" : $@"
_raw{header.Identifier} = null;")} _headers._raw{header.Identifier} = null;")}
return; return;
}} }}
")}}} ")}}}
@ -351,8 +348,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
if ({header.TestBit()}) if ({header.TestBit()})
{{ {{
{header.ClearBit()}; {header.ClearBit()};
_{header.Identifier} = StringValues.Empty;{(header.EnhancedSetter == false ? "" : $@" _headers._{header.Identifier} = StringValues.Empty;{(header.EnhancedSetter == false ? "" : $@"
_raw{header.Identifier} = null;")} _headers._raw{header.Identifier} = null;")}
return true; return true;
}} }}
else else
@ -368,6 +365,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
protected override void ClearFast() protected override void ClearFast()
{{ {{
_bits = 0; _bits = 0;
_headers = default(HeaderReferences);
MaybeUnknown?.Clear(); MaybeUnknown?.Clear();
}} }}
@ -375,17 +373,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
{{ {{
if (arrayIndex < 0) if (arrayIndex < 0)
{{ {{
throw new ArgumentException(); ThrowArgumentException();
}} }}
{Each(loop.Headers, header => $@" {Each(loop.Headers, header => $@"
if ({header.TestBit()}) if ({header.TestBit()})
{{ {{
if (arrayIndex == array.Length) if (arrayIndex == array.Length)
{{ {{
throw new ArgumentException(); ThrowArgumentException();
}} }}
array[arrayIndex] = new KeyValuePair<string, StringValues>(""{header.Name}"", _{header.Identifier}); array[arrayIndex] = new KeyValuePair<string, StringValues>(""{header.Name}"", _headers._{header.Identifier});
++arrayIndex; ++arrayIndex;
}} }}
")} ")}
@ -397,12 +395,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
{Each(loop.Headers, header => $@" {Each(loop.Headers, header => $@"
if ({header.TestBit()}) if ({header.TestBit()})
{{ {(header.EnhancedSetter == false ? "" : $@" {{ {(header.EnhancedSetter == false ? "" : $@"
if (_raw{header.Identifier} != null) if (_headers._raw{header.Identifier} != null)
{{ {{
output.CopyFrom(_raw{header.Identifier}, 0, _raw{header.Identifier}.Length); output.CopyFrom(_headers._raw{header.Identifier}, 0, _headers._raw{header.Identifier}.Length);
}} }}
else ")} else ")}
foreach (var value in _{header.Identifier}) foreach (var value in _headers._{header.Identifier})
{{ {{
if (value != null) if (value != null)
{{ {{
@ -413,6 +411,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
}} }}
")} ")}
}}" : "")} }}" : "")}
{(loop.ClassName == "FrameRequestHeaders" ? $@"
public unsafe void Append(byte[] keyBytes, int keyOffset, int keyLength, string value) public unsafe void Append(byte[] keyBytes, int keyOffset, int keyLength, string value)
{{ {{
fixed (byte* ptr = &keyBytes[keyOffset]) fixed (byte* ptr = &keyBytes[keyOffset])
@ -429,13 +428,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
{{ {{
if ({header.TestBit()}) if ({header.TestBit()})
{{ {{
_{header.Identifier} = AppendValue(_{header.Identifier}, value); _headers._{header.Identifier} = AppendValue(_headers._{header.Identifier}, value);
}} }}
else else
{{ {{
{header.SetBit()}; {header.SetBit()};
_{header.Identifier} = new StringValues(value);{(header.EnhancedSetter == false ? "" : $@" _headers._{header.Identifier} = new StringValues(value);{(header.EnhancedSetter == false ? "" : $@"
_raw{header.Identifier} = null;")} _headers._raw{header.Identifier} = null;")}
}} }}
return; return;
}} }}
@ -447,7 +446,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
StringValues existing; StringValues existing;
Unknown.TryGetValue(key, out existing); Unknown.TryGetValue(key, out existing);
Unknown[key] = AppendValue(existing, value); Unknown[key] = AppendValue(existing, value);
}}" : "")}
private struct HeaderReferences
{{{Each(loop.Headers, header => @"
public StringValues _" + header.Identifier + ";")}
{Each(loop.Headers.Where(header => header.EnhancedSetter), header => @"
public byte[] _raw" + header.Identifier + ";")}
}} }}
public partial struct Enumerator public partial struct Enumerator
{{ {{
public bool MoveNext() public bool MoveNext()
@ -465,7 +471,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
state{header.Index}: state{header.Index}:
if ({header.TestBit()}) if ({header.TestBit()})
{{ {{
_current = new KeyValuePair<string, StringValues>(""{header.Name}"", _collection._{header.Identifier}); _current = new KeyValuePair<string, StringValues>(""{header.Name}"", _collection._headers._{header.Identifier});
_state = {header.Index + 1}; _state = {header.Index + 1};
return true; return true;
}} }}