#364 - Expose HTTP_503_RESPONSE_VERBOSITY option (#447)

This commit is contained in:
FabianMeiswinkel 2018-04-10 12:08:33 -07:00 коммит произвёл Chris Ross
Родитель 61f7c82d49
Коммит bec259f976
5 изменённых файлов: 136 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,26 @@
// 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.
namespace Microsoft.AspNetCore.Server.HttpSys
{
/// <summary>
/// Enum declaring the allowed values for the verbosity level when http.sys reject requests due to throttling.
/// </summary>
public enum Http503VerbosityLevel : long
{
/// <summary>
/// A 503 response is not sent; the connection is reset. This is the default HTTP Server API behavior.
/// </summary>
Basic = 0,
/// <summary>
/// The HTTP Server API sends a 503 response with a "Service Unavailable" reason phrase.
/// </summary>
Limited = 1,
/// <summary>
/// The HTTP Server API sends a 503 response with a detailed reason phrase.
/// </summary>
Full = 2
}
}

Просмотреть файл

@ -2,18 +2,21 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Globalization;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.HttpSys
{
public class HttpSysOptions
{
private const Http503VerbosityLevel DefaultRejectionVerbosityLevel = Http503VerbosityLevel.Basic; // Http.sys default.
private const long DefaultRequestQueueLength = 1000; // Http.sys default.
internal static readonly int DefaultMaxAccepts = 5 * Environment.ProcessorCount;
// Matches the default maxAllowedContentLength in IIS (~28.6 MB)
// https://www.iis.net/configreference/system.webserver/security/requestfiltering/requestlimits#005
private const long DefaultMaxRequestBodySize = 30000000;
private Http503VerbosityLevel _rejectionVebosityLevel = DefaultRejectionVerbosityLevel;
// The native request queue
private long _requestQueueLength = DefaultRequestQueueLength;
private long? _maxConnections;
@ -137,6 +140,38 @@ namespace Microsoft.AspNetCore.Server.HttpSys
/// </summary>
public bool AllowSynchronousIO { get; set; } = true;
/// <summary>
/// Gets or sets a value that controls how http.sys reacts when rejecting requests due to throttling conditions - like when the request
/// queue limit is reached. The default in http.sys is "Basic" which means http.sys is just resetting the TCP connection. IIS uses Limited
/// as its default behavior which will result in sending back a 503 - Service Unavailable back to the client.
/// </summary>
public Http503VerbosityLevel Http503Verbosity
{
get
{
return _rejectionVebosityLevel;
}
set
{
if (value < Http503VerbosityLevel.Basic || value > Http503VerbosityLevel.Full)
{
string message = String.Format(
CultureInfo.InvariantCulture,
"The value must be one of the values defined in the '{0}' enum.",
typeof(Http503VerbosityLevel).Name);
throw new ArgumentOutOfRangeException(nameof(value), value, message);
}
if (_requestQueue != null)
{
_requestQueue.SetRejectionVerbosity(value);
}
// Only store it if it succeeds or hasn't started yet
_rejectionVebosityLevel = value;
}
}
internal void Apply(UrlGroup urlGroup, RequestQueue requestQueue)
{
_urlGroup = urlGroup;
@ -152,6 +187,11 @@ namespace Microsoft.AspNetCore.Server.HttpSys
_requestQueue.SetLengthLimit(_requestQueueLength);
}
if (_rejectionVebosityLevel != DefaultRejectionVerbosityLevel)
{
_requestQueue.SetRejectionVerbosity(_rejectionVebosityLevel);
}
Authentication.SetUrlGroupSecurity(urlGroup);
Timeouts.SetUrlGroupTimeouts(urlGroup);
}

Просмотреть файл

@ -99,6 +99,21 @@ namespace Microsoft.AspNetCore.Server.HttpSys
}
}
// The listener must be active for this to work.
internal unsafe void SetRejectionVerbosity(Http503VerbosityLevel verbosity)
{
CheckDisposed();
var result = HttpApi.HttpSetRequestQueueProperty(Handle,
HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServer503VerbosityProperty,
new IntPtr((void*)&verbosity), (uint)Marshal.SizeOf<long>(), 0, IntPtr.Zero);
if (result != 0)
{
throw new HttpSysException((int)result);
}
}
public void Dispose()
{
if (_disposed)

Просмотреть файл

@ -245,6 +245,22 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
}
}
[ConditionalFact]
public async Task Server_SetRejectionVerbosityLevel_Success()
{
using (var server = Utilities.CreateHttpServer(out string address))
{
server.Options.Http503Verbosity = Http503VerbosityLevel.Limited;
var responseTask = SendRequestAsync(address);
var context = await server.AcceptAsync(Utilities.DefaultTimeout);
context.Dispose();
var response = await responseTask;
Assert.Equal(string.Empty, response);
}
}
[ConditionalFact]
public async Task Server_HotAddPrefix_Success()
{

Просмотреть файл

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Text;
@ -298,6 +299,44 @@ namespace Microsoft.AspNetCore.Server.HttpSys
}
}
[ConditionalFact]
public async Task Server_SetHttp503VebosittHittingThrottle_Success()
{
// This is just to get a dynamic port
string address;
using (Utilities.CreateHttpServer(out address, httpContext => Task.FromResult(0))) { }
var server = Utilities.CreatePump();
server.Listener.Options.UrlPrefixes.Add(UrlPrefix.Create(address));
Assert.Null(server.Listener.Options.MaxConnections);
server.Listener.Options.MaxConnections = 3;
server.Listener.Options.Http503Verbosity = Http503VerbosityLevel.Limited;
using (server)
{
await server.StartAsync(new DummyApplication(), CancellationToken.None);
using (var client1 = await SendHungRequestAsync("GET", address))
using (var client2 = await SendHungRequestAsync("GET", address))
{
using (var client3 = await SendHungRequestAsync("GET", address))
{
using (HttpClient client4 = new HttpClient())
{
// Maxed out, refuses connection should return 503
HttpResponseMessage response = await client4.GetAsync(address);
Assert.Equal(HttpStatusCode.ServiceUnavailable, response.StatusCode);
}
}
// A connection has been closed, try again.
string responseText = await SendRequestAsync(address);
Assert.Equal(string.Empty, responseText);
}
}
}
[ConditionalFact]
public void Server_SetConnectionLimitArgumentValidation_Success()
{