Precomputed header bytes

This commit is contained in:
Ben Adams 2015-11-11 17:20:31 +00:00
parent 0dbf108353
commit a3173c487a
13 changed files with 1231 additions and 800 deletions

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Text;
using System.Threading;
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
@ -17,6 +18,9 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
private readonly TimeSpan _timerInterval;
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 bool _isDisposed = false;
private bool _hadRequestsSinceLastTimerTick = false;
@ -62,6 +66,12 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
return _dateValue ?? _systemClock.UtcNow.ToString(Constants.RFC1123DateFormat);
}
public byte[] GetDateHeaderValueBytes()
{
PumpTimer();
return _activeDateBytes ? _dateBytes0 : _dateBytes1;
}
/// <summary>
/// Releases all resources used by the current instance of <see cref="DateHeaderValueManager"/>.
/// </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
// inline now to serve requests that occur in the meantime.
_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);
}
}
@ -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
_dateValue = now.ToString(Constants.RFC1123DateFormat);
Encoding.ASCII.GetBytes(_dateValue, 0, _dateValue.Length, !_activeDateBytes ? _dateBytes0 : _dateBytes1, "\r\nDate: ".Length);
_activeDateBytes = !_activeDateBytes;
if (_hadRequestsSinceLastTimerTick)
{

View File

@ -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 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 _onCompletedSync = new Object();
private readonly FrameRequestHeaders _requestHeaders = new FrameRequestHeaders();
@ -53,6 +64,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
private bool _autoChunk;
private Exception _applicationException;
private HttpVersionType _httpVersion;
private readonly IPEndPoint _localEndPoint;
private readonly IPEndPoint _remoteEndPoint;
private readonly Action<IFeatureCollection> _prepareRequest;
@ -81,7 +94,37 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
public string RequestUri { get; set; }
public string Path { 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 Stream RequestBody { get; set; }
@ -118,7 +161,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
RequestUri = null;
Path = null;
QueryString = null;
HttpVersion = null;
_httpVersion = HttpVersionType.Unknown;
RequestHeaders = _requestHeaders;
RequestBody = null;
StatusCode = 200;
@ -151,8 +194,12 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
public void ResetResponseHeaders()
{
_responseHeaders.Reset();
_responseHeaders.HeaderServer = "Kestrel";
_responseHeaders.HeaderDate = DateHeaderValueManager.GetDateHeaderValue();
_responseHeaders.SetRawDate(
DateHeaderValueManager.GetDateHeaderValue(),
DateHeaderValueManager.GetDateHeaderValueBytes());
_responseHeaders.SetRawServer(
"Kestrel",
_bytesServer);
}
/// <summary>
@ -502,7 +549,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
if (_responseStarted) return;
StringValues expect;
if (HttpVersion.Equals("HTTP/1.1") &&
if (_httpVersion == HttpVersionType.Http1_1 &&
RequestHeaders.TryGetValue("Expect", out expect) &&
(expect.FirstOrDefault() ?? "").Equals("100-continue", StringComparison.OrdinalIgnoreCase))
{
@ -526,19 +573,14 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
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;
var status = ReasonPhrases.ToStatus(StatusCode, ReasonPhrase);
var statusBytes = ReasonPhrases.ToStatusBytes(StatusCode, ReasonPhrase);
var responseHeader = CreateResponseHeader(status, appCompleted);
using (responseHeader.Item2)
{
await SocketOutput.WriteAsync(responseHeader.Item1, immediate: immediate);
}
return CreateResponseHeader(statusBytes, appCompleted, immediate);
}
private async Task ProduceEnd()
@ -557,7 +599,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
ReasonPhrase = null;
ResetResponseHeaders();
_responseHeaders.HeaderContentLength = "0";
_responseHeaders.SetRawContentLength("0", _bytesContentLengthZero);
}
}
@ -576,58 +618,26 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
}
}
private Tuple<ArraySegment<byte>, IDisposable> CreateResponseHeader(
string status,
bool appCompleted)
private Task CreateResponseHeader(
byte[] statusBytes,
bool appCompleted,
bool immediate)
{
var writer = new MemoryPoolTextWriter(Memory);
writer.Write(HttpVersion);
writer.Write(' ');
writer.Write(status);
writer.Write('\r');
writer.Write('\n');
var hasConnection = false;
var hasTransferEncoding = false;
var hasContentLength = false;
foreach (var header in _responseHeaders)
var memoryBlock = Memory2.Lease();
var begin = memoryBlock.GetIterator();
var end = begin;
if (_keepAlive)
{
var isConnection = false;
if (!hasConnection &&
string.Equals(header.Key, "Connection", StringComparison.OrdinalIgnoreCase))
foreach (var connectionValue in _responseHeaders.HeaderConnection)
{
hasConnection = isConnection = true;
}
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)
if (connectionValue.IndexOf("close", StringComparison.OrdinalIgnoreCase) != -1)
{
_keepAlive = false;
}
}
}
if (_keepAlive && !hasTransferEncoding && !hasContentLength)
if (_keepAlive && !_responseHeaders.HasTransferEncoding && !_responseHeaders.HasContentLength)
{
if (appCompleted)
{
@ -637,15 +647,15 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
// Since the app has completed and we are only now generating
// the headers we can safely set the Content-Length to 0.
writer.Write("Content-Length: 0\r\n");
_responseHeaders.SetRawContentLength("0", _bytesContentLengthZero);
}
}
else
{
if (HttpVersion == "HTTP/1.1")
if (_httpVersion == HttpVersionType.Http1_1)
{
_autoChunk = true;
writer.Write("Transfer-Encoding: chunked\r\n");
_responseHeaders.SetRawTransferEncoding("chunked", _bytesTransferEncodingChunked);
}
else
{
@ -654,21 +664,58 @@ 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);
}
end.CopyFrom(_httpVersion == HttpVersionType.Http1_1 ? _bytesHttpVersion1_1 : _bytesHttpVersion1_0);
end.CopyFrom(statusBytes);
_responseHeaders.CopyTo(ref end);
end.CopyFrom(_bytesEndHeaders, 0, _bytesEndHeaders.Length);
// TODO: change this to SocketOutput.ProduceStart/ProduceComplete once that change is made
var scan = begin.Block;
while (scan.Next != null)
{
if (scan.Start != scan.End)
{
SocketOutput.WriteAsync(
new ArraySegment<byte>(scan.Array, scan.Start, scan.End - scan.Start),
false);
}
var next = scan.Next;
Memory2.Return(scan);
scan = next;
}
var writeTask = SocketOutput.WriteAsync(
new ArraySegment<byte>(scan.Array, scan.Start, scan.End - scan.Start),
immediate);
if (writeTask.IsCompleted)
{
Memory2.Return(scan);
return TaskUtilities.CompletedTask;
}
else
{
writer.Write('\r');
writer.Write('\n');
return writeTask.ContinueWith(
(t, o) =>
{
var mb = (MemoryPoolBlock2)o;
mb.Pool.Return(mb);
if (t.IsFaulted)
{
throw t.Exception;
}
},
scan);
}
writer.Flush();
return new Tuple<ArraySegment<byte>, IDisposable>(writer.Buffer, writer);
}
private bool TakeStartLine(SocketInput input)
@ -877,5 +924,12 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
_applicationException = ex;
Log.ApplicationError(ex);
}
private enum HttpVersionType
{
Unknown = -1,
Http1_0 = 0,
Http1_1 = 1
}
}
}

