aspnetcore/src/Microsoft.AspNetCore.Server.../TimeoutManager.cs

235 lines
8.8 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.Runtime.InteropServices;
namespace Microsoft.AspNetCore.Server.HttpSys
{
// See the native HTTP_TIMEOUT_LIMIT_INFO structure documentation for additional information.
// http://msdn.microsoft.com/en-us/library/aa364661.aspx
/// <summary>
/// Exposes the Http.Sys timeout configurations. These may also be configured in the registry.
/// </summary>
public sealed class TimeoutManager
{
private static readonly int TimeoutLimitSize =
Marshal.SizeOf<HttpApi.HTTP_TIMEOUT_LIMIT_INFO>();
private UrlGroup _urlGroup;
private int[] _timeouts;
private uint _minSendBytesPerSecond;
internal TimeoutManager()
{
// We have to maintain local state since we allow applications to set individual timeouts. Native Http
// API for setting timeouts expects all timeout values in every call so we have remember timeout values
// to fill in the blanks. Except MinSendBytesPerSecond, local state for remaining five timeouts is
// maintained in timeouts array.
//
// No initialization is required because a value of zero indicates that system defaults should be used.
_timeouts = new int[5];
}
#region Properties
/// <summary>
/// The time, in seconds, allowed for the request entity body to arrive. The default timer is 2 minutes.
///
/// The HTTP Server API turns on this timer when the request has an entity body. The timer expiration is
/// initially set to the configured value. When the HTTP Server API receives additional data indications on the
/// request, it resets the timer to give the connection another interval.
///
/// Use TimeSpan.Zero to indicate that system defaults should be used.
/// </summary>
public TimeSpan EntityBody
{
get
{
return GetTimeSpanTimeout(HttpApi.HTTP_TIMEOUT_TYPE.EntityBody);
}
set
{
SetTimeSpanTimeout(HttpApi.HTTP_TIMEOUT_TYPE.EntityBody, value);
}
}
/// <summary>
/// The time, in seconds, allowed for the HTTP Server API to drain the entity body on a Keep-Alive connection.
/// The default timer is 2 minutes.
///
/// On a Keep-Alive connection, after the application has sent a response for a request and before the request
/// entity body has completely arrived, the HTTP Server API starts draining the remainder of the entity body to
/// reach another potentially pipelined request from the client. If the time to drain the remaining entity body
/// exceeds the allowed period the connection is timed out.
///
/// Use TimeSpan.Zero to indicate that system defaults should be used.
/// </summary>
public TimeSpan DrainEntityBody
{
get
{
return GetTimeSpanTimeout(HttpApi.HTTP_TIMEOUT_TYPE.DrainEntityBody);
}
set
{
SetTimeSpanTimeout(HttpApi.HTTP_TIMEOUT_TYPE.DrainEntityBody, value);
}
}
/// <summary>
/// The time, in seconds, allowed for the request to remain in the request queue before the application picks
/// it up. The default timer is 2 minutes.
///
/// Use TimeSpan.Zero to indicate that system defaults should be used.
/// </summary>
public TimeSpan RequestQueue
{
get
{
return GetTimeSpanTimeout(HttpApi.HTTP_TIMEOUT_TYPE.RequestQueue);
}
set
{
SetTimeSpanTimeout(HttpApi.HTTP_TIMEOUT_TYPE.RequestQueue, value);
}
}
/// <summary>
/// The time, in seconds, allowed for an idle connection. The default timer is 2 minutes.
///
/// This timeout is only enforced after the first request on the connection is routed to the application.
///
/// Use TimeSpan.Zero to indicate that system defaults should be used.
/// </summary>
public TimeSpan IdleConnection
{
get
{
return GetTimeSpanTimeout(HttpApi.HTTP_TIMEOUT_TYPE.IdleConnection);
}
set
{
SetTimeSpanTimeout(HttpApi.HTTP_TIMEOUT_TYPE.IdleConnection, value);
}
}
/// <summary>
/// The time, in seconds, allowed for the HTTP Server API to parse the request header. The default timer is
/// 2 minutes.
///
/// This timeout is only enforced after the first request on the connection is routed to the application.
///
/// Use TimeSpan.Zero to indicate that system defaults should be used.
/// </summary>
public TimeSpan HeaderWait
{
get
{
return GetTimeSpanTimeout(HttpApi.HTTP_TIMEOUT_TYPE.HeaderWait);
}
set
{
SetTimeSpanTimeout(HttpApi.HTTP_TIMEOUT_TYPE.HeaderWait, value);
}
}
/// <summary>
/// The minimum send rate, in bytes-per-second, for the response. The default response send rate is 150
/// bytes-per-second.
///
/// Use 0 to indicate that system defaults should be used.
///
/// To disable this timer set it to UInt32.MaxValue
/// </summary>
public long MinSendBytesPerSecond
{
get
{
// Since we maintain local state, GET is local.
return _minSendBytesPerSecond;
}
set
{
// MinSendRate value is ULONG in native layer.
if (value < 0 || value > uint.MaxValue)
{
throw new ArgumentOutOfRangeException("value");
}
SetUrlGroupTimeouts(_timeouts, (uint)value);
_minSendBytesPerSecond = (uint)value;
}
}
#endregion Properties
#region Helpers
private TimeSpan GetTimeSpanTimeout(HttpApi.HTTP_TIMEOUT_TYPE type)
{
// Since we maintain local state, GET is local.
return new TimeSpan(0, 0, (int)_timeouts[(int)type]);
}
private void SetTimeSpanTimeout(HttpApi.HTTP_TIMEOUT_TYPE type, TimeSpan value)
{
// All timeouts are defined as USHORT in native layer (except MinSendRate, which is ULONG). Make sure that
// timeout value is within range.
var timeoutValue = Convert.ToInt64(value.TotalSeconds);
if (timeoutValue < 0 || timeoutValue > ushort.MaxValue)
{
throw new ArgumentOutOfRangeException("value");
}
// Use local state to get values for other timeouts. Call into the native layer and if that
// call succeeds, update local state.
var newTimeouts = (int[])_timeouts.Clone();
newTimeouts[(int)type] = (int)timeoutValue;
SetUrlGroupTimeouts(newTimeouts, _minSendBytesPerSecond);
_timeouts[(int)type] = (int)timeoutValue;
}
internal void SetUrlGroupTimeouts(UrlGroup urlGroup)
{
_urlGroup = urlGroup;
SetUrlGroupTimeouts(_timeouts, _minSendBytesPerSecond);
}
private unsafe void SetUrlGroupTimeouts(int[] timeouts, uint minSendBytesPerSecond)
{
if (_urlGroup == null)
{
// Not started yet
return;
}
var timeoutinfo = new HttpApi.HTTP_TIMEOUT_LIMIT_INFO();
timeoutinfo.Flags = HttpApi.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT;
timeoutinfo.DrainEntityBody =
(ushort)timeouts[(int)HttpApi.HTTP_TIMEOUT_TYPE.DrainEntityBody];
timeoutinfo.EntityBody =
(ushort)timeouts[(int)HttpApi.HTTP_TIMEOUT_TYPE.EntityBody];
timeoutinfo.RequestQueue =
(ushort)timeouts[(int)HttpApi.HTTP_TIMEOUT_TYPE.RequestQueue];
timeoutinfo.IdleConnection =
(ushort)timeouts[(int)HttpApi.HTTP_TIMEOUT_TYPE.IdleConnection];
timeoutinfo.HeaderWait =
(ushort)timeouts[(int)HttpApi.HTTP_TIMEOUT_TYPE.HeaderWait];
timeoutinfo.MinSendRate = minSendBytesPerSecond;
var infoptr = new IntPtr(&timeoutinfo);
_urlGroup.SetProperty(
HttpApi.HTTP_SERVER_PROPERTY.HttpServerTimeoutsProperty,
infoptr, (uint)TimeoutLimitSize);
}
#endregion Helpers
}
}