Skip to content
This repository was archived by the owner on Dec 18, 2018. It is now read-only.

Commit 5befd5c

Browse files
committed
Precomputed header bytes
1 parent 2572256 commit 5befd5c

File tree

13 files changed

+1231
-800
lines changed

13 files changed

+1231
-800
lines changed

src/Microsoft.AspNet.Server.Kestrel/Http/DateHeaderValueManager.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Text;
56
using System.Threading;
67
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
78

@@ -17,6 +18,9 @@ public class DateHeaderValueManager : IDisposable
1718
private readonly TimeSpan _timerInterval;
1819

1920
private volatile string _dateValue;
21+
private volatile bool _activeDateBytes;
22+
private readonly byte[] _dateBytes0 = Encoding.ASCII.GetBytes("\r\nDate: DDD, dd mmm yyyy hh:mm:ss GMT");
23+
private readonly byte[] _dateBytes1 = Encoding.ASCII.GetBytes("\r\nDate: DDD, dd mmm yyyy hh:mm:ss GMT");
2024
private object _timerLocker = new object();
2125
private bool _isDisposed = false;
2226
private bool _hadRequestsSinceLastTimerTick = false;
@@ -62,6 +66,12 @@ public virtual string GetDateHeaderValue()
6266
return _dateValue ?? _systemClock.UtcNow.ToString(Constants.RFC1123DateFormat);
6367
}
6468

69+
public byte[] GetDateHeaderValueBytes()
70+
{
71+
PumpTimer();
72+
return _activeDateBytes ? _dateBytes0 : _dateBytes1;
73+
}
74+
6575
/// <summary>
6676
/// Releases all resources used by the current instance of <see cref="DateHeaderValueManager"/>.
6777
/// </summary>
@@ -92,6 +102,8 @@ private void PumpTimer()
92102
// here as the timer won't fire until the timer interval has passed and we want a value assigned
93103
// inline now to serve requests that occur in the meantime.
94104
_dateValue = _systemClock.UtcNow.ToString(Constants.RFC1123DateFormat);
105+
Encoding.ASCII.GetBytes(_dateValue, 0, _dateValue.Length, !_activeDateBytes ? _dateBytes0 : _dateBytes1, "\r\nDate: ".Length);
106+
_activeDateBytes = !_activeDateBytes;
95107
_dateValueTimer = new Timer(UpdateDateValue, state: null, dueTime: _timerInterval, period: _timerInterval);
96108
}
97109
}
@@ -105,6 +117,8 @@ private void UpdateDateValue(object state)
105117

106118
// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.18 for required format of Date header
107119
_dateValue = now.ToString(Constants.RFC1123DateFormat);
120+
Encoding.ASCII.GetBytes(_dateValue, 0, _dateValue.Length, !_activeDateBytes ? _dateBytes0 : _dateBytes1, "\r\nDate: ".Length);
121+
_activeDateBytes = !_activeDateBytes;
108122