View File

@ -1,14 +1,25 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Extensions.Primitives;
using System.Collections;
using System.Collections.Generic;
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNet.Server.Kestrel.Http
{
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()
{
return new Enumerator(this);
@ -19,6 +30,24 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
return GetEnumerator();
}
public void CopyTo(ref MemoryPoolIterator2 output)
{
CopyToFast(ref output);
if (MaybeUnknown != null)
{
foreach (var kv in MaybeUnknown)
{
foreach (var value in kv.Value)
{
output.CopyFrom(_CRLF, 0, 2);
output.CopyFromAscii(kv.Key);
output.CopyFrom(_colonSpace, 0, 2);
output.CopyFromAscii(value);
}
}
}
}
public partial struct Enumerator : IEnumerator<KeyValuePair<string, StringValues>>
{
private FrameResponseHeaders _collection;

View File

@ -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);
}
}
}
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -2,11 +2,65 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Globalization;
using System.Text;
namespace Microsoft.AspNet.Server.Kestrel.Http
{
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)
{
if (string.IsNullOrEmpty(reasonPhrase))
@ -16,6 +70,123 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
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)
{
switch (statusCode)
@ -242,4 +413,4 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
}
}
}
}
}

View File

@ -30,6 +30,12 @@ namespace Microsoft.AspNet.Server.Kestrel.Infrastructure
/// 4096 - 64 gives you a blockLength of 4032 usable bytes per block.
/// </summary>
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>
/// 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,
/// 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>
public MemoryPoolBlock2 Lease(int minimumSize = _blockLength)
public MemoryPoolBlock2 Lease(int minimumSize = MaxPooledBlockLength)
{
if (minimumSize > _blockLength)
{

View File

@ -572,5 +572,109 @@ namespace Microsoft.AspNet.Server.Kestrel.Infrastructure
return new MemoryPoolIterator2(block, blockIndex);
}
public void CopyFrom(byte[] data)
{
CopyFrom(data, 0, data.Length);
}
public void CopyFrom(byte[] data, int offset, int count)
{
Debug.Assert(_block.Next == null);
Debug.Assert(_block.End == _index);
var block = _block;
var sourceData = data;
var sourceStart = offset;
var sourceEnd = offset + count;
var targetData = block.Array;
var targetStart = block.End;
var targetEnd = block.Data.Offset + block.Data.Count;
while (true)
{
// actual count to copy is remaining data, or unused trailing space in the current block, whichever is smaller
var copyCount = Math.Min(sourceEnd - sourceStart, targetEnd - targetStart);
Buffer.BlockCopy(sourceData, sourceStart, targetData, targetStart, copyCount);
sourceStart += copyCount;
targetStart += copyCount;
// if this means all source data has been copied
if (sourceStart == sourceEnd)
{
// increase occupied space in the block, and adjust iterator at start of unused trailing space
block.End = targetStart;
_block = block;
_index = targetStart;
return;
}
// otherwise another block needs to be allocated to follow this one
block.Next = block.Pool.Lease();
block = block.Next;
targetData = block.Array;
targetStart = block.End;
targetEnd = block.Data.Offset + block.Data.Count;
}
}
public unsafe void CopyFromAscii(string data)
{
Debug.Assert(_block.Next == null);
Debug.Assert(_block.End == _index);
var block = _block;
var inputLength = data.Length;
var inputLengthMinusSpan = inputLength - 3;
fixed (char* pData = data)
{
var input = pData;
var inputEnd = pData + data.Length;
var blockRemaining = block.Data.Offset + block.Data.Count - block.End;
var blockRemainingMinusSpan = blockRemaining - 3;
while (input < inputEnd)
{
if (blockRemaining == 0)
{
block.Next = block.Pool.Lease();
block = block.Next;
blockRemaining = block.Data.Count;
blockRemainingMinusSpan = blockRemaining - 3;
}
fixed (byte* pOutput = block.Data.Array)
{
var output = pOutput + block.End;
var copied = 0;
for (; copied < inputLengthMinusSpan && copied < blockRemainingMinusSpan; copied += 4)
{
*(output) = (byte)*(input);
*(output + 1) = (byte)*(input + 1);
*(output + 2) = (byte)*(input + 2);
*(output + 3) = (byte)*(input + 3);
output += 4;
input += 4;
blockRemainingMinusSpan -= 4;
}
for (; copied < inputLength && copied < blockRemaining; copied++)
{
*(output++) = (byte)*(input++);
blockRemaining--;
}
block.End += copied;
_block = block;
_index = block.End;
}
}
}
}
}
}

