diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Http/DateHeaderValueManager.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Http/DateHeaderValueManager.cs index 45e91194d6..d7022ab2dd 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Http/DateHeaderValueManager.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Http/DateHeaderValueManager.cs @@ -13,15 +13,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http /// public class DateHeaderValueManager : IDisposable { + private static readonly byte[] _datePreambleBytes = Encoding.ASCII.GetBytes("\r\nDate: "); + private readonly ISystemClock _systemClock; private readonly TimeSpan _timeWithoutRequestsUntilIdle; private readonly TimeSpan _timerInterval; + private readonly object _timerLocker = new object(); + + private DateHeaderValues _dateValues; - 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 volatile bool _isDisposed = false; private volatile bool _hadRequestsSinceLastTimerTick = false; private Timer _dateValueTimer; @@ -55,19 +55,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http /// Returns a value representing the current server date/time for use in the HTTP "Date" response header /// in accordance with http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.18 /// - /// The value. - public virtual string GetDateHeaderValue() + /// The value in string and byte[] format. + public DateHeaderValues GetDateHeaderValues() { - _hadRequestsSinceLastTimerTick = true; - PrepareDateValues(); - return _dateValue; - } + if (!_hadRequestsSinceLastTimerTick) + { + PrepareDateValues(); + } - public byte[] GetDateHeaderValueBytes() - { - _hadRequestsSinceLastTimerTick = true; - PrepareDateValues(); - return _activeDateBytes ? _dateBytes0 : _dateBytes1; + return _dateValues; } /// @@ -78,6 +74,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http if (!_isDisposed) { _isDisposed = true; + _hadRequestsSinceLastTimerTick = false; lock (_timerLocker) { @@ -125,6 +122,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http { _timerIsRunning = false; _dateValueTimer.Change(Timeout.Infinite, Timeout.Infinite); + _hadRequestsSinceLastTimerTick = false; } } } @@ -158,16 +156,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http /// private void PrepareDateValues() { - if (_isDisposed) + _hadRequestsSinceLastTimerTick = !_isDisposed; + if (!_timerIsRunning) { - SetDateValues(_systemClock.UtcNow); - } - else - { - if (!_timerIsRunning) - { - StartTimer(); - } + StartTimer(); } } @@ -178,9 +170,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http private void SetDateValues(DateTimeOffset value) { // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.18 for required format of Date header - _dateValue = value.ToString(Constants.RFC1123DateFormat); - Encoding.ASCII.GetBytes(_dateValue, 0, _dateValue.Length, !_activeDateBytes ? _dateBytes0 : _dateBytes1, "\r\nDate: ".Length); - _activeDateBytes = !_activeDateBytes; + var dateValue = value.ToString(Constants.RFC1123DateFormat); + var dateBytes = new byte[_datePreambleBytes.Length + dateValue.Length]; + Buffer.BlockCopy(_datePreambleBytes, 0, dateBytes, 0, _datePreambleBytes.Length); + Encoding.ASCII.GetBytes(dateValue, 0, dateValue.Length, dateBytes, _datePreambleBytes.Length); + + var dateValues = new DateHeaderValues() + { + Bytes = dateBytes, + String = dateValue + }; + Volatile.Write(ref _dateValues, dateValues); + } + + public class DateHeaderValues + { + public byte[] Bytes; + public string String; } } } diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Http/Frame.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Http/Frame.cs index 4ed675767c..bbae130ad0 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Http/Frame.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Http/Frame.cs @@ -589,12 +589,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http var responseHeaders = _frameHeaders.ResponseHeaders; responseHeaders.Reset(); - responseHeaders.SetRawDate( - DateHeaderValueManager.GetDateHeaderValue(), - DateHeaderValueManager.GetDateHeaderValueBytes()); - responseHeaders.SetRawServer( - "Kestrel", - Headers.BytesServer); + var dateHeaderValues = DateHeaderValueManager.GetDateHeaderValues(); + responseHeaders.SetRawDate(dateHeaderValues.String, dateHeaderValues.Bytes); + responseHeaders.SetRawServer("Kestrel", Headers.BytesServer); responseHeaders.SetRawContentLength("0", _bytesContentLengthZero); ResponseHeaders = responseHeaders; diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Infrastructure/HttpComponentFactory.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Infrastructure/HttpComponentFactory.cs index cacd005b55..2669471cd9 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Infrastructure/HttpComponentFactory.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Infrastructure/HttpComponentFactory.cs @@ -78,9 +78,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure public void Initialize(DateHeaderValueManager dateValueManager) { - ResponseHeaders.SetRawDate( - dateValueManager.GetDateHeaderValue(), - dateValueManager.GetDateHeaderValueBytes()); + var dateHeaderValues = dateValueManager.GetDateHeaderValues(); + ResponseHeaders.SetRawDate(dateHeaderValues.String, dateHeaderValues.Bytes); ResponseHeaders.SetRawServer("Kestrel", BytesServer); } diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/DateHeaderValueManagerTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/DateHeaderValueManagerTests.cs index d357ece6ae..0c221cba52 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/DateHeaderValueManagerTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/DateHeaderValueManagerTests.cs @@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests try { - result = dateHeaderValueManager.GetDateHeaderValue(); + result = dateHeaderValueManager.GetDateHeaderValues().String; } finally { @@ -54,9 +54,9 @@ namespace Microsoft.AspNetCore.Server.KestrelTests try { - result1 = dateHeaderValueManager.GetDateHeaderValue(); + result1 = dateHeaderValueManager.GetDateHeaderValues().String; systemClock.UtcNow = future; - result2 = dateHeaderValueManager.GetDateHeaderValue(); + result2 = dateHeaderValueManager.GetDateHeaderValues().String; } finally { @@ -85,11 +85,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests try { - result1 = dateHeaderValueManager.GetDateHeaderValue(); + result1 = dateHeaderValueManager.GetDateHeaderValues().String; systemClock.UtcNow = future; // Wait for longer than the idle timeout to ensure the timer is stopped await Task.Delay(TimeSpan.FromSeconds(1)); - result2 = dateHeaderValueManager.GetDateHeaderValue(); + result2 = dateHeaderValueManager.GetDateHeaderValues().String; } finally { @@ -114,10 +114,10 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var timerInterval = TimeSpan.FromSeconds(10); var dateHeaderValueManager = new DateHeaderValueManager(systemClock, timeWithoutRequestsUntilIdle, timerInterval); - var result1 = dateHeaderValueManager.GetDateHeaderValue(); + var result1 = dateHeaderValueManager.GetDateHeaderValues().String; dateHeaderValueManager.Dispose(); systemClock.UtcNow = future; - var result2 = dateHeaderValueManager.GetDateHeaderValue(); + var result2 = dateHeaderValueManager.GetDateHeaderValues().String; Assert.Equal(now.ToString(Constants.RFC1123DateFormat), result1); Assert.Equal(future.ToString(Constants.RFC1123DateFormat), result2); diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/TestDateHeaderValueManager.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/TestDateHeaderValueManager.cs deleted file mode 100644 index f558080e9d..0000000000 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/TestDateHeaderValueManager.cs +++ /dev/null @@ -1,16 +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 Microsoft.AspNetCore.Server.Kestrel.Http; - -namespace Microsoft.AspNetCore.Server.KestrelTests -{ - public class TestDateHeaderValueManager : DateHeaderValueManager - { - public override string GetDateHeaderValue() - { - return DateTimeOffset.UtcNow.ToString("r"); - } - } -} diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/TestServiceContext.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/TestServiceContext.cs index 7cfe7df885..0457345450 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/TestServiceContext.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/TestServiceContext.cs @@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests AppLifetime = new LifetimeNotImplemented(); Log = new TestKestrelTrace(); ThreadPool = new LoggingThreadPool(Log); - DateHeaderValueManager = new TestDateHeaderValueManager(); + DateHeaderValueManager = new DateHeaderValueManager(); ServerOptions = new KestrelServerOptions(); ServerOptions.ShutdownTimeout = TimeSpan.FromSeconds(5);