109123
if (_hadRequestsSinceLastTimerTick)
110124
{

src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs

Lines changed: 123 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,17 @@ public partial class Frame : FrameContext, IFrameControl
2929
private static readonly ArraySegment<byte> _emptyData = new ArraySegment<byte>(new byte[0]);
3030
private static readonly byte[] _hex = Encoding.ASCII.GetBytes("0123456789abcdef");
3131

32+
private static readonly byte[] _bytesConnectionClose = Encoding.ASCII.GetBytes("\r\nConnection: close");
33+
private static readonly byte[] _bytesConnectionKeepAlive = Encoding.ASCII.GetBytes("\r\nConnection: keep-alive");
34+
private static readonly byte[] _bytesTransferEncodingChunked = Encoding.ASCII.GetBytes("\r\nTransfer-Encoding: chunked");
35+
private static readonly byte[] _bytesHttpVersion1_0 = Encoding.ASCII.GetBytes("HTTP/1.0 ");
36+
private static readonly byte[] _bytesHttpVersion1_1 = Encoding.ASCII.GetBytes("HTTP/1.1 ");
37+
private static readonly byte[] _bytesContentLengthZero = Encoding.ASCII.GetBytes("\r\nContent-Length: 0");
38+
private static readonly byte[] _bytesSpace = Encoding.ASCII.GetBytes(" ");
39+
private static readonly byte[] _bytesServer = Encoding.ASCII.GetBytes("\r\nServer: Kestrel");
40+
private static readonly byte[] _bytesDate = Encoding.ASCII.GetBytes("Date: ");
41+
private static readonly byte[] _bytesEndHeaders = Encoding.ASCII.GetBytes("\r\n\r\n");
42+
3243
private readonly object _onStartingSync = new Object();
3344
private readonly object _onCompletedSync = new Object();
3445
private readonly FrameRequestHeaders _requestHeaders = new FrameRequestHeaders();
@@ -53,6 +64,8 @@ public partial class Frame : FrameContext, IFrameControl
5364
private bool _autoChunk;
5465
private Exception _applicationException;
5566

67+
private HttpVersionType _httpVersion;
68+
5669
private readonly IPEndPoint _localEndPoint;
5770
private readonly IPEndPoint _remoteEndPoint;
5871
private readonly Action<IFeatureCollection> _prepareRequest;
@@ -81,7 +94,37 @@ public Frame(ConnectionContext context,
8194
public string RequestUri { get; set; }
8295
public string Path { get; set; }
8396
public string QueryString { get; set; }
84-
public string HttpVersion { get; set; }
97+
public string HttpVersion
98+
{
99+
get
100+
{
101+
if (_httpVersion == HttpVersionType.Http1_1)
102+
{
103+
return "HTTP/1.1";
104+
}
105+
if (_httpVersion == HttpVersionType.Http1_0)
106+
{
107+
return "HTTP/1.0";
108+
}
109+
return "";
110+
}
111+
set
112+
{
113+
if (value == "HTTP/1.1")
114+
{
115+
_httpVersion = HttpVersionType.Http1_1;
116+
}
117+
else if (value == "HTTP/1.0")
118+
{
119+
_httpVersion = HttpVersionType.Http1_0;
120+
}
121+
else
122+
{
123+
_httpVersion = HttpVersionType.Unknown;
124+
}
125+
}
126+
}
127+
85128
public IHeaderDictionary RequestHeaders { get; set; }
86129
public Stream RequestBody { get; set; }
87130

@@ -118,7 +161,7 @@ public void Reset()
118161
RequestUri = null;
119162
Path = null;
120163
QueryString = null;
121-
HttpVersion = null;
164+
_httpVersion = HttpVersionType.Unknown;
122165
RequestHeaders = _requestHeaders;
123166
RequestBody = null;
124167
StatusCode = 200;
@@ -151,8 +194,12 @@ public void Reset()
151194
public void ResetResponseHeaders()
152195
{
153196
_responseHeaders.Reset();
154-
_responseHeaders.HeaderServer = "Kestrel";
155-
_responseHeaders.HeaderDate = DateHeaderValueManager.GetDateHeaderValue();
197+
_responseHeaders.SetRawDate(
198+
DateHeaderValueManager.GetDateHeaderValue(),
199+
DateHeaderValueManager.GetDateHeaderValueBytes());
200+
_responseHeaders.SetRawServer(
201+
"Kestrel",
202+
_bytesServer);
156203
}
157204

158205
/// <summary>
@@ -502,7 +549,7 @@ public void ProduceContinue()
502549
if (_responseStarted) return;
503550

504551
StringValues expect;
505-
if (HttpVersion.Equals("HTTP/1.1") &&
552+
if (_httpVersion == HttpVersionType.Http1_1 &&
506553
RequestHeaders.TryGetValue("Expect", out expect) &&
507554
(expect.FirstOrDefault() ?? "").Equals("100-continue", StringComparison.OrdinalIgnoreCase))
508555
{
@@ -526,19 +573,14 @@ public async Task ProduceStartAndFireOnStarting(bool immediate = true)
526573
await ProduceStart(immediate, appCompleted: false);
527574
}
528575

529-
private async Task ProduceStart(bool immediate, bool appCompleted)
576+
private Task ProduceStart(bool immediate, bool appCompleted)
530577
{
531-
if (_responseStarted) return;
578+
if (_responseStarted) return TaskUtilities.CompletedTask;
532579
_responseStarted = true;
533580

534-
var status = ReasonPhrases.ToStatus(StatusCode, ReasonPhrase);
535-
536-
var responseHeader = CreateResponseHeader(status, appCompleted);
581+
var statusBytes = ReasonPhrases.ToStatusBytes(StatusCode, ReasonPhrase);
537582

538-
using (responseHeader.Item2)
539-
{
540-
await SocketOutput.WriteAsync(responseHeader.Item1, immediate: immediate);
541-
}
583+
return CreateResponseHeader(statusBytes, appCompleted, immediate);
542584
}
543585

544586
private async Task ProduceEnd()
@@ -557,7 +599,7 @@ private async Task ProduceEnd()
557599
ReasonPhrase = null;
558600

559601
ResetResponseHeaders();
560-
_responseHeaders.HeaderContentLength = "0";
602+
_responseHeaders.SetRawContentLength("0", _bytesContentLengthZero);
561603
}
562604
}
563605

@@ -576,58 +618,26 @@ private async Task ProduceEnd()
576618
}
577619
}
578620