View File

@ -13,13 +13,11 @@ namespace Microsoft.AspNet.Server.Kestrel
{
public ServiceContext()
{
Memory = new MemoryPool();
}
public ServiceContext(ServiceContext context)
{
AppLifetime = context.AppLifetime;
Memory = context.Memory;
Log = context.Log;
HttpContextFactory = context.HttpContextFactory;
DateHeaderValueManager = context.DateHeaderValueManager;
@ -29,8 +27,6 @@ namespace Microsoft.AspNet.Server.Kestrel
public IApplicationLifetime AppLifetime { get; set; }
public IMemoryPool Memory { get; set; }
public IKestrelTrace Log { get; set; }
public IHttpContextFactory HttpContextFactory { get; set; }

View File

@ -171,8 +171,8 @@ namespace Microsoft.AspNet.Server.KestrelTests
"Content-Length: 0",
"",
"HTTP/1.1 200 OK",
"Content-Length: 7",
"Connection: close",
"Content-Length: 7",
"",
"Goodbye");
}
@ -241,8 +241,8 @@ namespace Microsoft.AspNet.Server.KestrelTests
"Goodbye");
await connection.Receive(
"HTTP/1.0 200 OK",
"Content-Length: 0",
"Connection: keep-alive",
"Content-Length: 0",
"\r\n");
await connection.ReceiveEnd(
"HTTP/1.0 200 OK",
@ -267,14 +267,14 @@ namespace Microsoft.AspNet.Server.KestrelTests
"Connection: keep-alive",
"",
"POST / HTTP/1.0",
"Connection: keep-alive",
"Content-Length: 7",
"Connection: keep-alive",
"",
"Goodbye");
await connection.Receive(
"HTTP/1.0 200 OK",
"Content-Length: 0",
"Connection: keep-alive",
"Content-Length: 0",
"\r\n");
await connection.ReceiveEnd(
"HTTP/1.0 200 OK",
@ -295,16 +295,16 @@ namespace Microsoft.AspNet.Server.KestrelTests
{
await connection.SendEnd(
"POST / HTTP/1.0",
"Connection: keep-alive",
"Content-Length: 11",
"Connection: keep-alive",
"",
"Hello WorldPOST / HTTP/1.0",
"",
"Goodbye");
await connection.Receive(
"HTTP/1.0 200 OK",
"Content-Length: 11",
"Connection: keep-alive",
"Content-Length: 11",
"",
"Hello World");
await connection.ReceiveEnd(
@ -336,8 +336,8 @@ namespace Microsoft.AspNet.Server.KestrelTests
"Goodbye");
await connection.Receive(
"HTTP/1.0 200 OK",
"Content-Length: 11",
"Connection: keep-alive",
"Content-Length: 11",
"",
"Hello World");
await connection.ReceiveEnd(
@ -361,15 +361,15 @@ namespace Microsoft.AspNet.Server.KestrelTests
await connection.Send(
"POST / HTTP/1.1",
"Expect: 100-continue",
"Content-Length: 11",
"Connection: close",
"Content-Length: 11",
"\r\n");
await connection.Receive("HTTP/1.1 100 Continue", "\r\n");
await connection.SendEnd("Hello World");
await connection.Receive(
"HTTP/1.1 200 OK",
"Content-Length: 11",
"Connection: close",
"Content-Length: 11",
"",
"Hello World");
}
@ -422,8 +422,8 @@ namespace Microsoft.AspNet.Server.KestrelTests
"Content-Length: 0",
"",
"HTTP/1.0 200 OK",
"Content-Length: 0",
"Connection: keep-alive",
"Content-Length: 0",
"",
"");
}
@ -584,11 +584,12 @@ namespace Microsoft.AspNet.Server.KestrelTests
"",
"HTTP/1.1 500 Internal Server Error",
"");
await connection.Receive("Connection: close",
"");
await connection.ReceiveStartsWith("Date:");
await connection.ReceiveEnd(
"Content-Length: 0",
"Server: Kestrel",
"Connection: close",
"",
"");
@ -812,12 +813,12 @@ namespace Microsoft.AspNet.Server.KestrelTests
"Server: Kestrel",
"",
"HTTP/1.1 500 Internal Server Error",
"Connection: close",
"");
await connection.ReceiveStartsWith("Date:");
await connection.ReceiveEnd(
"Content-Length: 0",
"Server: Kestrel",
"Connection: close",
"",
"");

