Merge branch 'benaadams/faster-headers' into dev
This commit is contained in:
commit
07c0b414f0
|
|
@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Filter
|
||||||
IKestrelTrace logger)
|
IKestrelTrace logger)
|
||||||
{
|
{
|
||||||
SocketInput = new SocketInput(memory);
|
SocketInput = new SocketInput(memory);
|
||||||
SocketOutput = new StreamSocketOutput(filteredStream);
|
SocketOutput = new StreamSocketOutput(filteredStream, memory);
|
||||||
|
|
||||||
_log = logger;
|
_log = logger;
|
||||||
_filteredStream = filteredStream;
|
_filteredStream = filteredStream;
|
||||||
|
|
|
||||||
|
|
@ -68,13 +68,29 @@ namespace Microsoft.AspNet.Server.Kestrel.Filter
|
||||||
|
|
||||||
public override void Write(byte[] buffer, int offset, int count)
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
{
|
{
|
||||||
var segment = new ArraySegment<byte>(buffer, offset, count);
|
ArraySegment<byte> segment;
|
||||||
|
if (buffer != null)
|
||||||
|
{
|
||||||
|
segment = new ArraySegment<byte>(buffer, offset, count);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
segment = default(ArraySegment<byte>);
|
||||||
|
}
|
||||||
_output.Write(segment);
|
_output.Write(segment);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken token)
|
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken token)
|
||||||
{
|
{
|
||||||
var segment = new ArraySegment<byte>(buffer, offset, count);
|
ArraySegment<byte> segment;
|
||||||
|
if (buffer != null)
|
||||||
|
{
|
||||||
|
segment = new ArraySegment<byte>(buffer, offset, count);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
segment = default(ArraySegment<byte>);
|
||||||
|
}
|
||||||
return _output.WriteAsync(segment, cancellationToken: token);
|
return _output.WriteAsync(segment, cancellationToken: token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,13 @@ namespace Microsoft.AspNet.Server.Kestrel.Filter
|
||||||
public class StreamSocketOutput : ISocketOutput
|
public class StreamSocketOutput : ISocketOutput
|
||||||
{
|
{
|
||||||
private readonly Stream _outputStream;
|
private readonly Stream _outputStream;
|
||||||
|
private readonly MemoryPool2 _memory;
|
||||||
|
private MemoryPoolBlock2 _producingBlock;
|
||||||
|
|
||||||
public StreamSocketOutput(Stream outputStream)
|
public StreamSocketOutput(Stream outputStream, MemoryPool2 memory)
|
||||||
{
|
{
|
||||||
_outputStream = outputStream;
|
_outputStream = outputStream;
|
||||||
|
_memory = memory;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ISocketOutput.Write(ArraySegment<byte> buffer, bool immediate)
|
void ISocketOutput.Write(ArraySegment<byte> buffer, bool immediate)
|
||||||
|
|
@ -30,5 +33,27 @@ namespace Microsoft.AspNet.Server.Kestrel.Filter
|
||||||
_outputStream.Write(buffer.Array, buffer.Offset, buffer.Count);
|
_outputStream.Write(buffer.Array, buffer.Offset, buffer.Count);
|
||||||
return TaskUtilities.CompletedTask;
|
return TaskUtilities.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MemoryPoolIterator2 ProducingStart()
|
||||||
|
{
|
||||||
|
_producingBlock = _memory.Lease();
|
||||||
|
return new MemoryPoolIterator2(_producingBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ProducingComplete(MemoryPoolIterator2 end, int count)
|
||||||
|
{
|
||||||
|
var block = _producingBlock;
|
||||||
|
while (block != end.Block)
|
||||||
|
{
|
||||||
|
_outputStream.Write(block.Data.Array, block.Data.Offset, block.Data.Count);
|
||||||
|
|
||||||
|
var returnBlock = block;
|
||||||
|
block = block.Next;
|
||||||
|
returnBlock.Pool?.Return(returnBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
_outputStream.Write(end.Block.Array, end.Block.Data.Offset, end.Index - end.Block.Data.Offset);
|
||||||
|
end.Block.Pool?.Return(end.Block);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
_connectionId = Interlocked.Increment(ref _lastConnectionId);
|
_connectionId = Interlocked.Increment(ref _lastConnectionId);
|
||||||
|
|
||||||
_rawSocketInput = new SocketInput(Memory2);
|
_rawSocketInput = new SocketInput(Memory2);
|
||||||
_rawSocketOutput = new SocketOutput(Thread, _socket, this, _connectionId, Log);
|
_rawSocketOutput = new SocketOutput(Thread, _socket, Memory2, this, _connectionId, Log);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
|
|
@ -116,7 +116,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
// called from a libuv thread.
|
// called from a libuv thread.
|
||||||
ThreadPool.QueueUserWorkItem(state =>
|
ThreadPool.QueueUserWorkItem(state =>
|
||||||
{
|
{
|
||||||
var connection = (Connection)this;
|
var connection = (Connection)state;
|
||||||
connection._frame.Abort();
|
connection._frame.Abort();
|
||||||
}, this);
|
}, this);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +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.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
|
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
|
||||||
|
|
||||||
|
|
@ -17,6 +18,9 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
private readonly TimeSpan _timerInterval;
|
private readonly TimeSpan _timerInterval;
|
||||||
|
|
||||||
private volatile string _dateValue;
|
private volatile string _dateValue;
|
||||||
|
private volatile bool _activeDateBytes;
|
||||||
|
private readonly byte[] _dateBytes0 = Encoding.ASCII.GetBytes("\r\nDate: DDD, dd mmm yyyy hh:mm:ss GMT");
|
||||||
|
private readonly byte[] _dateBytes1 = Encoding.ASCII.GetBytes("\r\nDate: DDD, dd mmm yyyy hh:mm:ss GMT");
|
||||||
private object _timerLocker = new object();
|
private object _timerLocker = new object();
|
||||||
private bool _isDisposed = false;
|
private bool _isDisposed = false;
|
||||||
private bool _hadRequestsSinceLastTimerTick = false;
|
private bool _hadRequestsSinceLastTimerTick = false;
|
||||||
|
|
@ -62,6 +66,12 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
return _dateValue ?? _systemClock.UtcNow.ToString(Constants.RFC1123DateFormat);
|
return _dateValue ?? _systemClock.UtcNow.ToString(Constants.RFC1123DateFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] GetDateHeaderValueBytes()
|
||||||
|
{
|
||||||
|
PumpTimer();
|
||||||
|
return _activeDateBytes ? _dateBytes0 : _dateBytes1;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Releases all resources used by the current instance of <see cref="DateHeaderValueManager"/>.
|
/// Releases all resources used by the current instance of <see cref="DateHeaderValueManager"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -92,6 +102,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
// here as the timer won't fire until the timer interval has passed and we want a value assigned
|
// here as the timer won't fire until the timer interval has passed and we want a value assigned
|
||||||
// inline now to serve requests that occur in the meantime.
|
// inline now to serve requests that occur in the meantime.
|
||||||
_dateValue = _systemClock.UtcNow.ToString(Constants.RFC1123DateFormat);
|
_dateValue = _systemClock.UtcNow.ToString(Constants.RFC1123DateFormat);
|
||||||
|
Encoding.ASCII.GetBytes(_dateValue, 0, _dateValue.Length, !_activeDateBytes ? _dateBytes0 : _dateBytes1, "\r\nDate: ".Length);
|
||||||
|
_activeDateBytes = !_activeDateBytes;
|
||||||
_dateValueTimer = new Timer(UpdateDateValue, state: null, dueTime: _timerInterval, period: _timerInterval);
|
_dateValueTimer = new Timer(UpdateDateValue, state: null, dueTime: _timerInterval, period: _timerInterval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -105,6 +117,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
|
|
||||||
// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.18 for required format of Date header
|
// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.18 for required format of Date header
|
||||||
_dateValue = now.ToString(Constants.RFC1123DateFormat);
|
_dateValue = now.ToString(Constants.RFC1123DateFormat);
|
||||||
|
Encoding.ASCII.GetBytes(_dateValue, 0, _dateValue.Length, !_activeDateBytes ? _dateBytes0 : _dateBytes1, "\r\nDate: ".Length);
|
||||||
|
_activeDateBytes = !_activeDateBytes;
|
||||||
|
|
||||||
if (_hadRequestsSinceLastTimerTick)
|
if (_hadRequestsSinceLastTimerTick)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,17 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
private static readonly ArraySegment<byte> _emptyData = new ArraySegment<byte>(new byte[0]);
|
private static readonly ArraySegment<byte> _emptyData = new ArraySegment<byte>(new byte[0]);
|
||||||
private static readonly byte[] _hex = Encoding.ASCII.GetBytes("0123456789abcdef");
|
private static readonly byte[] _hex = Encoding.ASCII.GetBytes("0123456789abcdef");
|
||||||
|
|
||||||
|
private static readonly byte[] _bytesConnectionClose = Encoding.ASCII.GetBytes("\r\nConnection: close");
|
||||||
|
private static readonly byte[] _bytesConnectionKeepAlive = Encoding.ASCII.GetBytes("\r\nConnection: keep-alive");
|
||||||
|
private static readonly byte[] _bytesTransferEncodingChunked = Encoding.ASCII.GetBytes("\r\nTransfer-Encoding: chunked");
|
||||||
|
private static readonly byte[] _bytesHttpVersion1_0 = Encoding.ASCII.GetBytes("HTTP/1.0 ");
|
||||||
|
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[] _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 readonly object _onStartingSync = new Object();
|
private readonly object _onStartingSync = new Object();
|
||||||
private readonly object _onCompletedSync = new Object();
|
private readonly object _onCompletedSync = new Object();
|
||||||
private readonly FrameRequestHeaders _requestHeaders = new FrameRequestHeaders();
|
private readonly FrameRequestHeaders _requestHeaders = new FrameRequestHeaders();
|
||||||
|
|
@ -53,6 +64,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
private bool _autoChunk;
|
private bool _autoChunk;
|
||||||
private Exception _applicationException;
|
private Exception _applicationException;
|
||||||
|
|
||||||
|
private HttpVersionType _httpVersion;
|
||||||
|
|
||||||
private readonly IPEndPoint _localEndPoint;
|
private readonly IPEndPoint _localEndPoint;
|
||||||
private readonly IPEndPoint _remoteEndPoint;
|
private readonly IPEndPoint _remoteEndPoint;
|
||||||
private readonly Action<IFeatureCollection> _prepareRequest;
|
private readonly Action<IFeatureCollection> _prepareRequest;
|
||||||
|
|
@ -81,7 +94,37 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
public string RequestUri { get; set; }
|
public string RequestUri { get; set; }
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
public string QueryString { get; set; }
|
public string QueryString { get; set; }
|
||||||
public string HttpVersion { get; set; }
|
public string HttpVersion
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_httpVersion == HttpVersionType.Http1_1)
|
||||||
|
{
|
||||||
|
return "HTTP/1.1";
|
||||||
|
}
|
||||||
|
if (_httpVersion == HttpVersionType.Http1_0)
|
||||||
|
{
|
||||||
|
return "HTTP/1.0";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == "HTTP/1.1")
|
||||||
|
{
|
||||||
|
_httpVersion = HttpVersionType.Http1_1;
|
||||||
|
}
|
||||||
|
else if (value == "HTTP/1.0")
|
||||||
|
{
|
||||||
|
_httpVersion = HttpVersionType.Http1_0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_httpVersion = HttpVersionType.Unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public IHeaderDictionary RequestHeaders { get; set; }
|
public IHeaderDictionary RequestHeaders { get; set; }
|
||||||
public Stream RequestBody { get; set; }
|
public Stream RequestBody { get; set; }
|
||||||
|
|
||||||
|
|
@ -118,7 +161,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
RequestUri = null;
|
RequestUri = null;
|
||||||
Path = null;
|
Path = null;
|
||||||
QueryString = null;
|
QueryString = null;
|
||||||
HttpVersion = null;
|
_httpVersion = HttpVersionType.Unknown;
|
||||||
RequestHeaders = _requestHeaders;
|
RequestHeaders = _requestHeaders;
|
||||||
RequestBody = null;
|
RequestBody = null;
|
||||||
StatusCode = 200;
|
StatusCode = 200;
|
||||||
|
|
@ -151,8 +194,12 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
public void ResetResponseHeaders()
|
public void ResetResponseHeaders()
|
||||||
{
|
{
|
||||||
_responseHeaders.Reset();
|
_responseHeaders.Reset();
|
||||||
_responseHeaders.HeaderServer = "Kestrel";
|
_responseHeaders.SetRawDate(
|
||||||
_responseHeaders.HeaderDate = DateHeaderValueManager.GetDateHeaderValue();
|
DateHeaderValueManager.GetDateHeaderValue(),
|
||||||
|
DateHeaderValueManager.GetDateHeaderValueBytes());
|
||||||
|
_responseHeaders.SetRawServer(
|
||||||
|
"Kestrel",
|
||||||
|
_bytesServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -505,7 +552,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
if (_responseStarted) return;
|
if (_responseStarted) return;
|
||||||
|
|
||||||
StringValues expect;
|
StringValues expect;
|
||||||
if (HttpVersion.Equals("HTTP/1.1") &&
|
if (_httpVersion == HttpVersionType.Http1_1 &&
|
||||||
RequestHeaders.TryGetValue("Expect", out expect) &&
|
RequestHeaders.TryGetValue("Expect", out expect) &&
|
||||||
(expect.FirstOrDefault() ?? "").Equals("100-continue", StringComparison.OrdinalIgnoreCase))
|
(expect.FirstOrDefault() ?? "").Equals("100-continue", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
|
@ -529,19 +576,14 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
await ProduceStart(immediate, appCompleted: false);
|
await ProduceStart(immediate, appCompleted: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProduceStart(bool immediate, bool appCompleted)
|
private Task ProduceStart(bool immediate, bool appCompleted)
|
||||||
{
|
{
|
||||||
if (_responseStarted) return;
|
if (_responseStarted) return TaskUtilities.CompletedTask;
|
||||||
_responseStarted = true;
|
_responseStarted = true;
|
||||||
|
|
||||||
var status = ReasonPhrases.ToStatus(StatusCode, ReasonPhrase);
|
var statusBytes = ReasonPhrases.ToStatusBytes(StatusCode, ReasonPhrase);
|
||||||
|
|
||||||
var responseHeader = CreateResponseHeader(status, appCompleted);
|
return CreateResponseHeader(statusBytes, appCompleted, immediate);
|
||||||
|
|
||||||
using (responseHeader.Item2)
|
|
||||||
{
|
|
||||||
await SocketOutput.WriteAsync(responseHeader.Item1, immediate: immediate);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProduceEnd()
|
private async Task ProduceEnd()
|
||||||
|
|
@ -560,7 +602,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
ReasonPhrase = null;
|
ReasonPhrase = null;
|
||||||
|
|
||||||
ResetResponseHeaders();
|
ResetResponseHeaders();
|
||||||
_responseHeaders.HeaderContentLength = "0";
|
_responseHeaders.SetRawContentLength("0", _bytesContentLengthZero);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -579,58 +621,26 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Tuple<ArraySegment<byte>, IDisposable> CreateResponseHeader(
|
private Task CreateResponseHeader(
|
||||||
string status,
|
byte[] statusBytes,
|
||||||
bool appCompleted)
|
bool appCompleted,
|
||||||
|
bool immediate)
|
||||||
{
|
{
|
||||||
var writer = new MemoryPoolTextWriter(Memory);
|
var begin = SocketOutput.ProducingStart();
|
||||||
writer.Write(HttpVersion);
|
var count = 0;
|
||||||
writer.Write(' ');
|
var end = begin;
|
||||||
writer.Write(status);
|
if (_keepAlive)
|
||||||
writer.Write('\r');
|
|
||||||
writer.Write('\n');
|
|
||||||
|
|
||||||
var hasConnection = false;
|
|
||||||
var hasTransferEncoding = false;
|
|
||||||
var hasContentLength = false;
|
|
||||||
|
|
||||||
foreach (var header in _responseHeaders)
|
|
||||||
{
|
{
|
||||||
var isConnection = false;
|
foreach (var connectionValue in _responseHeaders.HeaderConnection)
|
||||||
if (!hasConnection &&
|
|
||||||
string.Equals(header.Key, "Connection", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
hasConnection = isConnection = true;
|
if (connectionValue.IndexOf("close", StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
}
|
|
||||||
else if (!hasTransferEncoding &&
|
|
||||||
string.Equals(header.Key, "Transfer-Encoding", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
hasTransferEncoding = true;
|
|
||||||
}
|
|
||||||
else if (!hasContentLength &&
|
|
||||||
string.Equals(header.Key, "Content-Length", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
hasContentLength = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var value in header.Value)
|
|
||||||
{
|
|
||||||
writer.Write(header.Key);
|
|
||||||
writer.Write(':');
|
|
||||||
writer.Write(' ');
|
|
||||||
writer.Write(value);
|
|
||||||
writer.Write('\r');
|
|
||||||
writer.Write('\n');
|
|
||||||
|
|
||||||
if (isConnection && value.IndexOf("close", StringComparison.OrdinalIgnoreCase) != -1)
|
|
||||||
{
|
{
|
||||||
_keepAlive = false;
|
_keepAlive = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_keepAlive && !hasTransferEncoding && !hasContentLength)
|
if (_keepAlive && !_responseHeaders.HasTransferEncoding && !_responseHeaders.HasContentLength)
|
||||||
{
|
{
|
||||||
if (appCompleted)
|
if (appCompleted)
|
||||||
{
|
{
|
||||||
|
|
@ -640,15 +650,15 @@ namespace Microsoft.AspNet.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.
|
||||||
writer.Write("Content-Length: 0\r\n");
|
_responseHeaders.SetRawContentLength("0", _bytesContentLengthZero);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (HttpVersion == "HTTP/1.1")
|
if (_httpVersion == HttpVersionType.Http1_1)
|
||||||
{
|
{
|
||||||
_autoChunk = true;
|
_autoChunk = true;
|
||||||
writer.Write("Transfer-Encoding: chunked\r\n");
|
_responseHeaders.SetRawTransferEncoding("chunked", _bytesTransferEncodingChunked);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -657,21 +667,30 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_keepAlive == false && hasConnection == false && HttpVersion == "HTTP/1.1")
|
if (_keepAlive == false && _responseHeaders.HasConnection == false && _httpVersion == HttpVersionType.Http1_1)
|
||||||
{
|
{
|
||||||
writer.Write("Connection: close\r\n\r\n");
|
_responseHeaders.SetRawConnection("close", _bytesConnectionClose);
|
||||||
}
|
}
|
||||||
else if (_keepAlive && hasConnection == false && HttpVersion == "HTTP/1.0")
|
else if (_keepAlive && _responseHeaders.HasConnection == false && _httpVersion == HttpVersionType.Http1_0)
|
||||||
{
|
{
|
||||||
writer.Write("Connection: keep-alive\r\n\r\n");
|
_responseHeaders.SetRawConnection("keep-alive", _bytesConnectionKeepAlive);
|
||||||
|
}
|
||||||
|
|
||||||
|
count += end.CopyFrom(_httpVersion == HttpVersionType.Http1_1 ? _bytesHttpVersion1_1 : _bytesHttpVersion1_0);
|
||||||
|
count += end.CopyFrom(statusBytes);
|
||||||
|
count += _responseHeaders.CopyTo(ref end);
|
||||||
|
count += end.CopyFrom(_bytesEndHeaders, 0, _bytesEndHeaders.Length);
|
||||||
|
|
||||||
|
SocketOutput.ProducingComplete(end, count);
|
||||||
|
|
||||||
|
if (immediate)
|
||||||
|
{
|
||||||
|
return SocketOutput.WriteAsync(default(ArraySegment<byte>), immediate: true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
writer.Write('\r');
|
return TaskUtilities.CompletedTask;
|
||||||
writer.Write('\n');
|
|
||||||
}
|
}
|
||||||
writer.Flush();
|
|
||||||
return new Tuple<ArraySegment<byte>, IDisposable>(writer.Buffer, writer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TakeStartLine(SocketInput input)
|
private bool TakeStartLine(SocketInput input)
|
||||||
|
|
@ -880,5 +899,12 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
_applicationException = ex;
|
_applicationException = ex;
|
||||||
Log.ApplicationError(ex);
|
Log.ApplicationError(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum HttpVersionType
|
||||||
|
{
|
||||||
|
Unknown = -1,
|
||||||
|
Http1_0 = 0,
|
||||||
|
Http1_1 = 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,14 +1,25 @@
|
||||||
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using Microsoft.Extensions.Primitives;
|
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Server.Kestrel.Http
|
namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
{
|
{
|
||||||
public partial class FrameResponseHeaders : FrameHeaders
|
public partial class FrameResponseHeaders : FrameHeaders
|
||||||
{
|
{
|
||||||
|
private static byte[] _CrLf = new[] { (byte)'\r', (byte)'\n' };
|
||||||
|
private static byte[] _colonSpace = new[] { (byte)':', (byte)' ' };
|
||||||
|
|
||||||
|
public bool HasConnection => HeaderConnection.Count != 0;
|
||||||
|
|
||||||
|
public bool HasTransferEncoding => HeaderTransferEncoding.Count != 0;
|
||||||
|
|
||||||
|
public bool HasContentLength => HeaderContentLength.Count != 0;
|
||||||
|
|
||||||
|
|
||||||
public Enumerator GetEnumerator()
|
public Enumerator GetEnumerator()
|
||||||
{
|
{
|
||||||
return new Enumerator(this);
|
return new Enumerator(this);
|
||||||
|
|
@ -19,6 +30,25 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
return GetEnumerator();
|
return GetEnumerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int CopyTo(ref MemoryPoolIterator2 output)
|
||||||
|
{
|
||||||
|
var count = CopyToFast(ref output);
|
||||||
|
if (MaybeUnknown != null)
|
||||||
|
{
|
||||||
|
foreach (var kv in MaybeUnknown)
|
||||||
|
{
|
||||||
|
foreach (var value in kv.Value)
|
||||||
|
{
|
||||||
|
count += output.CopyFrom(_CrLf, 0, 2);
|
||||||
|
count += output.CopyFromAscii(kv.Key);
|
||||||
|
count += output.CopyFrom(_colonSpace, 0, 2);
|
||||||
|
count += output.CopyFromAscii(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
public partial struct Enumerator : IEnumerator<KeyValuePair<string, StringValues>>
|
public partial struct Enumerator : IEnumerator<KeyValuePair<string, StringValues>>
|
||||||
{
|
{
|
||||||
private FrameResponseHeaders _collection;
|
private FrameResponseHeaders _collection;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Server.Kestrel.Http
|
namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
{
|
{
|
||||||
|
|
@ -14,5 +15,22 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
{
|
{
|
||||||
void Write(ArraySegment<byte> buffer, bool immediate = true);
|
void Write(ArraySegment<byte> buffer, bool immediate = true);
|
||||||
Task WriteAsync(ArraySegment<byte> buffer, bool immediate = true, CancellationToken cancellationToken = default(CancellationToken));
|
Task WriteAsync(ArraySegment<byte> buffer, bool immediate = true, CancellationToken cancellationToken = default(CancellationToken));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an iterator pointing to the tail of the response buffer. Response data can be appended
|
||||||
|
/// manually or by using <see cref="MemoryPoolIterator2.CopyFrom(ArraySegment{byte})"/>.
|
||||||
|
/// Be careful to ensure all appended blocks are backed by a <see cref="MemoryPoolSlab2"/>.
|
||||||
|
/// </summary>
|
||||||
|
MemoryPoolIterator2 ProducingStart();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Commits the response data appended to the iterator returned from <see cref="ProducingStart"/>.
|
||||||
|
/// All the data up to <paramref name="end"/> will be included in the response.
|
||||||
|
/// A write operation isn't guaranteed to be scheduled unless <see cref="Write(ArraySegment{byte}, bool)"/>
|
||||||
|
/// or <see cref="WriteAsync(ArraySegment{byte}, bool, CancellationToken)"/> is called afterwards.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="end">Points to the end of the committed data.</param>
|
||||||
|
/// <param name="count">The number of bytes added to the response.</param>
|
||||||
|
void ProducingComplete(MemoryPoolIterator2 end, int count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,125 +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;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Server.Kestrel.Http
|
|
||||||
{
|
|
||||||
public class MemoryPool : IMemoryPool
|
|
||||||
{
|
|
||||||
private static readonly byte[] EmptyArray = new byte[0];
|
|
||||||
|
|
||||||
private readonly Pool<byte> _pool1 = new Pool<byte>();
|
|
||||||
private readonly Pool<byte> _pool2 = new Pool<byte>();
|
|
||||||
private readonly Pool<char> _pool3 = new Pool<char>();
|
|
||||||
|
|
||||||
public byte[] Empty
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return EmptyArray;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] AllocByte(int minimumSize)
|
|
||||||
{
|
|
||||||
if (minimumSize == 0)
|
|
||||||
{
|
|
||||||
return EmptyArray;
|
|
||||||
}
|
|
||||||
if (minimumSize <= 1024)
|
|
||||||
{
|
|
||||||
return _pool1.Alloc(1024);
|
|
||||||
}
|
|
||||||
if (minimumSize <= 2048)
|
|
||||||
{
|
|
||||||
return _pool2.Alloc(2048);
|
|
||||||
}
|
|
||||||
return new byte[minimumSize];
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FreeByte(byte[] memory)
|
|
||||||
{
|
|
||||||
if (memory == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch (memory.Length)
|
|
||||||
{
|
|
||||||
case 1024:
|
|
||||||
_pool1.Free(memory, 256);
|
|
||||||
break;
|
|
||||||
case 2048:
|
|
||||||
_pool2.Free(memory, 64);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public char[] AllocChar(int minimumSize)
|
|
||||||
{
|
|
||||||
if (minimumSize == 0)
|
|
||||||
{
|
|
||||||
return new char[0];
|
|
||||||
}
|
|
||||||
if (minimumSize <= 128)
|
|
||||||
{
|
|
||||||
return _pool3.Alloc(128);
|
|
||||||
}
|
|
||||||
return new char[minimumSize];
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FreeChar(char[] memory)
|
|
||||||
{
|
|
||||||
if (memory == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch (memory.Length)
|
|
||||||
{
|
|
||||||
case 128:
|
|
||||||
_pool3.Free(memory, 256);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArraySegment<byte> AllocSegment(int minimumSize)
|
|
||||||
{
|
|
||||||
return new ArraySegment<byte>(AllocByte(minimumSize));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FreeSegment(ArraySegment<byte> segment)
|
|
||||||
{
|
|
||||||
FreeByte(segment.Array);
|
|
||||||
}
|
|
||||||
|
|
||||||
class Pool<T>
|
|
||||||
{
|
|
||||||
private readonly Stack<T[]> _stack = new Stack<T[]>();
|
|
||||||
private readonly object _sync = new object();
|
|
||||||
|
|
||||||
public T[] Alloc(int size)
|
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
if (_stack.Count != 0)
|
|
||||||
{
|
|
||||||
return _stack.Pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new T[size];
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Free(T[] value, int limit)
|
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
if (_stack.Count < limit)
|
|
||||||
{
|
|
||||||
_stack.Push(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,155 +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.IO;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Server.Kestrel.Http
|
|
||||||
{
|
|
||||||
public class MemoryPoolTextWriter : TextWriter
|
|
||||||
{
|
|
||||||
private readonly IMemoryPool _memory;
|
|
||||||
|
|
||||||
private char[] _textArray;
|
|
||||||
private int _textBegin;
|
|
||||||
private int _textEnd;
|
|
||||||
// ReSharper disable InconsistentNaming
|
|
||||||
private const int _textLength = 128;
|
|
||||||
// ReSharper restore InconsistentNaming
|
|
||||||
|
|
||||||
private byte[] _dataArray;
|
|
||||||
private int _dataEnd;
|
|
||||||
|
|
||||||
private readonly Encoder _encoder;
|
|
||||||
|
|
||||||
public MemoryPoolTextWriter(IMemoryPool memory)
|
|
||||||
{
|
|
||||||
_memory = memory;
|
|
||||||
_textArray = _memory.AllocChar(_textLength);
|
|
||||||
_dataArray = _memory.Empty;
|
|
||||||
_encoder = Encoding.UTF8.GetEncoder();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArraySegment<byte> Buffer
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return new ArraySegment<byte>(_dataArray, 0, _dataEnd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Encoding Encoding
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return Encoding.UTF8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
if (_textArray != null)
|
|
||||||
{
|
|
||||||
_memory.FreeChar(_textArray);
|
|
||||||
_textArray = null;
|
|
||||||
}
|
|
||||||
if (_dataArray != null)
|
|
||||||
{
|
|
||||||
_memory.FreeByte(_dataArray);
|
|
||||||
_dataArray = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
base.Dispose(disposing);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Encode(bool flush)
|
|
||||||
{
|
|
||||||
var bytesNeeded = _encoder.GetByteCount(
|
|
||||||
_textArray,
|
|
||||||
_textBegin,
|
|
||||||
_textEnd - _textBegin,
|
|
||||||
flush);
|
|
||||||
|
|
||||||
Grow(bytesNeeded);
|
|
||||||
|
|
||||||
var bytesUsed = _encoder.GetBytes(
|
|
||||||
_textArray,
|
|
||||||
_textBegin,
|
|
||||||
_textEnd - _textBegin,
|
|
||||||
_dataArray,
|
|
||||||
_dataEnd,
|
|
||||||
flush);
|
|
||||||
|
|
||||||
_textBegin = _textEnd = 0;
|
|
||||||
_dataEnd += bytesUsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Grow(int minimumAvailable)
|
|
||||||
{
|
|
||||||
if (_dataArray.Length - _dataEnd >= minimumAvailable)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var newLength = _dataArray.Length + Math.Max(_dataArray.Length, minimumAvailable);
|
|
||||||
var newArray = _memory.AllocByte(newLength);
|
|
||||||
Array.Copy(_dataArray, 0, newArray, 0, _dataEnd);
|
|
||||||
_memory.FreeByte(_dataArray);
|
|
||||||
_dataArray = newArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Write(char value)
|
|
||||||
{
|
|
||||||
if (_textLength == _textEnd)
|
|
||||||
{
|
|
||||||
Encode(false);
|
|
||||||
if (_textLength == _textEnd)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Unexplainable failure to encode text");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_textArray[_textEnd++] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Write(string value)
|
|
||||||
{
|
|
||||||
var sourceIndex = 0;
|
|
||||||
var sourceLength = value.Length;
|
|
||||||
while (sourceIndex < sourceLength)
|
|
||||||
{
|
|
||||||
if (_textLength == _textEnd)
|
|
||||||
{
|
|
||||||
Encode(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
var count = sourceLength - sourceIndex;
|
|
||||||
if (count > _textLength - _textEnd)
|
|
||||||
{
|
|
||||||
count = _textLength - _textEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
value.CopyTo(sourceIndex, _textArray, _textEnd, count);
|
|
||||||
sourceIndex += count;
|
|
||||||
_textEnd += count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Flush()
|
|
||||||
{
|
|
||||||
while (_textBegin != _textEnd)
|
|
||||||
{
|
|
||||||
Encode(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,11 +2,65 @@
|
||||||
// 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.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Server.Kestrel.Http
|
namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
{
|
{
|
||||||
public static class ReasonPhrases
|
public static class ReasonPhrases
|
||||||
{
|
{
|
||||||
|
private static readonly byte[] _bytesStatus100 = Encoding.ASCII.GetBytes("100 Continue");
|
||||||
|
private static readonly byte[] _bytesStatus101 = Encoding.ASCII.GetBytes("101 Switching Protocols");
|
||||||
|
private static readonly byte[] _bytesStatus102 = Encoding.ASCII.GetBytes("102 Processing");
|
||||||
|
private static readonly byte[] _bytesStatus200 = Encoding.ASCII.GetBytes("200 OK");
|
||||||
|
private static readonly byte[] _bytesStatus201 = Encoding.ASCII.GetBytes("201 Created");
|
||||||
|
private static readonly byte[] _bytesStatus202 = Encoding.ASCII.GetBytes("202 Accepted");
|
||||||
|
private static readonly byte[] _bytesStatus203 = Encoding.ASCII.GetBytes("203 Non-Authoritative Information");
|
||||||
|
private static readonly byte[] _bytesStatus204 = Encoding.ASCII.GetBytes("204 No Content");
|
||||||
|
private static readonly byte[] _bytesStatus205 = Encoding.ASCII.GetBytes("205 Reset Content");
|
||||||
|
private static readonly byte[] _bytesStatus206 = Encoding.ASCII.GetBytes("206 Partial Content");
|
||||||
|
private static readonly byte[] _bytesStatus207 = Encoding.ASCII.GetBytes("207 Multi-Status");
|
||||||
|
private static readonly byte[] _bytesStatus226 = Encoding.ASCII.GetBytes("226 IM Used");
|
||||||
|
private static readonly byte[] _bytesStatus300 = Encoding.ASCII.GetBytes("300 Multiple Choices");
|
||||||
|
private static readonly byte[] _bytesStatus301 = Encoding.ASCII.GetBytes("301 Moved Permanently");
|
||||||
|
private static readonly byte[] _bytesStatus302 = Encoding.ASCII.GetBytes("302 Found");
|
||||||
|
private static readonly byte[] _bytesStatus303 = Encoding.ASCII.GetBytes("303 See Other");
|
||||||
|
private static readonly byte[] _bytesStatus304 = Encoding.ASCII.GetBytes("304 Not Modified");
|
||||||
|
private static readonly byte[] _bytesStatus305 = Encoding.ASCII.GetBytes("305 Use Proxy");
|
||||||
|
private static readonly byte[] _bytesStatus306 = Encoding.ASCII.GetBytes("306 Reserved");
|
||||||
|
private static readonly byte[] _bytesStatus307 = Encoding.ASCII.GetBytes("307 Temporary Redirect");
|
||||||
|
private static readonly byte[] _bytesStatus400 = Encoding.ASCII.GetBytes("400 Bad Request");
|
||||||
|
private static readonly byte[] _bytesStatus401 = Encoding.ASCII.GetBytes("401 Unauthorized");
|
||||||
|
private static readonly byte[] _bytesStatus402 = Encoding.ASCII.GetBytes("402 Payment Required");
|
||||||
|
private static readonly byte[] _bytesStatus403 = Encoding.ASCII.GetBytes("403 Forbidden");
|
||||||
|
private static readonly byte[] _bytesStatus404 = Encoding.ASCII.GetBytes("404 Not Found");
|
||||||
|
private static readonly byte[] _bytesStatus405 = Encoding.ASCII.GetBytes("405 Method Not Allowed");
|
||||||
|
private static readonly byte[] _bytesStatus406 = Encoding.ASCII.GetBytes("406 Not Acceptable");
|
||||||
|
private static readonly byte[] _bytesStatus407 = Encoding.ASCII.GetBytes("407 Proxy Authentication Required");
|
||||||
|
private static readonly byte[] _bytesStatus408 = Encoding.ASCII.GetBytes("408 Request Timeout");
|
||||||
|
private static readonly byte[] _bytesStatus409 = Encoding.ASCII.GetBytes("409 Conflict");
|
||||||
|
private static readonly byte[] _bytesStatus410 = Encoding.ASCII.GetBytes("410 Gone");
|
||||||
|
private static readonly byte[] _bytesStatus411 = Encoding.ASCII.GetBytes("411 Length Required");
|
||||||
|
private static readonly byte[] _bytesStatus412 = Encoding.ASCII.GetBytes("412 Precondition Failed");
|
||||||
|
private static readonly byte[] _bytesStatus413 = Encoding.ASCII.GetBytes("413 Request Entity Too Large");
|
||||||
|
private static readonly byte[] _bytesStatus414 = Encoding.ASCII.GetBytes("414 Request-URI Too Long");
|
||||||
|
private static readonly byte[] _bytesStatus415 = Encoding.ASCII.GetBytes("415 Unsupported Media Type");
|
||||||
|
private static readonly byte[] _bytesStatus416 = Encoding.ASCII.GetBytes("416 Requested Range Not Satisfiable");
|
||||||
|
private static readonly byte[] _bytesStatus417 = Encoding.ASCII.GetBytes("417 Expectation Failed");
|
||||||
|
private static readonly byte[] _bytesStatus418 = Encoding.ASCII.GetBytes("418 I'm a Teapot");
|
||||||
|
private static readonly byte[] _bytesStatus422 = Encoding.ASCII.GetBytes("422 Unprocessable Entity");
|
||||||
|
private static readonly byte[] _bytesStatus423 = Encoding.ASCII.GetBytes("423 Locked");
|
||||||
|
private static readonly byte[] _bytesStatus424 = Encoding.ASCII.GetBytes("424 Failed Dependency");
|
||||||
|
private static readonly byte[] _bytesStatus426 = Encoding.ASCII.GetBytes("426 Upgrade Required");
|
||||||
|
private static readonly byte[] _bytesStatus500 = Encoding.ASCII.GetBytes("500 Internal Server Error");
|
||||||
|
private static readonly byte[] _bytesStatus501 = Encoding.ASCII.GetBytes("501 Not Implemented");
|
||||||
|
private static readonly byte[] _bytesStatus502 = Encoding.ASCII.GetBytes("502 Bad Gateway");
|
||||||
|
private static readonly byte[] _bytesStatus503 = Encoding.ASCII.GetBytes("503 Service Unavailable");
|
||||||
|
private static readonly byte[] _bytesStatus504 = Encoding.ASCII.GetBytes("504 Gateway Timeout");
|
||||||
|
private static readonly byte[] _bytesStatus505 = Encoding.ASCII.GetBytes("505 HTTP Version Not Supported");
|
||||||
|
private static readonly byte[] _bytesStatus506 = Encoding.ASCII.GetBytes("506 Variant Also Negotiates");
|
||||||
|
private static readonly byte[] _bytesStatus507 = Encoding.ASCII.GetBytes("507 Insufficient Storage");
|
||||||
|
private static readonly byte[] _bytesStatus510 = Encoding.ASCII.GetBytes("510 Not Extended");
|
||||||
|
|
||||||
public static string ToStatus(int statusCode, string reasonPhrase = null)
|
public static string ToStatus(int statusCode, string reasonPhrase = null)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(reasonPhrase))
|
if (string.IsNullOrEmpty(reasonPhrase))
|
||||||
|
|
@ -16,6 +70,123 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
return statusCode.ToString(CultureInfo.InvariantCulture) + " " + reasonPhrase;
|
return statusCode.ToString(CultureInfo.InvariantCulture) + " " + reasonPhrase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static byte[] ToStatusBytes(int statusCode, string reasonPhrase = null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(reasonPhrase))
|
||||||
|
{
|
||||||
|
switch (statusCode)
|
||||||
|
{
|
||||||
|
case 100:
|
||||||
|
return _bytesStatus100;
|
||||||
|
case 101:
|
||||||
|
return _bytesStatus101;
|
||||||
|
case 102:
|
||||||
|
return _bytesStatus102;
|
||||||
|
case 200:
|
||||||
|
return _bytesStatus200;
|
||||||
|
case 201:
|
||||||
|
return _bytesStatus201;
|
||||||
|
case 202:
|
||||||
|
return _bytesStatus202;
|
||||||
|
case 203:
|
||||||
|
return _bytesStatus203;
|
||||||
|
case 204:
|
||||||
|
return _bytesStatus204;
|
||||||
|
case 205:
|
||||||
|
return _bytesStatus205;
|
||||||
|
case 206:
|
||||||
|
return _bytesStatus206;
|
||||||
|
case 207:
|
||||||
|
return _bytesStatus207;
|
||||||
|
case 226:
|
||||||
|
return _bytesStatus226;
|
||||||
|
case 300:
|
||||||
|
return _bytesStatus300;
|
||||||
|
case 301:
|
||||||
|
return _bytesStatus301;
|
||||||
|
case 302:
|
||||||
|
return _bytesStatus302;
|
||||||
|
case 303:
|
||||||
|
return _bytesStatus303;
|
||||||
|
case 304:
|
||||||
|
return _bytesStatus304;
|
||||||
|
case 305:
|
||||||
|
return _bytesStatus305;
|
||||||
|
case 306:
|
||||||
|
return _bytesStatus306;
|
||||||
|
case 307:
|
||||||
|
return _bytesStatus307;
|
||||||
|
case 400:
|
||||||
|
return _bytesStatus400;
|
||||||
|
case 401:
|
||||||
|
return _bytesStatus401;
|
||||||
|
case 402:
|
||||||
|
return _bytesStatus402;
|
||||||
|
case 403:
|
||||||
|
return _bytesStatus403;
|
||||||
|
case 404:
|
||||||
|
return _bytesStatus404;
|
||||||
|
case 405:
|
||||||
|
return _bytesStatus405;
|
||||||
|
case 406:
|
||||||
|
return _bytesStatus406;
|
||||||
|
case 407:
|
||||||
|
return _bytesStatus407;
|
||||||
|
case 408:
|
||||||
|
return _bytesStatus408;
|
||||||
|
case 409:
|
||||||
|
return _bytesStatus409;
|
||||||
|
case 410:
|
||||||
|
return _bytesStatus410;
|
||||||
|
case 411:
|
||||||
|
return _bytesStatus411;
|
||||||
|
case 412:
|
||||||
|
return _bytesStatus412;
|
||||||
|
case 413:
|
||||||
|
return _bytesStatus413;
|
||||||
|
case 414:
|
||||||
|
return _bytesStatus414;
|
||||||
|
case 415:
|
||||||
|
return _bytesStatus415;
|
||||||
|
case 416:
|
||||||
|
return _bytesStatus416;
|
||||||
|
case 417:
|
||||||
|
return _bytesStatus417;
|
||||||
|
case 418:
|
||||||
|
return _bytesStatus418;
|
||||||
|
case 422:
|
||||||
|
return _bytesStatus422;
|
||||||
|
case 423:
|
||||||
|
return _bytesStatus423;
|
||||||
|
case 424:
|
||||||
|
return _bytesStatus424;
|
||||||
|
case 426:
|
||||||
|
return _bytesStatus426;
|
||||||
|
case 500:
|
||||||
|
return _bytesStatus500;
|
||||||
|
case 501:
|
||||||
|
return _bytesStatus501;
|
||||||
|
case 502:
|
||||||
|
return _bytesStatus502;
|
||||||
|
case 503:
|
||||||
|
return _bytesStatus503;
|
||||||
|
case 504:
|
||||||
|
return _bytesStatus504;
|
||||||
|
case 505:
|
||||||
|
return _bytesStatus505;
|
||||||
|
case 506:
|
||||||
|
return _bytesStatus506;
|
||||||
|
case 507:
|
||||||
|
return _bytesStatus507;
|
||||||
|
case 510:
|
||||||
|
return _bytesStatus510;
|
||||||
|
default:
|
||||||
|
return Encoding.ASCII.GetBytes(statusCode.ToString(CultureInfo.InvariantCulture) + " Unknown");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Encoding.ASCII.GetBytes(statusCode.ToString(CultureInfo.InvariantCulture) + " " + reasonPhrase);
|
||||||
|
}
|
||||||
|
|
||||||
public static string ToReasonPhrase(int statusCode)
|
public static string ToReasonPhrase(int statusCode)
|
||||||
{
|
{
|
||||||
switch (statusCode)
|
switch (statusCode)
|
||||||
|
|
@ -242,4 +413,4 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -64,7 +64,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
{
|
{
|
||||||
_pinned = _tail;
|
_pinned = _tail;
|
||||||
var data = new ArraySegment<byte>(_pinned.Data.Array, _pinned.End, _pinned.Data.Offset + _pinned.Data.Count - _pinned.End);
|
var data = new ArraySegment<byte>(_pinned.Data.Array, _pinned.End, _pinned.Data.Offset + _pinned.Data.Count - _pinned.End);
|
||||||
var dataPtr = _pinned.Pin();
|
var dataPtr = _pinned.Pin() + _pinned.End;
|
||||||
return new IncomingBuffer
|
return new IncomingBuffer
|
||||||
{
|
{
|
||||||
Data = data,
|
Data = data,
|
||||||
|
|
@ -77,7 +77,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
return new IncomingBuffer
|
return new IncomingBuffer
|
||||||
{
|
{
|
||||||
Data = _pinned.Data,
|
Data = _pinned.Data,
|
||||||
DataPtr = _pinned.Pin()
|
DataPtr = _pinned.Pin() + _pinned.End
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,9 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
{
|
{
|
||||||
private const int _maxPendingWrites = 3;
|
private const int _maxPendingWrites = 3;
|
||||||
private const int _maxBytesPreCompleted = 65536;
|
private const int _maxBytesPreCompleted = 65536;
|
||||||
|
private const int _initialTaskQueues = 64;
|
||||||
|
|
||||||
|
private static WaitCallback _returnBlocks = (state) => ReturnBlocks((MemoryPoolBlock2)state);
|
||||||
|
|
||||||
private readonly KestrelThread _thread;
|
private readonly KestrelThread _thread;
|
||||||
private readonly UvStreamHandle _socket;
|
private readonly UvStreamHandle _socket;
|
||||||
|
|
@ -23,8 +26,18 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
private readonly long _connectionId;
|
private readonly long _connectionId;
|
||||||
private readonly IKestrelTrace _log;
|
private readonly IKestrelTrace _log;
|
||||||
|
|
||||||
|
// This locks all access to _tail, _isProducing and _returnFromOnProducingComplete.
|
||||||
|
// _head does not require a lock, since it is only used in the ctor and uv thread.
|
||||||
|
private readonly object _returnLock = new object();
|
||||||
|
|
||||||
|
private MemoryPoolBlock2 _head;
|
||||||
|
private MemoryPoolBlock2 _tail;
|
||||||
|
|
||||||
|
private bool _isProducing;
|
||||||
|
private MemoryPoolBlock2 _returnFromOnProducingComplete;
|
||||||
|
|
||||||
// This locks access to to all of the below fields
|
// This locks access to to all of the below fields
|
||||||
private readonly object _lockObj = new object();
|
private readonly object _contextLock = new object();
|
||||||
|
|
||||||
// The number of write operations that have been scheduled so far
|
// The number of write operations that have been scheduled so far
|
||||||
// but have not completed.
|
// but have not completed.
|
||||||
|
|
@ -34,10 +47,12 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
private Exception _lastWriteError;
|
private Exception _lastWriteError;
|
||||||
private WriteContext _nextWriteContext;
|
private WriteContext _nextWriteContext;
|
||||||
private readonly Queue<TaskCompletionSource<object>> _tasksPending;
|
private readonly Queue<TaskCompletionSource<object>> _tasksPending;
|
||||||
|
private readonly Queue<TaskCompletionSource<object>> _tasksCompleted;
|
||||||
|
|
||||||
public SocketOutput(
|
public SocketOutput(
|
||||||
KestrelThread thread,
|
KestrelThread thread,
|
||||||
UvStreamHandle socket,
|
UvStreamHandle socket,
|
||||||
|
MemoryPool2 memory,
|
||||||
Connection connection,
|
Connection connection,
|
||||||
long connectionId,
|
long connectionId,
|
||||||
IKestrelTrace log)
|
IKestrelTrace log)
|
||||||
|
|
@ -47,7 +62,11 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
_connection = connection;
|
_connection = connection;
|
||||||
_connectionId = connectionId;
|
_connectionId = connectionId;
|
||||||
_log = log;
|
_log = log;
|
||||||
_tasksPending = new Queue<TaskCompletionSource<object>>();
|
_tasksPending = new Queue<TaskCompletionSource<object>>(_initialTaskQueues);
|
||||||
|
_tasksCompleted = new Queue<TaskCompletionSource<object>>(_initialTaskQueues);
|
||||||
|
|
||||||
|
_head = memory.Lease();
|
||||||
|
_tail = _head;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task WriteAsync(
|
public Task WriteAsync(
|
||||||
|
|
@ -56,28 +75,24 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
bool socketShutdownSend = false,
|
bool socketShutdownSend = false,
|
||||||
bool socketDisconnect = false)
|
bool socketDisconnect = false)
|
||||||
{
|
{
|
||||||
//TODO: need buffering that works
|
if (buffer.Count > 0)
|
||||||
if (buffer.Array != null)
|
|
||||||
{
|
{
|
||||||
var copy = new byte[buffer.Count];
|
var tail = ProducingStart();
|
||||||
Array.Copy(buffer.Array, buffer.Offset, copy, 0, buffer.Count);
|
tail.CopyFrom(buffer);
|
||||||
buffer = new ArraySegment<byte>(copy);
|
// We do our own accounting below
|
||||||
_log.ConnectionWrite(_connectionId, buffer.Count);
|
ProducingComplete(tail, count: 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TaskCompletionSource<object> tcs = null;
|
TaskCompletionSource<object> tcs = null;
|
||||||
|
|
||||||
lock (_lockObj)
|
var scheduleWrite = false;
|
||||||
|
|
||||||
|
lock (_contextLock)
|
||||||
{
|
{
|
||||||
if (_nextWriteContext == null)
|
if (_nextWriteContext == null)
|
||||||
{
|
{
|
||||||
_nextWriteContext = new WriteContext(this);
|
_nextWriteContext = new WriteContext(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buffer.Array != null)
|
|
||||||
{
|
|
||||||
_nextWriteContext.Buffers.Enqueue(buffer);
|
|
||||||
}
|
|
||||||
if (socketShutdownSend)
|
if (socketShutdownSend)
|
||||||
{
|
{
|
||||||
_nextWriteContext.SocketShutdownSend = true;
|
_nextWriteContext.SocketShutdownSend = true;
|
||||||
|
|
@ -110,11 +125,16 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
|
|
||||||
if (_writesPending < _maxPendingWrites && immediate)
|
if (_writesPending < _maxPendingWrites && immediate)
|
||||||
{
|
{
|
||||||
ScheduleWrite();
|
scheduleWrite = true;
|
||||||
_writesPending++;
|
_writesPending++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (scheduleWrite)
|
||||||
|
{
|
||||||
|
ScheduleWrite();
|
||||||
|
}
|
||||||
|
|
||||||
// Return TaskCompletionSource's Task if set, otherwise completed Task
|
// Return TaskCompletionSource's Task if set, otherwise completed Task
|
||||||
return tcs?.Task ?? TaskUtilities.CompletedTask;
|
return tcs?.Task ?? TaskUtilities.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
@ -138,6 +158,75 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MemoryPoolIterator2 ProducingStart()
|
||||||
|
{
|
||||||
|
lock (_returnLock)
|
||||||
|
{
|
||||||
|
Debug.Assert(!_isProducing);
|
||||||
|
_isProducing = true;
|
||||||
|
|
||||||
|
if (_tail == null)
|
||||||
|
{
|
||||||
|
throw new IOException("The socket has been closed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MemoryPoolIterator2(_tail, _tail.End);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ProducingComplete(MemoryPoolIterator2 end, int count)
|
||||||
|
{
|
||||||
|
var decreasePreCompleted = false;
|
||||||
|
MemoryPoolBlock2 blockToReturn = null;
|
||||||
|
|
||||||
|
lock (_returnLock)
|
||||||
|
{
|
||||||
|
Debug.Assert(_isProducing);
|
||||||
|
_isProducing = false;
|
||||||
|
|
||||||
|
if (_returnFromOnProducingComplete == null)
|
||||||
|
{
|
||||||
|
_tail = end.Block;
|
||||||
|
_tail.End = end.Index;
|
||||||
|
|
||||||
|
if (count != 0)
|
||||||
|
{
|
||||||
|
decreasePreCompleted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
blockToReturn = _returnFromOnProducingComplete;
|
||||||
|
_returnFromOnProducingComplete = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decreasePreCompleted)
|
||||||
|
{
|
||||||
|
lock (_contextLock)
|
||||||
|
{
|
||||||
|
_numBytesPreCompleted += count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (blockToReturn != null)
|
||||||
|
{
|
||||||
|
ThreadPool.QueueUserWorkItem(_returnBlocks, blockToReturn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReturnBlocks(MemoryPoolBlock2 block)
|
||||||
|
{
|
||||||
|
while(block != null)
|
||||||
|
{
|
||||||
|
var returningBlock = block;
|
||||||
|
block = returningBlock.Next;
|
||||||
|
|
||||||
|
returningBlock.Pool?.Return(returningBlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void ScheduleWrite()
|
private void ScheduleWrite()
|
||||||
{
|
{
|
||||||
_thread.Post(_this => _this.WriteAllPending(), this);
|
_thread.Post(_this => _this.WriteAllPending(), this);
|
||||||
|
|
@ -148,7 +237,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
{
|
{
|
||||||
WriteContext writingContext;
|
WriteContext writingContext;
|
||||||
|
|
||||||
lock (_lockObj)
|
lock (_contextLock)
|
||||||
{
|
{
|
||||||
if (_nextWriteContext != null)
|
if (_nextWriteContext != null)
|
||||||
{
|
{
|
||||||
|
|
@ -168,7 +257,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
lock (_lockObj)
|
lock (_contextLock)
|
||||||
{
|
{
|
||||||
// Lock instead of using Interlocked.Decrement so _writesSending
|
// Lock instead of using Interlocked.Decrement so _writesSending
|
||||||
// doesn't change in the middle of executing other synchronized code.
|
// doesn't change in the middle of executing other synchronized code.
|
||||||
|
|
@ -180,7 +269,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is called on the libuv event loop
|
// This is called on the libuv event loop
|
||||||
private void OnWriteCompleted(Queue<ArraySegment<byte>> writtenBuffers, int status, Exception error)
|
private void OnWriteCompleted(int bytesWritten, int status, Exception error)
|
||||||
{
|
{
|
||||||
_log.ConnectionWriteCallback(_connectionId, status);
|
_log.ConnectionWriteCallback(_connectionId, status);
|
||||||
|
|
||||||
|
|
@ -192,24 +281,23 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
_connection.Abort();
|
_connection.Abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (_lockObj)
|
bool scheduleWrite = false;
|
||||||
|
|
||||||
|
lock (_contextLock)
|
||||||
{
|
{
|
||||||
if (_nextWriteContext != null)
|
if (_nextWriteContext != null)
|
||||||
{
|
{
|
||||||
ScheduleWrite();
|
scheduleWrite = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_writesPending--;
|
_writesPending--;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var writeBuffer in writtenBuffers)
|
// _numBytesPreCompleted can temporarily go negative in the event there are
|
||||||
{
|
// completed writes that we haven't triggered callbacks for yet.
|
||||||
// _numBytesPreCompleted can temporarily go negative in the event there are
|
_numBytesPreCompleted -= bytesWritten;
|
||||||
// completed writes that we haven't triggered callbacks for yet.
|
|
||||||
_numBytesPreCompleted -= writeBuffer.Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
// bytesLeftToBuffer can be greater than _maxBytesPreCompleted
|
// bytesLeftToBuffer can be greater than _maxBytesPreCompleted
|
||||||
// This allows large writes to complete once they've actually finished.
|
// This allows large writes to complete once they've actually finished.
|
||||||
var bytesLeftToBuffer = _maxBytesPreCompleted - _numBytesPreCompleted;
|
var bytesLeftToBuffer = _maxBytesPreCompleted - _numBytesPreCompleted;
|
||||||
|
|
@ -222,23 +310,63 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
_numBytesPreCompleted += bytesToWrite;
|
_numBytesPreCompleted += bytesToWrite;
|
||||||
bytesLeftToBuffer -= bytesToWrite;
|
bytesLeftToBuffer -= bytesToWrite;
|
||||||
|
|
||||||
if (_lastWriteError == null)
|
_tasksCompleted.Enqueue(tcs);
|
||||||
{
|
}
|
||||||
ThreadPool.QueueUserWorkItem(
|
}
|
||||||
(o) => ((TaskCompletionSource<object>)o).SetResult(null),
|
|
||||||
tcs);
|
while (_tasksCompleted.Count > 0)
|
||||||
}
|
{
|
||||||
else
|
var tcs = _tasksCompleted.Dequeue();
|
||||||
{
|
if (_lastWriteError == null)
|
||||||
// error is closure captured
|
{
|
||||||
ThreadPool.QueueUserWorkItem(
|
ThreadPool.QueueUserWorkItem(
|
||||||
(o) => ((TaskCompletionSource<object>)o).SetException(_lastWriteError),
|
(o) => ((TaskCompletionSource<object>)o).SetResult(null),
|
||||||
tcs);
|
tcs);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// error is closure captured
|
||||||
|
ThreadPool.QueueUserWorkItem(
|
||||||
|
(o) => ((TaskCompletionSource<object>)o).SetException(_lastWriteError),
|
||||||
|
tcs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scheduleWrite)
|
||||||
|
{
|
||||||
|
// ScheduleWrite();
|
||||||
|
// on right thread, fairness issues?
|
||||||
|
WriteAllPending();
|
||||||
|
}
|
||||||
|
|
||||||
|
_tasksCompleted.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is called on the libuv event loop
|
||||||
|
private void ReturnAllBlocks()
|
||||||
|
{
|
||||||
|
lock (_returnLock)
|
||||||
|
{
|
||||||
|
var block = _head;
|
||||||
|
while (block != _tail)
|
||||||
|
{
|
||||||
|
var returnBlock = block;
|
||||||
|
block = block.Next;
|
||||||
|
|
||||||
|
returnBlock.Pool?.Return(returnBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that the while loop has completed the following invariants should hold true:
|
if (_isProducing)
|
||||||
Debug.Assert(_numBytesPreCompleted >= 0);
|
{
|
||||||
|
_returnFromOnProducingComplete = _tail;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_tail.Pool?.Return(_tail);
|
||||||
|
}
|
||||||
|
|
||||||
|
_head = null;
|
||||||
|
_tail = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -263,9 +391,15 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
|
|
||||||
private class WriteContext
|
private class WriteContext
|
||||||
{
|
{
|
||||||
|
private static WaitCallback _returnWrittenBlocks = (state) => ReturnWrittenBlocks((MemoryPoolBlock2)state);
|
||||||
|
|
||||||
|
private MemoryPoolIterator2 _lockedStart;
|
||||||
|
private MemoryPoolIterator2 _lockedEnd;
|
||||||
|
private int _bufferCount;
|
||||||
|
private int _byteCount;
|
||||||
|
|
||||||
public SocketOutput Self;
|
public SocketOutput Self;
|
||||||
|
|
||||||
public Queue<ArraySegment<byte>> Buffers;
|
|
||||||
public bool SocketShutdownSend;
|
public bool SocketShutdownSend;
|
||||||
public bool SocketDisconnect;
|
public bool SocketDisconnect;
|
||||||
|
|
||||||
|
|
@ -277,7 +411,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
public WriteContext(SocketOutput self)
|
public WriteContext(SocketOutput self)
|
||||||
{
|
{
|
||||||
Self = self;
|
Self = self;
|
||||||
Buffers = new Queue<ArraySegment<byte>>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -285,30 +418,29 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void DoWriteIfNeeded()
|
public void DoWriteIfNeeded()
|
||||||
{
|
{
|
||||||
if (Buffers.Count == 0 || Self._socket.IsClosed)
|
LockWrite();
|
||||||
|
|
||||||
|
if (_byteCount == 0 || Self._socket.IsClosed)
|
||||||
{
|
{
|
||||||
DoShutdownIfNeeded();
|
DoShutdownIfNeeded();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var buffers = new ArraySegment<byte>[Buffers.Count];
|
|
||||||
|
|
||||||
var i = 0;
|
|
||||||
foreach (var buffer in Buffers)
|
|
||||||
{
|
|
||||||
buffers[i++] = buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
var writeReq = new UvWriteReq(Self._log);
|
var writeReq = new UvWriteReq(Self._log);
|
||||||
writeReq.Init(Self._thread.Loop);
|
writeReq.Init(Self._thread.Loop);
|
||||||
writeReq.Write(Self._socket, new ArraySegment<ArraySegment<byte>>(buffers), (_writeReq, status, error, state) =>
|
|
||||||
|
writeReq.Write(Self._socket, _lockedStart, _lockedEnd, _bufferCount, (_writeReq, status, error, state) =>
|
||||||
{
|
{
|
||||||
_writeReq.Dispose();
|
_writeReq.Dispose();
|
||||||
var _this = (WriteContext)state;
|
var _this = (WriteContext)state;
|
||||||
|
_this.ScheduleReturnFullyWrittenBlocks();
|
||||||
_this.WriteStatus = status;
|
_this.WriteStatus = status;
|
||||||
_this.WriteError = error;
|
_this.WriteError = error;
|
||||||
_this.DoShutdownIfNeeded();
|
_this.DoShutdownIfNeeded();
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
|
Self._head = _lockedEnd.Block;
|
||||||
|
Self._head.Start = _lockedEnd.Index;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -348,13 +480,81 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
}
|
}
|
||||||
|
|
||||||
Self._socket.Dispose();
|
Self._socket.Dispose();
|
||||||
|
Self.ReturnAllBlocks();
|
||||||
Self._log.ConnectionStop(Self._connectionId);
|
Self._log.ConnectionStop(Self._connectionId);
|
||||||
Complete();
|
Complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Complete()
|
public void Complete()
|
||||||
{
|
{
|
||||||
Self.OnWriteCompleted(Buffers, WriteStatus, WriteError);
|
Self.OnWriteCompleted(_byteCount, WriteStatus, WriteError);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ScheduleReturnFullyWrittenBlocks()
|
||||||
|
{
|
||||||
|
var block = _lockedStart.Block;
|
||||||
|
var end = _lockedEnd.Block;
|
||||||
|
if (block == end)
|
||||||
|
{
|
||||||
|
end.Unpin();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (block.Next != end)
|
||||||
|
{
|
||||||
|
block = block.Next;
|
||||||
|
block.Unpin();
|
||||||
|
}
|
||||||
|
block.Next = null;
|
||||||
|
|
||||||
|
ThreadPool.QueueUserWorkItem(_returnWrittenBlocks, _lockedStart.Block);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReturnWrittenBlocks(MemoryPoolBlock2 block)
|
||||||
|
{
|
||||||
|
while (block != null)
|
||||||
|
{
|
||||||
|
var returnBlock = block;
|
||||||
|
block = block.Next;
|
||||||
|
|
||||||
|
returnBlock.Unpin();
|
||||||
|
returnBlock.Pool?.Return(returnBlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LockWrite()
|
||||||
|
{
|
||||||
|
var head = Self._head;
|
||||||
|
var tail = Self._tail;
|
||||||
|
|
||||||
|
if (head == null || tail == null)
|
||||||
|
{
|
||||||
|
// ReturnAllBlocks has already bee called. Nothing to do here.
|
||||||
|
// Write will no-op since _byteCount will remain 0.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lockedStart = new MemoryPoolIterator2(head, head.Start);
|
||||||
|
_lockedEnd = new MemoryPoolIterator2(tail, tail.End);
|
||||||
|
|
||||||
|
if (_lockedStart.Block == _lockedEnd.Block)
|
||||||
|
{
|
||||||
|
_byteCount = _lockedEnd.Index - _lockedStart.Index;
|
||||||
|
_bufferCount = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_byteCount = _lockedStart.Block.Data.Offset + _lockedStart.Block.Data.Count - _lockedStart.Index;
|
||||||
|
_bufferCount = 1;
|
||||||
|
|
||||||
|
for (var block = _lockedStart.Block.Next; block != _lockedEnd.Block; block = block.Next)
|
||||||
|
{
|
||||||
|
_byteCount += block.Data.Count;
|
||||||
|
_bufferCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
_byteCount += _lockedEnd.Index - _lockedEnd.Block.Data.Offset;
|
||||||
|
_bufferCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,12 @@ namespace Microsoft.AspNet.Server.Kestrel.Infrastructure
|
||||||
/// 4096 - 64 gives you a blockLength of 4032 usable bytes per block.
|
/// 4096 - 64 gives you a blockLength of 4032 usable bytes per block.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const int _blockLength = _blockStride - _blockUnused;
|
private const int _blockLength = _blockStride - _blockUnused;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Max allocation block size for pooled blocks,
|
||||||
|
/// larger values can be leased but they will be disposed after use rather than returned to the pool.
|
||||||
|
/// </summary>
|
||||||
|
public const int MaxPooledBlockLength = _blockLength;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 4096 * 32 gives you a slabLength of 128k contiguous bytes allocated per slab
|
/// 4096 * 32 gives you a slabLength of 128k contiguous bytes allocated per slab
|
||||||
|
|
@ -59,7 +65,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Infrastructure
|
||||||
/// <param name="minimumSize">The block returned must be at least this size. It may be larger than this minimum size, and if so,
|
/// <param name="minimumSize">The block returned must be at least this size. It may be larger than this minimum size, and if so,
|
||||||
/// the caller may write to the block's entire size rather than being limited to the minumumSize requested.</param>
|
/// the caller may write to the block's entire size rather than being limited to the minumumSize requested.</param>
|
||||||
/// <returns>The block that is reserved for the called. It must be passed to Return when it is no longer being used.</returns>
|
/// <returns>The block that is reserved for the called. It must be passed to Return when it is no longer being used.</returns>
|
||||||
public MemoryPoolBlock2 Lease(int minimumSize)
|
public MemoryPoolBlock2 Lease(int minimumSize = MaxPooledBlockLength)
|
||||||
{
|
{
|
||||||
if (minimumSize > _blockLength)
|
if (minimumSize > _blockLength)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -98,8 +98,9 @@ namespace Microsoft.AspNet.Server.Kestrel.Infrastructure
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called to ensure that a block is pinned, and return the pointer to native memory just after
|
/// Called to ensure that a block is pinned, and return the pointer to the native address
|
||||||
/// the range of "active" bytes. This is where arriving data is read into.
|
/// of the first byte of this block's Data memory. Arriving data is read into Pin() + End.
|
||||||
|
/// Outgoing data is read from Pin() + Start.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public IntPtr Pin()
|
public IntPtr Pin()
|
||||||
|
|
@ -109,13 +110,13 @@ namespace Microsoft.AspNet.Server.Kestrel.Infrastructure
|
||||||
if (_dataArrayPtr != IntPtr.Zero)
|
if (_dataArrayPtr != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
// this is a slab managed block - use the native address of the slab which is always locked
|
// this is a slab managed block - use the native address of the slab which is always locked
|
||||||
return _dataArrayPtr + End;
|
return _dataArrayPtr;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// this is one-time-use memory - lock the managed memory until Unpin is called
|
// this is one-time-use memory - lock the managed memory until Unpin is called
|
||||||
_pinHandle = GCHandle.Alloc(Data.Array, GCHandleType.Pinned);
|
_pinHandle = GCHandle.Alloc(Data.Array, GCHandleType.Pinned);
|
||||||
return _pinHandle.AddrOfPinnedObject() + End;
|
return _pinHandle.AddrOfPinnedObject();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +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.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
|
|
@ -530,5 +531,126 @@ namespace Microsoft.AspNet.Server.Kestrel.Infrastructure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int CopyFrom(byte[] data)
|
||||||
|
{
|
||||||
|
return CopyFrom(new ArraySegment<byte>(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CopyFrom(byte[] data, int offset, int count)
|
||||||
|
{
|
||||||
|
return CopyFrom(new ArraySegment<byte>(data, offset, count));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CopyFrom(ArraySegment<byte> buffer)
|
||||||
|
{
|
||||||
|
Debug.Assert(_block != null);
|
||||||
|
Debug.Assert(_block.Pool != null);
|
||||||
|
Debug.Assert(_block.Next == null);
|
||||||
|
Debug.Assert(_block.End == _index);
|
||||||
|
|
||||||
|
var pool = _block.Pool;
|
||||||
|
var block = _block;
|
||||||
|
var blockIndex = _index;
|
||||||
|
|
||||||
|
var bufferIndex = buffer.Offset;
|
||||||
|
var remaining = buffer.Count;
|
||||||
|
var bytesLeftInBlock = block.Data.Offset + block.Data.Count - blockIndex;
|
||||||
|
|
||||||
|
while (remaining > 0)
|
||||||
|
{
|
||||||
|
if (bytesLeftInBlock == 0)
|
||||||
|
{
|
||||||
|
var nextBlock = pool.Lease();
|
||||||
|
block.Next = nextBlock;
|
||||||
|
block = nextBlock;
|
||||||
|
|
||||||
|
blockIndex = block.Data.Offset;
|
||||||
|
bytesLeftInBlock = block.Data.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytesToCopy = remaining < bytesLeftInBlock ? remaining : bytesLeftInBlock;
|
||||||
|
|
||||||
|
Buffer.BlockCopy(buffer.Array, bufferIndex, block.Array, blockIndex, bytesToCopy);
|
||||||
|
|
||||||
|
blockIndex += bytesToCopy;
|
||||||
|
bufferIndex += bytesToCopy;
|
||||||
|
remaining -= bytesToCopy;
|
||||||
|
bytesLeftInBlock -= bytesToCopy;
|
||||||
|
block.End = blockIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
_block = block;
|
||||||
|
_index = blockIndex;
|
||||||
|
|
||||||
|
return buffer.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe int CopyFromAscii(string data)
|
||||||
|
{
|
||||||
|
Debug.Assert(_block != null);
|
||||||
|
Debug.Assert(_block.Pool != null);
|
||||||
|
Debug.Assert(_block.Next == null);
|
||||||
|
Debug.Assert(_block.End == _index);
|
||||||
|
|
||||||
|
var pool = _block.Pool;
|
||||||
|
var block = _block;
|
||||||
|
var blockIndex = _index;
|
||||||
|
var length = data.Length;
|
||||||
|
|
||||||
|
var bytesLeftInBlock = block.Data.Offset + block.Data.Count - blockIndex;
|
||||||
|
var bytesLeftInBlockMinusSpan = bytesLeftInBlock - 3;
|
||||||
|
|
||||||
|
fixed (char* pData = data)
|
||||||
|
{
|
||||||
|
var input = pData;
|
||||||
|
var inputEnd = pData + length;
|
||||||
|
var inputEndMinusSpan = inputEnd - 3;
|
||||||
|
|
||||||
|
while (input < inputEnd)
|
||||||
|
{
|
||||||
|
if (bytesLeftInBlock == 0)
|
||||||
|
{
|
||||||
|
var nextBlock = pool.Lease();
|
||||||
|
block.Next = nextBlock;
|
||||||
|
block = nextBlock;
|
||||||
|
|
||||||
|
blockIndex = block.Data.Offset;
|
||||||
|
bytesLeftInBlock = block.Data.Count;
|
||||||
|
bytesLeftInBlockMinusSpan = bytesLeftInBlock - 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed (byte* pOutput = block.Data.Array)
|
||||||
|
{
|
||||||
|
var output = pOutput + block.End;
|
||||||
|
|
||||||
|
var copied = 0;
|
||||||
|
for (; input < inputEndMinusSpan && copied < bytesLeftInBlockMinusSpan; copied += 4)
|
||||||
|
{
|
||||||
|
*(output) = (byte)*(input);
|
||||||
|
*(output + 1) = (byte)*(input + 1);
|
||||||
|
*(output + 2) = (byte)*(input + 2);
|
||||||
|
*(output + 3) = (byte)*(input + 3);
|
||||||
|
output += 4;
|
||||||
|
input += 4;
|
||||||
|
}
|
||||||
|
for (; input < inputEnd && copied < bytesLeftInBlock; copied++)
|
||||||
|
{
|
||||||
|
*(output++) = (byte)*(input++);
|
||||||
|
}
|
||||||
|
|
||||||
|
blockIndex += copied;
|
||||||
|
bytesLeftInBlockMinusSpan -= copied;
|
||||||
|
bytesLeftInBlock -= copied;
|
||||||
|
}
|
||||||
|
block.End = blockIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_block = block;
|
||||||
|
_index = blockIndex;
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,9 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
|
||||||
|
|
||||||
public unsafe void Write(
|
public unsafe void Write(
|
||||||
UvStreamHandle handle,
|
UvStreamHandle handle,
|
||||||
ArraySegment<ArraySegment<byte>> bufs,
|
MemoryPoolIterator2 start,
|
||||||
|
MemoryPoolIterator2 end,
|
||||||
|
int nBuffers,
|
||||||
Action<UvWriteReq, int, Exception, object> callback,
|
Action<UvWriteReq, int, Exception, object> callback,
|
||||||
object state)
|
object state)
|
||||||
{
|
{
|
||||||
|
|
@ -51,7 +53,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
|
||||||
_pins.Add(GCHandle.Alloc(this, GCHandleType.Normal));
|
_pins.Add(GCHandle.Alloc(this, GCHandleType.Normal));
|
||||||
|
|
||||||
var pBuffers = (Libuv.uv_buf_t*)_bufs;
|
var pBuffers = (Libuv.uv_buf_t*)_bufs;
|
||||||
var nBuffers = bufs.Count;
|
|
||||||
if (nBuffers > BUFFER_COUNT)
|
if (nBuffers > BUFFER_COUNT)
|
||||||
{
|
{
|
||||||
// create and pin buffer array when it's larger than the pre-allocated one
|
// create and pin buffer array when it's larger than the pre-allocated one
|
||||||
|
|
@ -61,16 +62,18 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
|
||||||
pBuffers = (Libuv.uv_buf_t*)gcHandle.AddrOfPinnedObject();
|
pBuffers = (Libuv.uv_buf_t*)gcHandle.AddrOfPinnedObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var block = start.Block;
|
||||||
for (var index = 0; index < nBuffers; index++)
|
for (var index = 0; index < nBuffers; index++)
|
||||||
{
|
{
|
||||||
// create and pin each segment being written
|
var blockStart = block == start.Block ? start.Index : block.Data.Offset;
|
||||||
var buf = bufs.Array[bufs.Offset + index];
|
var blockEnd = block == end.Block ? end.Index : block.Data.Offset + block.Data.Count;
|
||||||
|
|
||||||
var gcHandle = GCHandle.Alloc(buf.Array, GCHandleType.Pinned);
|
// create and pin each segment being written
|
||||||
_pins.Add(gcHandle);
|
|
||||||
pBuffers[index] = Libuv.buf_init(
|
pBuffers[index] = Libuv.buf_init(
|
||||||
gcHandle.AddrOfPinnedObject() + buf.Offset,
|
block.Pin() + blockStart,
|
||||||
buf.Count);
|
blockEnd - blockStart);
|
||||||
|
|
||||||
|
block = block.Next;
|
||||||
}
|
}
|
||||||
|
|
||||||
_callback = callback;
|
_callback = callback;
|
||||||
|
|
@ -82,6 +85,14 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
|
||||||
_callback = null;
|
_callback = null;
|
||||||
_state = null;
|
_state = null;
|
||||||
Unpin(this);
|
Unpin(this);
|
||||||
|
|
||||||
|
var block = start.Block;
|
||||||
|
for (var index = 0; index < nBuffers; index++)
|
||||||
|
{
|
||||||
|
block.Unpin();
|
||||||
|
block = block.Next;
|
||||||
|
}
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,11 @@ namespace Microsoft.AspNet.Server.Kestrel
|
||||||
{
|
{
|
||||||
public ServiceContext()
|
public ServiceContext()
|
||||||
{
|
{
|
||||||
Memory = new MemoryPool();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServiceContext(ServiceContext context)
|
public ServiceContext(ServiceContext context)
|
||||||
{
|
{
|
||||||
AppLifetime = context.AppLifetime;
|
AppLifetime = context.AppLifetime;
|
||||||
Memory = context.Memory;
|
|
||||||
Log = context.Log;
|
Log = context.Log;
|
||||||
HttpContextFactory = context.HttpContextFactory;
|
HttpContextFactory = context.HttpContextFactory;
|
||||||
DateHeaderValueManager = context.DateHeaderValueManager;
|
DateHeaderValueManager = context.DateHeaderValueManager;
|
||||||
|
|
@ -29,8 +27,6 @@ namespace Microsoft.AspNet.Server.Kestrel
|
||||||
|
|
||||||
public IApplicationLifetime AppLifetime { get; set; }
|
public IApplicationLifetime AppLifetime { get; set; }
|
||||||
|
|
||||||
public IMemoryPool Memory { get; set; }
|
|
||||||
|
|
||||||
public IKestrelTrace Log { get; set; }
|
public IKestrelTrace Log { get; set; }
|
||||||
|
|
||||||
public IHttpContextFactory HttpContextFactory { get; set; }
|
public IHttpContextFactory HttpContextFactory { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -171,8 +171,8 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
"Content-Length: 0",
|
"Content-Length: 0",
|
||||||
"",
|
"",
|
||||||
"HTTP/1.1 200 OK",
|
"HTTP/1.1 200 OK",
|
||||||
"Content-Length: 7",
|
|
||||||
"Connection: close",
|
"Connection: close",
|
||||||
|
"Content-Length: 7",
|
||||||
"",
|
"",
|
||||||
"Goodbye");
|
"Goodbye");
|
||||||
}
|
}
|
||||||
|
|
@ -241,8 +241,8 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
"Goodbye");
|
"Goodbye");
|
||||||
await connection.Receive(
|
await connection.Receive(
|
||||||
"HTTP/1.0 200 OK",
|
"HTTP/1.0 200 OK",
|
||||||
"Content-Length: 0",
|
|
||||||
"Connection: keep-alive",
|
"Connection: keep-alive",
|
||||||
|
"Content-Length: 0",
|
||||||
"\r\n");
|
"\r\n");
|
||||||
await connection.ReceiveEnd(
|
await connection.ReceiveEnd(
|
||||||
"HTTP/1.0 200 OK",
|
"HTTP/1.0 200 OK",
|
||||||
|
|
@ -267,14 +267,14 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
"Connection: keep-alive",
|
"Connection: keep-alive",
|
||||||
"",
|
"",
|
||||||
"POST / HTTP/1.0",
|
"POST / HTTP/1.0",
|
||||||
"Connection: keep-alive",
|
|
||||||
"Content-Length: 7",
|
"Content-Length: 7",
|
||||||
|
"Connection: keep-alive",
|
||||||
"",
|
"",
|
||||||
"Goodbye");
|
"Goodbye");
|
||||||
await connection.Receive(
|
await connection.Receive(
|
||||||
"HTTP/1.0 200 OK",
|
"HTTP/1.0 200 OK",
|
||||||
"Content-Length: 0",
|
|
||||||
"Connection: keep-alive",
|
"Connection: keep-alive",
|
||||||
|
"Content-Length: 0",
|
||||||
"\r\n");
|
"\r\n");
|
||||||
await connection.ReceiveEnd(
|
await connection.ReceiveEnd(
|
||||||
"HTTP/1.0 200 OK",
|
"HTTP/1.0 200 OK",
|
||||||
|
|
@ -295,16 +295,16 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
{
|
{
|
||||||
await connection.SendEnd(
|
await connection.SendEnd(
|
||||||
"POST / HTTP/1.0",
|
"POST / HTTP/1.0",
|
||||||
"Connection: keep-alive",
|
|
||||||
"Content-Length: 11",
|
"Content-Length: 11",
|
||||||
|
"Connection: keep-alive",
|
||||||
"",
|
"",
|
||||||
"Hello WorldPOST / HTTP/1.0",
|
"Hello WorldPOST / HTTP/1.0",
|
||||||
"",
|
"",
|
||||||
"Goodbye");
|
"Goodbye");
|
||||||
await connection.Receive(
|
await connection.Receive(
|
||||||
"HTTP/1.0 200 OK",
|
"HTTP/1.0 200 OK",
|
||||||
"Content-Length: 11",
|
|
||||||
"Connection: keep-alive",
|
"Connection: keep-alive",
|
||||||
|
"Content-Length: 11",
|
||||||
"",
|
"",
|
||||||
"Hello World");
|
"Hello World");
|
||||||
await connection.ReceiveEnd(
|
await connection.ReceiveEnd(
|
||||||
|
|
@ -336,8 +336,8 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
"Goodbye");
|
"Goodbye");
|
||||||
await connection.Receive(
|
await connection.Receive(
|
||||||
"HTTP/1.0 200 OK",
|
"HTTP/1.0 200 OK",
|
||||||
"Content-Length: 11",
|
|
||||||
"Connection: keep-alive",
|
"Connection: keep-alive",
|
||||||
|
"Content-Length: 11",
|
||||||
"",
|
"",
|
||||||
"Hello World");
|
"Hello World");
|
||||||
await connection.ReceiveEnd(
|
await connection.ReceiveEnd(
|
||||||
|
|
@ -361,15 +361,15 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
await connection.Send(
|
await connection.Send(
|
||||||
"POST / HTTP/1.1",
|
"POST / HTTP/1.1",
|
||||||
"Expect: 100-continue",
|
"Expect: 100-continue",
|
||||||
"Content-Length: 11",
|
|
||||||
"Connection: close",
|
"Connection: close",
|
||||||
|
"Content-Length: 11",
|
||||||
"\r\n");
|
"\r\n");
|
||||||
await connection.Receive("HTTP/1.1 100 Continue", "\r\n");
|
await connection.Receive("HTTP/1.1 100 Continue", "\r\n");
|
||||||
await connection.SendEnd("Hello World");
|
await connection.SendEnd("Hello World");
|
||||||
await connection.Receive(
|
await connection.Receive(
|
||||||
"HTTP/1.1 200 OK",
|
"HTTP/1.1 200 OK",
|
||||||
"Content-Length: 11",
|
|
||||||
"Connection: close",
|
"Connection: close",
|
||||||
|
"Content-Length: 11",
|
||||||
"",
|
"",
|
||||||
"Hello World");
|
"Hello World");
|
||||||
}
|
}
|
||||||
|
|
@ -422,8 +422,8 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
"Content-Length: 0",
|
"Content-Length: 0",
|
||||||
"",
|
"",
|
||||||
"HTTP/1.0 200 OK",
|
"HTTP/1.0 200 OK",
|
||||||
"Content-Length: 0",
|
|
||||||
"Connection: keep-alive",
|
"Connection: keep-alive",
|
||||||
|
"Content-Length: 0",
|
||||||
"",
|
"",
|
||||||
"");
|
"");
|
||||||
}
|
}
|
||||||
|
|
@ -584,11 +584,12 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
"",
|
"",
|
||||||
"HTTP/1.1 500 Internal Server Error",
|
"HTTP/1.1 500 Internal Server Error",
|
||||||
"");
|
"");
|
||||||
|
await connection.Receive("Connection: close",
|
||||||
|
"");
|
||||||
await connection.ReceiveStartsWith("Date:");
|
await connection.ReceiveStartsWith("Date:");
|
||||||
await connection.ReceiveEnd(
|
await connection.ReceiveEnd(
|
||||||
"Content-Length: 0",
|
"Content-Length: 0",
|
||||||
"Server: Kestrel",
|
"Server: Kestrel",
|
||||||
"Connection: close",
|
|
||||||
"",
|
"",
|
||||||
"");
|
"");
|
||||||
|
|
||||||
|
|
@ -812,12 +813,12 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
"Server: Kestrel",
|
"Server: Kestrel",
|
||||||
"",
|
"",
|
||||||
"HTTP/1.1 500 Internal Server Error",
|
"HTTP/1.1 500 Internal Server Error",
|
||||||
|
"Connection: close",
|
||||||
"");
|
"");
|
||||||
await connection.ReceiveStartsWith("Date:");
|
await connection.ReceiveStartsWith("Date:");
|
||||||
await connection.ReceiveEnd(
|
await connection.ReceiveEnd(
|
||||||
"Content-Length: 0",
|
"Content-Length: 0",
|
||||||
"Server: Kestrel",
|
"Server: Kestrel",
|
||||||
"Connection: close",
|
|
||||||
"",
|
"",
|
||||||
"");
|
"");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Linq;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
|
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -152,6 +153,39 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CopyFromCorrectlyTraversesBlocks()
|
||||||
|
{
|
||||||
|
using (var pool = new MemoryPool2())
|
||||||
|
{
|
||||||
|
var block1 = pool.Lease(128);
|
||||||
|
var start = block1.GetIterator();
|
||||||
|
var end = start;
|
||||||
|
var bufferSize = block1.Data.Count * 3;
|
||||||
|
var buffer = new byte[bufferSize];
|
||||||
|
|
||||||
|
for (int i = 0; i < bufferSize; i++)
|
||||||
|
{
|
||||||
|
buffer[i] = (byte)(i % 73);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Null(block1.Next);
|
||||||
|
|
||||||
|
end.CopyFrom(new ArraySegment<byte>(buffer));
|
||||||
|
|
||||||
|
Assert.NotNull(block1.Next);
|
||||||
|
|
||||||
|
for (int i = 0; i < bufferSize; i++)
|
||||||
|
{
|
||||||
|
Assert.Equal(i % 73, start.Take());
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Equal(-1, start.Take());
|
||||||
|
Assert.Equal(start.Block, end.Block);
|
||||||
|
Assert.Equal(start.Index, end.Index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void IsEndCorrectlyTraversesBlocks()
|
public void IsEndCorrectlyTraversesBlocks()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -62,14 +62,24 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
|
|
||||||
var writeRequest = new UvWriteReq(new KestrelTrace(new TestKestrelTrace()));
|
var writeRequest = new UvWriteReq(new KestrelTrace(new TestKestrelTrace()));
|
||||||
writeRequest.Init(loop);
|
writeRequest.Init(loop);
|
||||||
|
var block = MemoryPoolBlock2.Create(
|
||||||
|
new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 }),
|
||||||
|
dataPtr: IntPtr.Zero,
|
||||||
|
pool: null,
|
||||||
|
slab: null);
|
||||||
|
var start = new MemoryPoolIterator2(block, 0);
|
||||||
|
var end = new MemoryPoolIterator2(block, block.Data.Count);
|
||||||
writeRequest.Write(
|
writeRequest.Write(
|
||||||
serverConnectionPipe,
|
serverConnectionPipe,
|
||||||
new ArraySegment<ArraySegment<byte>>(new ArraySegment<byte>[] { new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 }) }),
|
start,
|
||||||
|
end,
|
||||||
|
1,
|
||||||
(_3, status2, error2, _4) =>
|
(_3, status2, error2, _4) =>
|
||||||
{
|
{
|
||||||
writeRequest.Dispose();
|
writeRequest.Dispose();
|
||||||
serverConnectionPipe.Dispose();
|
serverConnectionPipe.Dispose();
|
||||||
serverListenPipe.Dispose();
|
serverListenPipe.Dispose();
|
||||||
|
block.Unpin();
|
||||||
},
|
},
|
||||||
null);
|
null);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -201,12 +201,22 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
{
|
{
|
||||||
var req = new UvWriteReq(new KestrelTrace(new TestKestrelTrace()));
|
var req = new UvWriteReq(new KestrelTrace(new TestKestrelTrace()));
|
||||||
req.Init(loop);
|
req.Init(loop);
|
||||||
|
var block = MemoryPoolBlock2.Create(
|
||||||
|
new ArraySegment<byte>(new byte[] { 65, 66, 67, 68, 69 }),
|
||||||
|
dataPtr: IntPtr.Zero,
|
||||||
|
pool: null,
|
||||||
|
slab: null);
|
||||||
|
var start = new MemoryPoolIterator2(block, 0);
|
||||||
|
var end = new MemoryPoolIterator2(block, block.Data.Count);
|
||||||
req.Write(
|
req.Write(
|
||||||
tcp2,
|
tcp2,
|
||||||
new ArraySegment<ArraySegment<byte>>(
|
start,
|
||||||
new[] { new ArraySegment<byte>(new byte[] { 65, 66, 67, 68, 69 }) }
|
end,
|
||||||
),
|
1,
|
||||||
(_1, _2, _3, _4) => { },
|
(_1, _2, _3, _4) =>
|
||||||
|
{
|
||||||
|
block.Unpin();
|
||||||
|
},
|
||||||
null);
|
null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,13 +34,14 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
};
|
};
|
||||||
|
|
||||||
using (var kestrelEngine = new KestrelEngine(mockLibuv, new TestServiceContext()))
|
using (var kestrelEngine = new KestrelEngine(mockLibuv, new TestServiceContext()))
|
||||||
|
using (var memory = new MemoryPool2())
|
||||||
{
|
{
|
||||||
kestrelEngine.Start(count: 1);
|
kestrelEngine.Start(count: 1);
|
||||||
|
|
||||||
var kestrelThread = kestrelEngine.Threads[0];
|
var kestrelThread = kestrelEngine.Threads[0];
|
||||||
var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace());
|
var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace());
|
||||||
var trace = new KestrelTrace(new TestKestrelTrace());
|
var trace = new KestrelTrace(new TestKestrelTrace());
|
||||||
var socketOutput = new SocketOutput(kestrelThread, socket, null, 0, trace);
|
var socketOutput = new SocketOutput(kestrelThread, socket, memory, null, 0, trace);
|
||||||
|
|
||||||
// I doubt _maxBytesPreCompleted will ever be over a MB. If it is, we should change this test.
|
// I doubt _maxBytesPreCompleted will ever be over a MB. If it is, we should change this test.
|
||||||
var bufferSize = 1048576;
|
var bufferSize = 1048576;
|
||||||
|
|
@ -79,13 +80,14 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
};
|
};
|
||||||
|
|
||||||
using (var kestrelEngine = new KestrelEngine(mockLibuv, new TestServiceContext()))
|
using (var kestrelEngine = new KestrelEngine(mockLibuv, new TestServiceContext()))
|
||||||
|
using (var memory = new MemoryPool2())
|
||||||
{
|
{
|
||||||
kestrelEngine.Start(count: 1);
|
kestrelEngine.Start(count: 1);
|
||||||
|
|
||||||
var kestrelThread = kestrelEngine.Threads[0];
|
var kestrelThread = kestrelEngine.Threads[0];
|
||||||
var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace());
|
var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace());
|
||||||
var trace = new KestrelTrace(new TestKestrelTrace());
|
var trace = new KestrelTrace(new TestKestrelTrace());
|
||||||
var socketOutput = new SocketOutput(kestrelThread, socket, null, 0, trace);
|
var socketOutput = new SocketOutput(kestrelThread, socket, memory, null, 0, trace);
|
||||||
|
|
||||||
var bufferSize = maxBytesPreCompleted;
|
var bufferSize = maxBytesPreCompleted;
|
||||||
var buffer = new ArraySegment<byte>(new byte[bufferSize], 0, bufferSize);
|
var buffer = new ArraySegment<byte>(new byte[bufferSize], 0, bufferSize);
|
||||||
|
|
@ -134,13 +136,14 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
};
|
};
|
||||||
|
|
||||||
using (var kestrelEngine = new KestrelEngine(mockLibuv, new TestServiceContext()))
|
using (var kestrelEngine = new KestrelEngine(mockLibuv, new TestServiceContext()))
|
||||||
|
using (var memory = new MemoryPool2())
|
||||||
{
|
{
|
||||||
kestrelEngine.Start(count: 1);
|
kestrelEngine.Start(count: 1);
|
||||||
|
|
||||||
var kestrelThread = kestrelEngine.Threads[0];
|
var kestrelThread = kestrelEngine.Threads[0];
|
||||||
var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace());
|
var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace());
|
||||||
var trace = new KestrelTrace(new TestKestrelTrace());
|
var trace = new KestrelTrace(new TestKestrelTrace());
|
||||||
var socketOutput = new SocketOutput(kestrelThread, socket, null, 0, trace);
|
var socketOutput = new SocketOutput(kestrelThread, socket, memory, null, 0, trace);
|
||||||
|
|
||||||
var bufferSize = maxBytesPreCompleted;
|
var bufferSize = maxBytesPreCompleted;
|
||||||
|
|
||||||
|
|
@ -213,13 +216,14 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
};
|
};
|
||||||
|
|
||||||
using (var kestrelEngine = new KestrelEngine(mockLibuv, new TestServiceContext()))
|
using (var kestrelEngine = new KestrelEngine(mockLibuv, new TestServiceContext()))
|
||||||
|
using (var memory = new MemoryPool2())
|
||||||
{
|
{
|
||||||
kestrelEngine.Start(count: 1);
|
kestrelEngine.Start(count: 1);
|
||||||
|
|
||||||
var kestrelThread = kestrelEngine.Threads[0];
|
var kestrelThread = kestrelEngine.Threads[0];
|
||||||
var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace());
|
var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace());
|
||||||
var trace = new KestrelTrace(new TestKestrelTrace());
|
var trace = new KestrelTrace(new TestKestrelTrace());
|
||||||
var socketOutput = new SocketOutput(kestrelThread, socket, null, 0, trace);
|
var socketOutput = new SocketOutput(kestrelThread, socket, memory, null, 0, trace);
|
||||||
|
|
||||||
var bufferSize = maxBytesPreCompleted;
|
var bufferSize = maxBytesPreCompleted;
|
||||||
var buffer = new ArraySegment<byte>(new byte[bufferSize], 0, bufferSize);
|
var buffer = new ArraySegment<byte>(new byte[bufferSize], 0, bufferSize);
|
||||||
|
|
@ -270,6 +274,57 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ProducingStartAndProducingCompleteCanBeUsedDirectly()
|
||||||
|
{
|
||||||
|
int nBuffers = 0;
|
||||||
|
var nBufferWh = new ManualResetEventSlim();
|
||||||
|
|
||||||
|
var mockLibuv = new MockLibuv
|
||||||
|
{
|
||||||
|
OnWrite = (socket, buffers, triggerCompleted) =>
|
||||||
|
{
|
||||||
|
nBuffers = buffers;
|
||||||
|
nBufferWh.Set();
|
||||||
|
triggerCompleted(0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using (var kestrelEngine = new KestrelEngine(mockLibuv, new TestServiceContext()))
|
||||||
|
using (var memory = new MemoryPool2())
|
||||||
|
{
|
||||||
|
kestrelEngine.Start(count: 1);
|
||||||
|
|
||||||
|
var kestrelThread = kestrelEngine.Threads[0];
|
||||||
|
var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace());
|
||||||
|
var trace = new KestrelTrace(new TestKestrelTrace());
|
||||||
|
var socketOutput = new SocketOutput(kestrelThread, socket, memory, null, 0, trace);
|
||||||
|
|
||||||
|
// block 1
|
||||||
|
var start = socketOutput.ProducingStart();
|
||||||
|
start.Block.End = start.Block.Data.Offset + start.Block.Data.Count;
|
||||||
|
var totalBytes = start.Block.Data.Count;
|
||||||
|
|
||||||
|
// block 2
|
||||||
|
var block2 = memory.Lease();
|
||||||
|
block2.End = block2.Data.Offset + block2.Data.Count;
|
||||||
|
start.Block.Next = block2;
|
||||||
|
totalBytes += block2.Data.Count;
|
||||||
|
|
||||||
|
var end = new MemoryPoolIterator2(block2, block2.End);
|
||||||
|
|
||||||
|
socketOutput.ProducingComplete(end, totalBytes);
|
||||||
|
|
||||||
|
// A call to Write is required to ensure a write is scheduled
|
||||||
|
socketOutput.WriteAsync(default(ArraySegment<byte>));
|
||||||
|
|
||||||
|
Assert.True(nBufferWh.Wait(1000));
|
||||||
|
Assert.Equal(2, nBuffers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private class MockSocket : UvStreamHandle
|
private class MockSocket : UvStreamHandle
|
||||||
{
|
{
|
||||||
public MockSocket(int threadId, IKestrelTrace logger) : base(logger)
|
public MockSocket(int threadId, IKestrelTrace logger) : base(logger)
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ namespace Microsoft.AspNet.Server.KestrelTests.TestHelpers
|
||||||
private bool _stopLoop;
|
private bool _stopLoop;
|
||||||
private readonly ManualResetEventSlim _loopWh = new ManualResetEventSlim();
|
private readonly ManualResetEventSlim _loopWh = new ManualResetEventSlim();
|
||||||
|
|
||||||
private Func<UvStreamHandle, ArraySegment<ArraySegment<byte>>, Action<int>, int> _onWrite;
|
private Func<UvStreamHandle, int, Action<int>, int> _onWrite;
|
||||||
|
|
||||||
unsafe public MockLibuv()
|
unsafe public MockLibuv()
|
||||||
{
|
{
|
||||||
|
|
@ -68,7 +68,7 @@ namespace Microsoft.AspNet.Server.KestrelTests.TestHelpers
|
||||||
_uv_walk = (loop, callback, ignore) => 0;
|
_uv_walk = (loop, callback, ignore) => 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Func<UvStreamHandle, ArraySegment<ArraySegment<byte>>, Action<int>, int> OnWrite
|
public Func<UvStreamHandle, int, Action<int>, int> OnWrite
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|
@ -82,7 +82,7 @@ namespace Microsoft.AspNet.Server.KestrelTests.TestHelpers
|
||||||
|
|
||||||
unsafe private int UvWrite(UvRequest req, UvStreamHandle handle, uv_buf_t* bufs, int nbufs, uv_write_cb cb)
|
unsafe private int UvWrite(UvRequest req, UvStreamHandle handle, uv_buf_t* bufs, int nbufs, uv_write_cb cb)
|
||||||
{
|
{
|
||||||
return _onWrite(handle, new ArraySegment<ArraySegment<byte>>(), status => cb(req.InternalGetHandle(), status));
|
return _onWrite(handle, nbufs, status => cb(req.InternalGetHandle(), status));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,10 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
{
|
{
|
||||||
public TestInput()
|
public TestInput()
|
||||||
{
|
{
|
||||||
var memory = new MemoryPool();
|
|
||||||
var memory2 = new MemoryPool2();
|
var memory2 = new MemoryPool2();
|
||||||
FrameContext = new FrameContext
|
FrameContext = new FrameContext
|
||||||
{
|
{
|
||||||
SocketInput = new SocketInput(memory2),
|
SocketInput = new SocketInput(memory2),
|
||||||
Memory = memory,
|
|
||||||
ConnectionControl = this,
|
ConnectionControl = this,
|
||||||
FrameControl = this
|
FrameControl = this
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.Dnx.Compilation.CSharp;
|
using Microsoft.Dnx.Compilation.CSharp;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Server.Kestrel.GeneratedCode
|
namespace Microsoft.AspNet.Server.Kestrel.GeneratedCode
|
||||||
{
|
{
|
||||||
|
|
@ -11,7 +12,7 @@ namespace Microsoft.AspNet.Server.Kestrel.GeneratedCode
|
||||||
{
|
{
|
||||||
static string Each<T>(IEnumerable<T> values, Func<T, string> formatter)
|
static string Each<T>(IEnumerable<T> values, Func<T, string> formatter)
|
||||||
{
|
{
|
||||||
return values.Select(formatter).Aggregate((a, b) => a + b);
|
return values.Any() ? values.Select(formatter).Aggregate((a, b) => a + b) : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
class KnownHeader
|
class KnownHeader
|
||||||
|
|
@ -19,10 +20,14 @@ namespace Microsoft.AspNet.Server.Kestrel.GeneratedCode
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public int Index { get; set; }
|
public int Index { get; set; }
|
||||||
public string Identifier => Name.Replace("-", "");
|
public string Identifier => Name.Replace("-", "");
|
||||||
|
|
||||||
|
public byte[] Bytes => Encoding.ASCII.GetBytes($"\r\n{Name}: ");
|
||||||
|
public int BytesOffset { get; set; }
|
||||||
|
public int BytesCount { get; set; }
|
||||||
|
public bool EnhancedSetter { get; set; }
|
||||||
public string TestBit() => $"((_bits & {1L << Index}L) != 0)";
|
public string TestBit() => $"((_bits & {1L << Index}L) != 0)";
|
||||||
public string SetBit() => $"_bits |= {1L << Index}L";
|
public string SetBit() => $"_bits |= {1L << Index}L";
|
||||||
public string ClearBit() => $"_bits &= ~{1L << Index}L";
|
public string ClearBit() => $"_bits &= ~{1L << Index}L";
|
||||||
|
|
||||||
public string EqualIgnoreCaseBytes()
|
public string EqualIgnoreCaseBytes()
|
||||||
{
|
{
|
||||||
var result = "";
|
var result = "";
|
||||||
|
|
@ -54,7 +59,6 @@ namespace Microsoft.AspNet.Server.Kestrel.GeneratedCode
|
||||||
}
|
}
|
||||||
return $"({result})";
|
return $"({result})";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected string Term(string name, int offset, int count, string array, string suffix)
|
protected string Term(string name, int offset, int count, string array, string suffix)
|
||||||
{
|
{
|
||||||
ulong mask = 0;
|
ulong mask = 0;
|
||||||
|
|
@ -69,13 +73,11 @@ namespace Microsoft.AspNet.Server.Kestrel.GeneratedCode
|
||||||
return $"(({array}[{offset / count}] & {mask}{suffix}) == {comp}{suffix})";
|
return $"(({array}[{offset / count}] & {mask}{suffix}) == {comp}{suffix})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void BeforeCompile(BeforeCompileContext context)
|
public virtual void BeforeCompile(BeforeCompileContext context)
|
||||||
{
|
{
|
||||||
var syntaxTree = Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.ParseText(GeneratedFile());
|
var syntaxTree = Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.ParseText(GeneratedFile());
|
||||||
context.Compilation = context.Compilation.AddSyntaxTrees(syntaxTree);
|
context.Compilation = context.Compilation.AddSyntaxTrees(syntaxTree);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GeneratedFile()
|
public static string GeneratedFile()
|
||||||
{
|
{
|
||||||
var commonHeaders = new[]
|
var commonHeaders = new[]
|
||||||
|
|
@ -128,8 +130,15 @@ namespace Microsoft.AspNet.Server.Kestrel.GeneratedCode
|
||||||
{
|
{
|
||||||
Name = header,
|
Name = header,
|
||||||
Index = index
|
Index = index
|
||||||
});
|
}).ToArray();
|
||||||
|
var enhancedHeaders = new[]
|
||||||
|
{
|
||||||
|
"Connection",
|
||||||
|
"Server",
|
||||||
|
"Date",
|
||||||
|
"Transfer-Encoding",
|
||||||
|
"Content-Length",
|
||||||
|
};
|
||||||
var responseHeaders = commonHeaders.Concat(new[]
|
var responseHeaders = commonHeaders.Concat(new[]
|
||||||
{
|
{
|
||||||
"Accept-Ranges",
|
"Accept-Ranges",
|
||||||
|
|
@ -145,38 +154,60 @@ namespace Microsoft.AspNet.Server.Kestrel.GeneratedCode
|
||||||
}).Select((header, index) => new KnownHeader
|
}).Select((header, index) => new KnownHeader
|
||||||
{
|
{
|
||||||
Name = header,
|
Name = header,
|
||||||
Index = index
|
Index = index,
|
||||||
});
|
EnhancedSetter = enhancedHeaders.Contains(header)
|
||||||
|
}).ToArray();
|
||||||
var loops = new[]
|
var loops = new[]
|
||||||
{
|
{
|
||||||
new
|
new
|
||||||
{
|
{
|
||||||
Headers = requestHeaders,
|
Headers = requestHeaders,
|
||||||
HeadersByLength = requestHeaders.GroupBy(x => x.Name.Length),
|
HeadersByLength = requestHeaders.GroupBy(x => x.Name.Length),
|
||||||
ClassName = "FrameRequestHeaders"
|
ClassName = "FrameRequestHeaders",
|
||||||
|
Bytes = default(byte[])
|
||||||
},
|
},
|
||||||
new
|
new
|
||||||
{
|
{
|
||||||
Headers = responseHeaders,
|
Headers = responseHeaders,
|
||||||
HeadersByLength = responseHeaders.GroupBy(x => x.Name.Length),
|
HeadersByLength = responseHeaders.GroupBy(x => x.Name.Length),
|
||||||
ClassName = "FrameResponseHeaders"
|
ClassName = "FrameResponseHeaders",
|
||||||
|
Bytes = responseHeaders.SelectMany(header => header.Bytes).ToArray()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
foreach (var loop in loops.Where(l => l.Bytes != null))
|
||||||
|
{
|
||||||
|
var offset = 0;
|
||||||
|
foreach (var header in loop.Headers)
|
||||||
|
{
|
||||||
|
header.BytesOffset = offset;
|
||||||
|
header.BytesCount += header.Bytes.Length;
|
||||||
|
offset += header.BytesCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
return $@"
|
return $@"
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Server.Kestrel.Http
|
namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
{{
|
{{
|
||||||
{Each(loops, loop => $@"
|
{Each(loops, loop => $@"
|
||||||
public partial class {loop.ClassName}
|
public partial class {loop.ClassName}
|
||||||
{{
|
{{{(loop.Bytes != null ?
|
||||||
|
$@"
|
||||||
|
private static byte[] _headerBytes = new byte[]
|
||||||
|
{{
|
||||||
|
{Each(loop.Bytes, b => $"{b},")}
|
||||||
|
}};"
|
||||||
|
: "")}
|
||||||
|
|
||||||
private long _bits = 0;
|
private long _bits = 0;
|
||||||
{Each(loop.Headers, header => @"
|
{Each(loop.Headers, header => @"
|
||||||
private StringValues _" + header.Identifier + ";")}
|
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}
|
||||||
{{
|
{{
|
||||||
|
|
@ -187,22 +218,28 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
set
|
set
|
||||||
{{
|
{{
|
||||||
{header.SetBit()};
|
{header.SetBit()};
|
||||||
_{header.Identifier} = value;
|
_{header.Identifier} = value; {(header.EnhancedSetter == false ? "" : $@"
|
||||||
|
_raw{header.Identifier} = null;")}
|
||||||
}}
|
}}
|
||||||
}}
|
}}")}
|
||||||
")}
|
{Each(loop.Headers.Where(header => header.EnhancedSetter), header => $@"
|
||||||
|
public void SetRaw{header.Identifier}(StringValues value, byte[] raw)
|
||||||
|
{{
|
||||||
|
{header.SetBit()};
|
||||||
|
_{header.Identifier} = value;
|
||||||
|
_raw{header.Identifier} = raw;
|
||||||
|
}}")}
|
||||||
protected override int GetCountFast()
|
protected override int GetCountFast()
|
||||||
{{
|
{{
|
||||||
return BitCount(_bits) + (MaybeUnknown?.Count ?? 0);
|
return BitCount(_bits) + (MaybeUnknown?.Count ?? 0);
|
||||||
}}
|
}}
|
||||||
|
|
||||||
protected override StringValues GetValueFast(string key)
|
protected override StringValues GetValueFast(string key)
|
||||||
{{
|
{{
|
||||||
switch(key.Length)
|
switch(key.Length)
|
||||||
{{{Each(loop.HeadersByLength, byLength => $@"
|
{{{Each(loop.HeadersByLength, byLength => $@"
|
||||||
case {byLength.Key}:
|
case {byLength.Key}:
|
||||||
{{{Each(byLength, header => $@"
|
{{{Each(byLength, header => $@"
|
||||||
if (""{header.Name}"".Equals(key, StringComparison.OrdinalIgnoreCase))
|
if (""{header.Name}"".Equals(key, StringComparison.OrdinalIgnoreCase))
|
||||||
{{
|
{{
|
||||||
if ({header.TestBit()})
|
if ({header.TestBit()})
|
||||||
{{
|
{{
|
||||||
|
|
@ -215,14 +252,13 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
}}
|
}}
|
||||||
")}}}
|
")}}}
|
||||||
break;
|
break;
|
||||||
")}}}
|
")}}}
|
||||||
if (MaybeUnknown == null)
|
if (MaybeUnknown == null)
|
||||||
{{
|
{{
|
||||||
throw new System.Collections.Generic.KeyNotFoundException();
|
throw new System.Collections.Generic.KeyNotFoundException();
|
||||||
}}
|
}}
|
||||||
return MaybeUnknown[key];
|
return MaybeUnknown[key];
|
||||||
}}
|
}}
|
||||||
|
|
||||||
protected override bool TryGetValueFast(string key, out StringValues value)
|
protected override bool TryGetValueFast(string key, out StringValues value)
|
||||||
{{
|
{{
|
||||||
switch(key.Length)
|
switch(key.Length)
|
||||||
|
|
@ -244,11 +280,10 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
}}
|
}}
|
||||||
")}}}
|
")}}}
|
||||||
break;
|
break;
|
||||||
")}}}
|
")}}}
|
||||||
value = StringValues.Empty;
|
value = StringValues.Empty;
|
||||||
return MaybeUnknown?.TryGetValue(key, out value) ?? false;
|
return MaybeUnknown?.TryGetValue(key, out value) ?? false;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
protected override void SetValueFast(string key, StringValues value)
|
protected override void SetValueFast(string key, StringValues value)
|
||||||
{{
|
{{
|
||||||
switch(key.Length)
|
switch(key.Length)
|
||||||
|
|
@ -258,29 +293,30 @@ namespace Microsoft.AspNet.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.Identifier} = value;{(header.EnhancedSetter == false ? "" : $@"
|
||||||
|
_raw{header.Identifier} = null;")}
|
||||||
return;
|
return;
|
||||||
}}
|
}}
|
||||||
")}}}
|
")}}}
|
||||||
break;
|
break;
|
||||||
")}}}
|
")}}}
|
||||||
Unknown[key] = value;
|
Unknown[key] = value;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
protected override void AddValueFast(string key, StringValues value)
|
protected override void AddValueFast(string key, StringValues value)
|
||||||
{{
|
{{
|
||||||
switch(key.Length)
|
switch(key.Length)
|
||||||
{{{Each(loop.HeadersByLength, byLength => $@"
|
{{{Each(loop.HeadersByLength, byLength => $@"
|
||||||
case {byLength.Key}:
|
case {byLength.Key}:
|
||||||
{{{Each(byLength, header => $@"
|
{{{Each(byLength, header => $@"
|
||||||
if (""{header.Name}"".Equals(key, StringComparison.OrdinalIgnoreCase))
|
if (""{header.Name}"".Equals(key, StringComparison.OrdinalIgnoreCase))
|
||||||
{{
|
{{
|
||||||
if ({header.TestBit()})
|
if ({header.TestBit()})
|
||||||
{{
|
{{
|
||||||
throw new ArgumentException(""An item with the same key has already been added."");
|
throw new ArgumentException(""An item with the same key has already been added."");
|
||||||
}}
|
}}
|
||||||
{header.SetBit()};
|
{header.SetBit()};
|
||||||
_{header.Identifier} = value;
|
_{header.Identifier} = value;{(header.EnhancedSetter == false ? "" : $@"
|
||||||
|
_raw{header.Identifier} = null;")}
|
||||||
return;
|
return;
|
||||||
}}
|
}}
|
||||||
")}}}
|
")}}}
|
||||||
|
|
@ -288,7 +324,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
")}}}
|
")}}}
|
||||||
Unknown.Add(key, value);
|
Unknown.Add(key, value);
|
||||||
}}
|
}}
|
||||||
|
|
||||||
protected override bool RemoveFast(string key)
|
protected override bool RemoveFast(string key)
|
||||||
{{
|
{{
|
||||||
switch(key.Length)
|
switch(key.Length)
|
||||||
|
|
@ -300,7 +335,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
if ({header.TestBit()})
|
if ({header.TestBit()})
|
||||||
{{
|
{{
|
||||||
{header.ClearBit()};
|
{header.ClearBit()};
|
||||||
_{header.Identifier} = StringValues.Empty;
|
_{header.Identifier} = StringValues.Empty;{(header.EnhancedSetter == false ? "" : $@"
|
||||||
|
_raw{header.Identifier} = null;")}
|
||||||
return true;
|
return true;
|
||||||
}}
|
}}
|
||||||
else
|
else
|
||||||
|
|
@ -313,12 +349,13 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
")}}}
|
")}}}
|
||||||
return MaybeUnknown?.Remove(key) ?? false;
|
return MaybeUnknown?.Remove(key) ?? false;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
protected override void ClearFast()
|
protected override void ClearFast()
|
||||||
{{
|
{{
|
||||||
_bits = 0;
|
_bits = 0;
|
||||||
{Each(loop.Headers, header => $@"
|
{Each(loop.Headers, header => $@"
|
||||||
_{header.Identifier} = StringValues.Empty;")}
|
_{header.Identifier} = StringValues.Empty;")}
|
||||||
|
{Each(loop.Headers.Where(header => header.EnhancedSetter), header => $@"
|
||||||
|
_raw{header.Identifier} = null;")}
|
||||||
MaybeUnknown?.Clear();
|
MaybeUnknown?.Clear();
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
|
@ -328,7 +365,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
{{
|
{{
|
||||||
throw new ArgumentException();
|
throw new ArgumentException();
|
||||||
}}
|
}}
|
||||||
|
|
||||||
{Each(loop.Headers, header => $@"
|
{Each(loop.Headers, header => $@"
|
||||||
if ({header.TestBit()})
|
if ({header.TestBit()})
|
||||||
{{
|
{{
|
||||||
|
|
@ -343,7 +379,26 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
")}
|
")}
|
||||||
((ICollection<KeyValuePair<string, StringValues>>)MaybeUnknown)?.CopyTo(array, arrayIndex);
|
((ICollection<KeyValuePair<string, StringValues>>)MaybeUnknown)?.CopyTo(array, arrayIndex);
|
||||||
}}
|
}}
|
||||||
|
{(loop.ClassName == "FrameResponseHeaders" ? $@"
|
||||||
|
protected int CopyToFast(ref MemoryPoolIterator2 output)
|
||||||
|
{{
|
||||||
|
var count = 0;
|
||||||
|
{Each(loop.Headers, header => $@"
|
||||||
|
if ({header.TestBit()})
|
||||||
|
{{ {(header.EnhancedSetter == false ? "" : $@"
|
||||||
|
if (_raw{header.Identifier} != null)
|
||||||
|
{{
|
||||||
|
count += output.CopyFrom(_raw{header.Identifier}, 0, _raw{header.Identifier}.Length);
|
||||||
|
}} else ")}
|
||||||
|
foreach(var value in _{header.Identifier})
|
||||||
|
{{
|
||||||
|
count += output.CopyFrom(_headerBytes, {header.BytesOffset}, {header.BytesCount});
|
||||||
|
count += output.CopyFromAscii(value);
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
")}
|
||||||
|
return count;
|
||||||
|
}}" : "")}
|
||||||
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) {{ var pUB = ptr + keyOffset; var pUL = (ulong*)pUB; var pUI = (uint*)pUB; var pUS = (ushort*)pUB;
|
fixed(byte* ptr = keyBytes) {{ var pUB = ptr + keyOffset; var pUL = (ulong*)pUB; var pUI = (uint*)pUB; var pUS = (ushort*)pUB;
|
||||||
|
|
@ -360,7 +415,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
else
|
else
|
||||||
{{
|
{{
|
||||||
{header.SetBit()};
|
{header.SetBit()};
|
||||||
_{header.Identifier} = new StringValues(value);
|
_{header.Identifier} = new StringValues(value);{(header.EnhancedSetter == false ? "" : $@"
|
||||||
|
_raw{header.Identifier} = null;")}
|
||||||
}}
|
}}
|
||||||
return;
|
return;
|
||||||
}}
|
}}
|
||||||
|
|
@ -372,7 +428,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
Unknown.TryGetValue(key, out existing);
|
Unknown.TryGetValue(key, out existing);
|
||||||
Unknown[key] = AppendValue(existing, value);
|
Unknown[key] = AppendValue(existing, value);
|
||||||
}}
|
}}
|
||||||
|
|
||||||
public partial struct Enumerator
|
public partial struct Enumerator
|
||||||
{{
|
{{
|
||||||
public bool MoveNext()
|
public bool MoveNext()
|
||||||
|
|
@ -409,9 +464,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
")}}}
|
")}}}
|
||||||
";
|
";
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void AfterCompile(AfterCompileContext context)
|
public virtual void AfterCompile(AfterCompileContext context)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue