aspnetcore/src/Microsoft.AspNetCore.Server.../Http/DateHeaderValueManager.cs

185 lines
6.3 KiB
C#

// 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.Text;
using System.Threading;
using Microsoft.AspNetCore.Server.Kestrel.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Http
{
/// <summary>
/// Manages the generation of the date header value.
/// </summary>
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 bool _isDisposed = false;
private volatile bool _hadRequestsSinceLastTimerTick = false;
private Timer _dateValueTimer;
private long _lastRequestSeenTicks;
private volatile bool _timerIsRunning;
/// <summary>
/// Initializes a new instance of the <see cref="DateHeaderValueManager"/> class.
/// </summary>
public DateHeaderValueManager()
: this(systemClock: new SystemClock())
{
}
// Internal for testing
internal DateHeaderValueManager(
ISystemClock systemClock,
TimeSpan? timeWithoutRequestsUntilIdle = null,
TimeSpan? timerInterval = null)
{
if (systemClock == null)
{
throw new ArgumentNullException(nameof(systemClock));
}
_systemClock = systemClock;
_timeWithoutRequestsUntilIdle = timeWithoutRequestsUntilIdle ?? TimeSpan.FromSeconds(10);
_timerInterval = timerInterval ?? TimeSpan.FromSeconds(1);
_dateValueTimer = new Timer(TimerLoop, state: null, dueTime: Timeout.Infinite, period: Timeout.Infinite);
}
/// <summary>
/// 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
/// </summary>
/// <returns>The value in string and byte[] format.</returns>
public DateHeaderValues GetDateHeaderValues()
{
_hadRequestsSinceLastTimerTick = !_isDisposed;
if (!_timerIsRunning)
{
StartTimer();
}
return _dateValues;
}
/// <summary>
/// Releases all resources used by the current instance of <see cref="DateHeaderValueManager"/>.
/// </summary>
public void Dispose()
{
if (!_isDisposed)
{
_isDisposed = true;
_hadRequestsSinceLastTimerTick = false;
lock (_timerLocker)
{
if (_dateValueTimer != null)
{
_timerIsRunning = false;
_dateValueTimer.Dispose();
_dateValueTimer = null;
}
}
}
}
/// <summary>
/// Starts the timer
/// </summary>
private void StartTimer()
{
var now = _systemClock.UtcNow;
SetDateValues(now);
if (!_isDisposed)
{
lock (_timerLocker)
{
if (!_timerIsRunning && _dateValueTimer != null)
{
_timerIsRunning = true;
_dateValueTimer.Change(_timerInterval, _timerInterval);
}
}
}
}
/// <summary>
/// Stops the timer
/// </summary>
private void StopTimer()
{
if (!_isDisposed)
{
lock (_timerLocker)
{
if (_dateValueTimer != null)
{
_timerIsRunning = false;
_dateValueTimer.Change(Timeout.Infinite, Timeout.Infinite);
_hadRequestsSinceLastTimerTick = false;
}
}
}
}
// Called by the Timer (background) thread
private void TimerLoop(object state)
{
var now = _systemClock.UtcNow;
SetDateValues(now);
if (_hadRequestsSinceLastTimerTick)
{
// We served requests since the last tick, reset the flag and return as we're still active
_hadRequestsSinceLastTimerTick = false;
Interlocked.Exchange(ref _lastRequestSeenTicks, now.Ticks);
return;
}
// No requests since the last timer tick, we need to check if we're beyond the idle threshold
if ((now.Ticks - Interlocked.Read(ref _lastRequestSeenTicks)) >= _timeWithoutRequestsUntilIdle.Ticks)
{
// No requests since idle threshold so stop the timer if it's still running
StopTimer();
}
}
/// <summary>
/// Sets date values from a provided ticks value
/// </summary>
/// <param name="value">A DateTimeOffset value</param>
private void SetDateValues(DateTimeOffset value)
{
// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.18 for required format of Date header
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;
}
}
}