View File

@ -13,12 +13,10 @@ namespace Microsoft.AspNet.Server.KestrelTests
{
public TestInput()
{
var memory = new MemoryPool();
var memory2 = new MemoryPool2();
FrameContext = new FrameContext
{
SocketInput = new SocketInput(memory2),
Memory = memory,
ConnectionControl = this,
FrameControl = this
};

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Dnx.Compilation.CSharp;
using System.Text;
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)
{
return values.Select(formatter).Aggregate((a, b) => a + b);
return values.Any() ? values.Select(formatter).Aggregate((a, b) => a + b) : "";
}
class KnownHeader
@ -19,10 +20,14 @@ namespace Microsoft.AspNet.Server.Kestrel.GeneratedCode
public string Name { get; set; }
public int Index { get; set; }
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 SetBit() => $"_bits |= {1L << Index}L";
public string ClearBit() => $"_bits &= ~{1L << Index}L";
public string EqualIgnoreCaseBytes()
{
var result = "";
@ -54,7 +59,6 @@ namespace Microsoft.AspNet.Server.Kestrel.GeneratedCode
}
return $"({result})";
}
protected string Term(string name, int offset, int count, string array, string suffix)
{
ulong mask = 0;
@ -69,13 +73,11 @@ namespace Microsoft.AspNet.Server.Kestrel.GeneratedCode
return $"(({array}[{offset / count}] & {mask}{suffix}) == {comp}{suffix})";
}
}
public virtual void BeforeCompile(BeforeCompileContext context)
{
var syntaxTree = Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.ParseText(GeneratedFile());
context.Compilation = context.Compilation.AddSyntaxTrees(syntaxTree);
}
public static string GeneratedFile()
{
var commonHeaders = new[]
@ -128,8 +130,15 @@ namespace Microsoft.AspNet.Server.Kestrel.GeneratedCode
{
Name = header,
Index = index
});
}).ToArray();
var enhancedHeaders = new[]
{
"Connection",
"Server",
"Date",
"Transfer-Encoding",
"Content-Length",
};
var responseHeaders = commonHeaders.Concat(new[]
{
"Accept-Ranges",
@ -145,38 +154,60 @@ namespace Microsoft.AspNet.Server.Kestrel.GeneratedCode
}).Select((header, index) => new KnownHeader
{
Name = header,
Index = index
});
Index = index,
EnhancedSetter = enhancedHeaders.Contains(header)
}).ToArray();
var loops = new[]
{
new
{
Headers = requestHeaders,
HeadersByLength = requestHeaders.GroupBy(x => x.Name.Length),
ClassName = "FrameRequestHeaders"
ClassName = "FrameRequestHeaders",
Bytes = default(byte[])
},
new
{
Headers = responseHeaders,
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 $@"
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNet.Server.Kestrel.Http
{{
{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;
{Each(loop.Headers, header => @"
private StringValues _" + header.Identifier + ";")}
{Each(loop.Headers.Where(header => header.EnhancedSetter), header => @"
private byte[] _raw" + header.Identifier + ";")}
{Each(loop.Headers, header => $@"
public StringValues Header{header.Identifier}
{{
@ -187,22 +218,28 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
set
{{
{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()
{{
return BitCount(_bits) + (MaybeUnknown?.Count ?? 0);
}}
protected override StringValues GetValueFast(string key)
{{
switch(key.Length)
{{{Each(loop.HeadersByLength, byLength => $@"
case {byLength.Key}:
{{{Each(byLength, header => $@"
if (""{header.Name}"".Equals(key, StringComparison.OrdinalIgnoreCase))
if (""{header.Name}"".Equals(key, StringComparison.OrdinalIgnoreCase))
{{
if ({header.TestBit()})
{{
@ -215,14 +252,13 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
}}
")}}}
break;
")}}}
")}}}
if (MaybeUnknown == null)
{{
throw new System.Collections.Generic.KeyNotFoundException();
}}
return MaybeUnknown[key];
}}
protected override bool TryGetValueFast(string key, out StringValues value)
{{
switch(key.Length)
@ -244,11 +280,10 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
}}
")}}}
break;
")}}}
")}}}
value = StringValues.Empty;
return MaybeUnknown?.TryGetValue(key, out value) ?? false;
}}
protected override void SetValueFast(string key, StringValues value)
{{
switch(key.Length)
@ -258,29 +293,30 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
if (""{header.Name}"".Equals(key, StringComparison.OrdinalIgnoreCase))
{{
{header.SetBit()};
_{header.Identifier} = value;
_{header.Identifier} = value;{(header.EnhancedSetter == false ? "" : $@"
_raw{header.Identifier} = null;")}
return;
}}
")}}}
break;
")}}}
")}}}
Unknown[key] = value;
}}
protected override void AddValueFast(string key, StringValues value)
{{
switch(key.Length)
{{{Each(loop.HeadersByLength, byLength => $@"
case {byLength.Key}:
{{{Each(byLength, header => $@"
if (""{header.Name}"".Equals(key, StringComparison.OrdinalIgnoreCase))
if (""{header.Name}"".Equals(key, StringComparison.OrdinalIgnoreCase))
{{
if ({header.TestBit()})
{{
throw new ArgumentException(""An item with the same key has already been added."");
}}
{header.SetBit()};
_{header.Identifier} = value;
_{header.Identifier} = value;{(header.EnhancedSetter == false ? "" : $@"
_raw{header.Identifier} = null;")}
return;
}}
")}}}
@ -288,7 +324,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
")}}}
Unknown.Add(key, value);
}}
protected override bool RemoveFast(string key)
{{
switch(key.Length)
@ -300,7 +335,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
if ({header.TestBit()})
{{
{header.ClearBit()};
_{header.Identifier} = StringValues.Empty;
_{header.Identifier} = StringValues.Empty;{(header.EnhancedSetter == false ? "" : $@"
_raw{header.Identifier} = null;")}
return true;
}}
else
@ -313,12 +349,13 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
")}}}
return MaybeUnknown?.Remove(key) ?? false;
}}
protected override void ClearFast()
{{
_bits = 0;
{Each(loop.Headers, header => $@"
_{header.Identifier} = StringValues.Empty;")}
{Each(loop.Headers.Where(header => header.EnhancedSetter), header => $@"
_raw{header.Identifier} = null;")}
MaybeUnknown?.Clear();
}}
@ -328,7 +365,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{{
throw new ArgumentException();
}}
{Each(loop.Headers, header => $@"
if ({header.TestBit()})
{{
@ -343,7 +379,24 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
")}
((ICollection<KeyValuePair<string, StringValues>>)MaybeUnknown)?.CopyTo(array, arrayIndex);
}}
{(loop.ClassName == "FrameResponseHeaders" ? $@"
protected void CopyToFast(ref MemoryPoolIterator2 output)
{{
{Each(loop.Headers, header => $@"
if ({header.TestBit()})
{{ {(header.EnhancedSetter == false ? "" : $@"
if (_raw{header.Identifier} != null)
{{
output.CopyFromAscii(_raw{header.Identifier}, 0, _raw{header.Identifier}.Length);
}} else ")}
foreach(var value in _{header.Identifier})
{{
output.CopyFromAscii(_headerBytes, {header.BytesOffset}, {header.BytesCount});
output.CopyFromAscii(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;
@ -360,7 +413,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
else
{{
{header.SetBit()};
_{header.Identifier} = new StringValues(value);
_{header.Identifier} = new StringValues(value);{(header.EnhancedSetter == false ? "" : $@"
_raw{header.Identifier} = null;")}
}}
return;
}}
@ -372,7 +426,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
Unknown.TryGetValue(key, out existing);
Unknown[key] = AppendValue(existing, value);
}}
public partial struct Enumerator
{{
public bool MoveNext()
@ -409,9 +462,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
")}}}
";
}
public virtual void AfterCompile(AfterCompileContext context)
{
}
}
}
}