579-
private Tuple<ArraySegment<byte>, IDisposable> CreateResponseHeader(
580-
string status,
581-
bool appCompleted)
621+
private Task CreateResponseHeader(
622+
byte[] statusBytes,
623+
bool appCompleted,
624+
bool immediate)
582625
{
583-
var writer = new MemoryPoolTextWriter(Memory);
584-
writer.Write(HttpVersion);
585-
writer.Write(' ');
586-
writer.Write(status);
587-
writer.Write('\r');
588-
writer.Write('\n');
589-
590-
var hasConnection = false;
591-
var hasTransferEncoding = false;
592-
var hasContentLength = false;
593-
594-
foreach (var header in _responseHeaders)
626+
var memoryBlock = Memory2.Lease();
627+
var begin = memoryBlock.GetIterator();
628+
var end = begin;
629+
if (_keepAlive)
595630
{
596-
var isConnection = false;
597-
if (!hasConnection &&
598-
string.Equals(header.Key, "Connection", StringComparison.OrdinalIgnoreCase))
599-
{
600-
hasConnection = isConnection = true;
601-
}
602-
else if (!hasTransferEncoding &&
603-
string.Equals(header.Key, "Transfer-Encoding", StringComparison.OrdinalIgnoreCase))
631+
foreach (var connectionValue in _responseHeaders.HeaderConnection)
604632
{
605-
hasTransferEncoding = true;
606-
}
607-
else if (!hasContentLength &&
608-
string.Equals(header.Key, "Content-Length", StringComparison.OrdinalIgnoreCase))
609-
{
610-
hasContentLength = true;
611-
}
612-
613-
foreach (var value in header.Value)
614-
{
615-
writer.Write(header.Key);
616-
writer.Write(':');
617-
writer.Write(' ');
618-
writer.Write(value);
619-
writer.Write('\r');
620-
writer.Write('\n');
621-
622-
if (isConnection && value.IndexOf("close", StringComparison.OrdinalIgnoreCase) != -1)
633+
if (connectionValue.IndexOf("close", StringComparison.OrdinalIgnoreCase) != -1)
623634
{
624635
_keepAlive = false;
625636
}
626637
}
627-
628638
}
629639

630-
if (_keepAlive && !hasTransferEncoding && !hasContentLength)
640+
if (_keepAlive && !_responseHeaders.HasTransferEncoding && !_responseHeaders.HasContentLength)
631641
{
632642
if (appCompleted)
633643
{
@@ -637,15 +647,15 @@ private Tuple<ArraySegment<byte>, IDisposable> CreateResponseHeader(
637647
{
638648
// Since the app has completed and we are only now generating
639649
// the headers we can safely set the Content-Length to 0.
640-
writer.Write("Content-Length: 0\r\n");
650+
_responseHeaders.SetRawContentLength("0", _bytesContentLengthZero);
641651
}
642652
}
643653
else
644654
{
645-
if (HttpVersion == "HTTP/1.1")
655+
if (_httpVersion == HttpVersionType.Http1_1)
646656
{
647657
_autoChunk = true;
648-
writer.Write("Transfer-Encoding: chunked\r\n");
658+
_responseHeaders.SetRawTransferEncoding("chunked", _bytesTransferEncodingChunked);
649659
}
650660
else
651661
{
@@ -654,21 +664,58 @@ private Tuple<ArraySegment<byte>, IDisposable> CreateResponseHeader(
654664
}
655665
}
656666

657-
if (_keepAlive == false && hasConnection == false && HttpVersion == "HTTP/1.1")
667+
if (_keepAlive == false && _responseHeaders.HasConnection == false && _httpVersion == HttpVersionType.Http1_1)
658668
{
659-
writer.Write("Connection: close\r\n\r\n");
669+
_responseHeaders.SetRawConnection("close", _bytesConnectionClose);
660670
}
661-
else if (_keepAlive && hasConnection == false && HttpVersion == "HTTP/1.0")
671+
else if (_keepAlive && _responseHeaders.HasConnection == false && _httpVersion == HttpVersionType.Http1_0)
662672
{
663-
writer.Write("Connection: keep-alive\r\n\r\n");
673+
_responseHeaders.SetRawConnection("keep-alive", _bytesConnectionKeepAlive);
674+
}
675+
676+
end.CopyFrom(_httpVersion == HttpVersionType.Http1_1 ? _bytesHttpVersion1_1 : _bytesHttpVersion1_0);
677+
end.CopyFrom(statusBytes);
678+
_responseHeaders.CopyTo(ref end);
679+
end.CopyFrom(_bytesEndHeaders, 0, _bytesEndHeaders.Length);
680+
681+
// TODO: change this to SocketOutput.ProduceStart/ProduceComplete once that change is made
682+
var scan = begin.Block;
683+
while (scan.Next != null)
684+
{
685+
if (scan.Start != scan.End)
686+
{
687+
SocketOutput.WriteAsync(
688+
new ArraySegment<byte>(scan.Array, scan.Start, scan.End - scan.Start),
689+
false);
690+
}
691+
var next = scan.Next;
692+
Memory2.Return(scan);
693+
scan = next;
694+
}
695+
var writeTask = SocketOutput.WriteAsync(
696+
new ArraySegment<byte>(scan.Array, scan.Start, scan.End - scan.Start),
697+
immediate);
698+
699+
if (writeTask.IsCompleted)
700+
{
701+
Memory2.Return(scan);
702+
return TaskUtilities.CompletedTask;
664703
}
665704
else
666705
{
667-
writer.Write('\r');
668-
writer.Write('\n');
706+
return writeTask.ContinueWith(
707+
(t, o) =>
708+
{
709+
var mb = (MemoryPoolBlock2)o;
710+
mb.Pool.Return(mb);
711+
712+
if (t.IsFaulted)
713+
{
714+
throw t.Exception;
715+
}
716+
},
717+
scan);
669718
}
670-
writer.Flush();
671-
return new Tuple<ArraySegment<byte>, IDisposable>(writer.Buffer, writer);
672719
}
673720

674721
private bool TakeStartLine(SocketInput input)
@@ -877,5 +924,12 @@ private void ReportApplicationError(Exception ex)
877924
_applicationException = ex;
878925
Log.ApplicationError(ex);
879926
}
927+
928+
private enum HttpVersionType
929+
{
930+
Unknown = -1,
931+
Http1_0 = 0,
932+
Http1_1 = 1
933+
}
880934
}
881935
}

0 commit comments

Comments
 (0)