#160 Refactor options/settings

This commit is contained in:
Chris R 2016-08-18 13:46:36 -07:00
parent cd886802fe
commit e39ea62808
23 changed files with 275 additions and 198 deletions

View File

@ -16,9 +16,11 @@ namespace HelloWorld
public static async Task Run(string[] args) public static async Task Run(string[] args)
{ {
using (WebListener listener = new WebListener()) var settings = new WebListenerSettings();
settings.UrlPrefixes.Add("http://localhost:8080");
using (WebListener listener = new WebListener(settings))
{ {
listener.UrlPrefixes.Add("http://localhost:8080");
listener.Start(); listener.Start();
Console.WriteLine("Running..."); Console.WriteLine("Running...");

View File

@ -16,7 +16,7 @@ namespace HotAddSample
{ {
loggerfactory.AddConsole(LogLevel.Information); loggerfactory.AddConsole(LogLevel.Information);
var addresses = app.ServerFeatures.Get<WebListener>().UrlPrefixes; var addresses = app.ServerFeatures.Get<WebListener>().Settings.UrlPrefixes;
addresses.Add("http://localhost:12346/pathBase/"); addresses.Add("http://localhost:12346/pathBase/");
app.Use(async (context, next) => app.Use(async (context, next) =>

View File

@ -19,8 +19,8 @@ namespace SelfHostServer
// Server options can be configured here instead of in Main. // Server options can be configured here instead of in Main.
services.Configure<WebListenerOptions>(options => services.Configure<WebListenerOptions>(options =>
{ {
options.Listener.AuthenticationManager.AuthenticationSchemes = AuthenticationSchemes.None; options.ListenerSettings.Authentication.Schemes = AuthenticationSchemes.None;
options.Listener.AuthenticationManager.AllowAnonymous = true; options.ListenerSettings.Authentication.AllowAnonymous = true;
}); });
} }
@ -52,8 +52,8 @@ namespace SelfHostServer
.UseStartup<Startup>() .UseStartup<Startup>()
.UseWebListener(options => .UseWebListener(options =>
{ {
options.Listener.AuthenticationManager.AuthenticationSchemes = AuthenticationSchemes.None; options.ListenerSettings.Authentication.Schemes = AuthenticationSchemes.None;
options.Listener.AuthenticationManager.AllowAnonymous = true; options.ListenerSettings.Authentication.AllowAnonymous = true;
}) })
.Build(); .Build();

View File

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Open Technologies, Inc. // Copyright (c) Microsoft Open Technologies, Inc.
// All Rights Reserved // All Rights Reserved
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Server.WebListener.Internal
public void Configure(WebListenerOptions options) public void Configure(WebListenerOptions options)
{ {
options.Listener = new Microsoft.Net.Http.Server.WebListener(_loggerFactory); options.ListenerSettings.Logger = _loggerFactory.CreateLogger<Microsoft.Net.Http.Server.WebListener>();
} }
} }
} }

View File

@ -57,15 +57,16 @@ namespace Microsoft.AspNetCore.Server.WebListener
throw new ArgumentNullException(nameof(loggerFactory)); throw new ArgumentNullException(nameof(loggerFactory));
} }
_listener = options.Value?.Listener ?? new Microsoft.Net.Http.Server.WebListener(loggerFactory); var optionsInstance = options.Value;
_listener = new Microsoft.Net.Http.Server.WebListener(optionsInstance.ListenerSettings);
_logger = LogHelper.CreateLogger(loggerFactory, typeof(MessagePump)); _logger = LogHelper.CreateLogger(loggerFactory, typeof(MessagePump));
Features = new FeatureCollection(); Features = new FeatureCollection();
_serverAddresses = new ServerAddressesFeature(); _serverAddresses = new ServerAddressesFeature();
Features.Set<IServerAddressesFeature>(_serverAddresses); Features.Set<IServerAddressesFeature>(_serverAddresses);
_processRequest = new Action<object>(ProcessRequestAsync); _processRequest = new Action<object>(ProcessRequestAsync);
_maxAccepts = options.Value?.MaxAccepts ?? WebListenerOptions.DefaultMaxAccepts; _maxAccepts = optionsInstance.MaxAccepts;
EnableResponseCaching = options.Value?.EnableResponseCaching ?? true; EnableResponseCaching = optionsInstance.EnableResponseCaching;
_shutdownSignal = new ManualResetEvent(false); _shutdownSignal = new ManualResetEvent(false);
} }
@ -94,7 +95,7 @@ namespace Microsoft.AspNetCore.Server.WebListener
_application = new ApplicationWrapper<TContext>(application); _application = new ApplicationWrapper<TContext>(application);
if (_listener.UrlPrefixes.Count == 0) if (_listener.Settings.UrlPrefixes.Count == 0)
{ {
throw new InvalidOperationException("No address prefixes were defined."); throw new InvalidOperationException("No address prefixes were defined.");
} }
@ -224,7 +225,7 @@ namespace Microsoft.AspNetCore.Server.WebListener
{ {
foreach (var value in addresses) foreach (var value in addresses)
{ {
listener.UrlPrefixes.Add(UrlPrefix.Create(value)); listener.Settings.UrlPrefixes.Add(UrlPrefix.Create(value));
} }
} }

View File

@ -16,6 +16,7 @@
// permissions and limitations under the License. // permissions and limitations under the License.
using System; using System;
using Microsoft.Net.Http.Server;
namespace Microsoft.AspNetCore.Server.WebListener namespace Microsoft.AspNetCore.Server.WebListener
{ {
@ -23,10 +24,21 @@ namespace Microsoft.AspNetCore.Server.WebListener
{ {
internal static readonly int DefaultMaxAccepts = 5 * Environment.ProcessorCount; internal static readonly int DefaultMaxAccepts = 5 * Environment.ProcessorCount;
public Microsoft.Net.Http.Server.WebListener Listener { get; set; } = new Microsoft.Net.Http.Server.WebListener(); /// <summary>
/// Settings for the underlying WebListener instance.
/// </summary>
public WebListenerSettings ListenerSettings { get; } = new WebListenerSettings();
/// <summary>
/// The maximum number of concurrent calls to WebListener.AcceptAsync().
/// </summary>
public int MaxAccepts { get; set; } = DefaultMaxAccepts; public int MaxAccepts { get; set; } = DefaultMaxAccepts;
/// <summary>
/// Attempts kernel mode caching for responses with eligible headers. The response may not include
/// Set-Cookie, Vary, or Pragma headers. It must include a Cache-Control header with Public and
/// either a Shared-Max-Age or Max-Age value, or an Expires header.
/// </summary>
public bool EnableResponseCaching { get; set; } = true; public bool EnableResponseCaching { get; set; } = true;
} }
} }

View File

@ -23,6 +23,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Claims; using System.Security.Claims;
@ -42,24 +43,21 @@ namespace Microsoft.Net.Http.Server
private static readonly int AuthInfoSize = private static readonly int AuthInfoSize =
Marshal.SizeOf<HttpApi.HTTP_SERVER_AUTHENTICATION_INFO>(); Marshal.SizeOf<HttpApi.HTTP_SERVER_AUTHENTICATION_INFO>();
private WebListener _server; private UrlGroup _urlGroup;
private AuthenticationSchemes _authSchemes; private AuthenticationSchemes _authSchemes;
private bool _allowAnonymous = true; private bool _allowAnonymous = true;
internal AuthenticationManager(WebListener listener) internal AuthenticationManager()
{ {
_server = listener;
} }
#region Properties public AuthenticationSchemes Schemes
public AuthenticationSchemes AuthenticationSchemes
{ {
get { return _authSchemes; } get { return _authSchemes; }
set set
{ {
_authSchemes = value; _authSchemes = value;
SetServerSecurity(); SetUrlGroupSecurity();
} }
} }
@ -69,10 +67,21 @@ namespace Microsoft.Net.Http.Server
set { _allowAnonymous = value; } set { _allowAnonymous = value; }
} }
#endregion Properties internal void SetUrlGroupSecurity(UrlGroup urlGroup)
private unsafe void SetServerSecurity()
{ {
Debug.Assert(_urlGroup == null, "SetUrlGroupSecurity called more than once.");
_urlGroup = urlGroup;
SetUrlGroupSecurity();
}
private unsafe void SetUrlGroupSecurity()
{
if (_urlGroup == null)
{
// Not started yet.
return;
}
HttpApi.HTTP_SERVER_AUTHENTICATION_INFO authInfo = HttpApi.HTTP_SERVER_AUTHENTICATION_INFO authInfo =
new HttpApi.HTTP_SERVER_AUTHENTICATION_INFO(); new HttpApi.HTTP_SERVER_AUTHENTICATION_INFO();
@ -91,7 +100,7 @@ namespace Microsoft.Net.Http.Server
IntPtr infoptr = new IntPtr(&authInfo); IntPtr infoptr = new IntPtr(&authInfo);
_server.UrlGroup.SetProperty( _urlGroup.SetProperty(
HttpApi.HTTP_SERVER_PROPERTY.HttpServerAuthenticationProperty, HttpApi.HTTP_SERVER_PROPERTY.HttpServerAuthenticationProperty,
infoptr, (uint)AuthInfoSize); infoptr, (uint)AuthInfoSize);
} }

View File

@ -29,16 +29,6 @@ namespace Microsoft.Net.Http.Server
{ {
internal static class LogHelper internal static class LogHelper
{ {
internal static ILogger CreateLogger(ILoggerFactory factory, Type type)
{
if (factory == null)
{
return new NullLogger();
}
return factory.CreateLogger(type.FullName);
}
internal static void LogInfo(ILogger logger, string data) internal static void LogInfo(ILogger logger, string data)
{ {
if (logger == null) if (logger == null)
@ -86,29 +76,5 @@ namespace Microsoft.Net.Http.Server
logger.LogError(location + "; " + message); logger.LogError(location + "; " + message);
} }
} }
private class NullLogger : ILogger
{
public IDisposable BeginScope<TState>(TState state)
{
return new NullDispose();
}
public bool IsEnabled(LogLevel logLevel)
{
return false;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
}
private class NullDispose : IDisposable
{
public void Dispose()
{
}
}
}
} }
} }

View File

@ -16,11 +16,8 @@
// permissions and limitations under the License. // permissions and limitations under the License.
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Microsoft.Net.Http.Server namespace Microsoft.Net.Http.Server
@ -32,6 +29,7 @@ namespace Microsoft.Net.Http.Server
private readonly UrlGroup _urlGroup; private readonly UrlGroup _urlGroup;
private readonly ILogger _logger; private readonly ILogger _logger;
private bool _disposed;
internal RequestQueue(UrlGroup urlGroup, ILogger logger) internal RequestQueue(UrlGroup urlGroup, ILogger logger)
{ {
@ -66,6 +64,7 @@ namespace Microsoft.Net.Http.Server
internal unsafe void AttachToUrlGroup() internal unsafe void AttachToUrlGroup()
{ {
CheckDisposed();
// Set the association between request queue and url group. After this, requests for registered urls will // Set the association between request queue and url group. After this, requests for registered urls will
// get delivered to this request queue. // get delivered to this request queue.
@ -81,6 +80,7 @@ namespace Microsoft.Net.Http.Server
internal unsafe void DetachFromUrlGroup() internal unsafe void DetachFromUrlGroup()
{ {
CheckDisposed();
// Break the association between request queue and url group. After this, requests for registered urls // Break the association between request queue and url group. After this, requests for registered urls
// will get 503s. // will get 503s.
// Note that this method may be called multiple times (Stop() and then Abort()). This // Note that this method may be called multiple times (Stop() and then Abort()). This
@ -100,6 +100,8 @@ namespace Microsoft.Net.Http.Server
// The listener must be active for this to work. // The listener must be active for this to work.
internal unsafe void SetLengthLimit(long length) internal unsafe void SetLengthLimit(long length)
{ {
CheckDisposed();
var result = HttpApi.HttpSetRequestQueueProperty(Handle, var result = HttpApi.HttpSetRequestQueueProperty(Handle,
HttpApi.HTTP_SERVER_PROPERTY.HttpServerQueueLengthProperty, HttpApi.HTTP_SERVER_PROPERTY.HttpServerQueueLengthProperty,
new IntPtr((void*)&length), (uint)Marshal.SizeOf<long>(), 0, IntPtr.Zero); new IntPtr((void*)&length), (uint)Marshal.SizeOf<long>(), 0, IntPtr.Zero);
@ -112,8 +114,22 @@ namespace Microsoft.Net.Http.Server
public void Dispose() public void Dispose()
{ {
if (_disposed)
{
return;
}
_disposed = true;
BoundHandle.Dispose(); BoundHandle.Dispose();
Handle.Dispose(); Handle.Dispose();
} }
private void CheckDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(this.GetType().FullName);
}
}
} }
} }

View File

@ -50,7 +50,8 @@ namespace Microsoft.Net.Http.Server
internal void SetProperty(HttpApi.HTTP_SERVER_PROPERTY property, IntPtr info, uint infosize, bool throwOnError = true) internal void SetProperty(HttpApi.HTTP_SERVER_PROPERTY property, IntPtr info, uint infosize, bool throwOnError = true)
{ {
Debug.Assert(info != IntPtr.Zero, "SetUrlGroupProperty called with invalid pointer"); Debug.Assert(info != IntPtr.Zero, "SetUrlGroupProperty called with invalid pointer");
CheckDisposed();
var statusCode = HttpApi.HttpSetUrlGroupProperty(Id, property, info, infosize); var statusCode = HttpApi.HttpSetUrlGroupProperty(Id, property, info, infosize);
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
@ -67,6 +68,7 @@ namespace Microsoft.Net.Http.Server
internal void RegisterPrefix(string uriPrefix, int contextId) internal void RegisterPrefix(string uriPrefix, int contextId)
{ {
LogHelper.LogInfo(_logger, "Listening on prefix: " + uriPrefix); LogHelper.LogInfo(_logger, "Listening on prefix: " + uriPrefix);
CheckDisposed();
var statusCode = HttpApi.HttpAddUrlToUrlGroup(Id, uriPrefix, (ulong)contextId, 0); var statusCode = HttpApi.HttpAddUrlToUrlGroup(Id, uriPrefix, (ulong)contextId, 0);
@ -86,6 +88,7 @@ namespace Microsoft.Net.Http.Server
internal bool UnregisterPrefix(string uriPrefix) internal bool UnregisterPrefix(string uriPrefix)
{ {
LogHelper.LogInfo(_logger, "Stop listening on prefix: " + uriPrefix); LogHelper.LogInfo(_logger, "Stop listening on prefix: " + uriPrefix);
CheckDisposed();
var statusCode = HttpApi.HttpRemoveUrlFromUrlGroup(Id, uriPrefix, 0); var statusCode = HttpApi.HttpRemoveUrlFromUrlGroup(Id, uriPrefix, 0);
@ -115,5 +118,13 @@ namespace Microsoft.Net.Http.Server
} }
Id = 0; Id = 0;
} }
private void CheckDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(this.GetType().FullName);
}
}
} }
} }

View File

@ -85,7 +85,7 @@ namespace Microsoft.Net.Http.Server
QueryString = Marshal.PtrToStringUni((IntPtr)cookedUrl.pQueryString, cookedUrl.QueryStringLength / 2); QueryString = Marshal.PtrToStringUni((IntPtr)cookedUrl.pQueryString, cookedUrl.QueryStringLength / 2);
} }
var prefix = requestContext.Server.UrlPrefixes.GetPrefix((int)memoryBlob.RequestBlob->UrlContext); var prefix = requestContext.Server.Settings.UrlPrefixes.GetPrefix((int)memoryBlob.RequestBlob->UrlContext);
var originalPath = RequestUriBuilder.GetRequestPath(RawUrl, cookedUrlPath, RequestContext.Logger); var originalPath = RequestUriBuilder.GetRequestPath(RawUrl, cookedUrlPath, RequestContext.Logger);
// These paths are both unescaped already. // These paths are both unescaped already.

View File

@ -64,7 +64,7 @@ namespace Microsoft.Net.Http.Server
_expectedBodyLength = 0; _expectedBodyLength = 0;
_nativeStream = null; _nativeStream = null;
_cacheTtl = null; _cacheTtl = null;
_authChallenges = RequestContext.Server.AuthenticationManager.AuthenticationSchemes; _authChallenges = RequestContext.Server.Settings.Authentication.Schemes;
} }
private enum ResponseState private enum ResponseState
@ -412,7 +412,7 @@ namespace Microsoft.Net.Http.Server
// 401 // 401
if (StatusCode == (ushort)HttpStatusCode.Unauthorized) if (StatusCode == (ushort)HttpStatusCode.Unauthorized)
{ {
RequestContext.Server.AuthenticationManager.SetAuthenticationChallenge(RequestContext); RequestContext.Server.Settings.Authentication.SetAuthenticationChallenge(RequestContext);
} }
var flags = HttpApi.HTTP_FLAGS.NONE; var flags = HttpApi.HTTP_FLAGS.NONE;

View File

@ -171,7 +171,7 @@ namespace Microsoft.Net.Http.Server
IntPtr.Zero); IntPtr.Zero);
} }
if (_requestContext.Server.IgnoreWriteExceptions) if (_requestContext.Server.Settings.IgnoreWriteExceptions)
{ {
statusCode = UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS; statusCode = UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS;
} }
@ -338,7 +338,7 @@ namespace Microsoft.Net.Http.Server
if (statusCode != ErrorCodes.ERROR_SUCCESS && statusCode != ErrorCodes.ERROR_IO_PENDING) if (statusCode != ErrorCodes.ERROR_SUCCESS && statusCode != ErrorCodes.ERROR_IO_PENDING)
{ {
asyncResult.Dispose(); asyncResult.Dispose();
if (_requestContext.Server.IgnoreWriteExceptions && started) if (_requestContext.Server.Settings.IgnoreWriteExceptions && started)
{ {
asyncResult.Complete(); asyncResult.Complete();
} }
@ -602,7 +602,7 @@ namespace Microsoft.Net.Http.Server
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING) if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
{ {
asyncResult.Dispose(); asyncResult.Dispose();
if (_requestContext.Server.IgnoreWriteExceptions && started) if (_requestContext.Server.Settings.IgnoreWriteExceptions && started)
{ {
asyncResult.Complete(); asyncResult.Complete();
} }

View File

@ -37,14 +37,12 @@ namespace Microsoft.Net.Http.Server
private static readonly int TimeoutLimitSize = private static readonly int TimeoutLimitSize =
Marshal.SizeOf<HttpApi.HTTP_TIMEOUT_LIMIT_INFO>(); Marshal.SizeOf<HttpApi.HTTP_TIMEOUT_LIMIT_INFO>();
private WebListener _server; private UrlGroup _urlGroup;
private int[] _timeouts; private int[] _timeouts;
private uint _minSendBytesPerSecond; private uint _minSendBytesPerSecond;
internal TimeoutManager(WebListener listener) internal TimeoutManager()
{ {
_server = listener;
// We have to maintain local state since we allow applications to set individual timeouts. Native Http // 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 // 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 // to fill in the blanks. Except MinSendBytesPerSecond, local state for remaining five timeouts is
@ -180,7 +178,7 @@ namespace Microsoft.Net.Http.Server
throw new ArgumentOutOfRangeException("value"); throw new ArgumentOutOfRangeException("value");
} }
SetServerTimeouts(_timeouts, (uint)value); SetUrlGroupTimeouts(_timeouts, (uint)value);
_minSendBytesPerSecond = (uint)value; _minSendBytesPerSecond = (uint)value;
} }
} }
@ -211,12 +209,24 @@ namespace Microsoft.Net.Http.Server
// call succeeds, update local state. // call succeeds, update local state.
var newTimeouts = (int[])_timeouts.Clone(); var newTimeouts = (int[])_timeouts.Clone();
newTimeouts[(int)type] = (int)timeoutValue; newTimeouts[(int)type] = (int)timeoutValue;
SetServerTimeouts(newTimeouts, _minSendBytesPerSecond); SetUrlGroupTimeouts(newTimeouts, _minSendBytesPerSecond);
_timeouts[(int)type] = (int)timeoutValue; _timeouts[(int)type] = (int)timeoutValue;
} }
private unsafe void SetServerTimeouts(int[] timeouts, uint minSendBytesPerSecond) 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(); var timeoutinfo = new HttpApi.HTTP_TIMEOUT_LIMIT_INFO();
timeoutinfo.Flags = HttpApi.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT; timeoutinfo.Flags = HttpApi.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT;
@ -234,7 +244,7 @@ namespace Microsoft.Net.Http.Server
var infoptr = new IntPtr(&timeoutinfo); var infoptr = new IntPtr(&timeoutinfo);
_server.UrlGroup.SetProperty( _urlGroup.SetProperty(
HttpApi.HTTP_SERVER_PROPERTY.HttpServerTimeoutsProperty, HttpApi.HTTP_SERVER_PROPERTY.HttpServerTimeoutsProperty,
infoptr, (uint)TimeoutLimitSize); infoptr, (uint)TimeoutLimitSize);
} }

View File

@ -1,7 +1,6 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
@ -12,13 +11,12 @@ namespace Microsoft.Net.Http.Server
/// </summary> /// </summary>
public class UrlPrefixCollection : ICollection<UrlPrefix> public class UrlPrefixCollection : ICollection<UrlPrefix>
{ {
private readonly WebListener _webListener;
private readonly IDictionary<int, UrlPrefix> _prefixes = new Dictionary<int, UrlPrefix>(1); private readonly IDictionary<int, UrlPrefix> _prefixes = new Dictionary<int, UrlPrefix>(1);
private UrlGroup _urlGroup;
private int _nextId = 1; private int _nextId = 1;
internal UrlPrefixCollection(WebListener webListener) internal UrlPrefixCollection()
{ {
_webListener = webListener;
} }
public int Count public int Count
@ -47,9 +45,9 @@ namespace Microsoft.Net.Http.Server
lock (_prefixes) lock (_prefixes)
{ {
var id = _nextId++; var id = _nextId++;
if (_webListener.IsListening) if (_urlGroup != null)
{ {
_webListener.UrlGroup.RegisterPrefix(item.FullPrefix, id); _urlGroup.RegisterPrefix(item.FullPrefix, id);
} }
_prefixes.Add(id, item); _prefixes.Add(id, item);
} }
@ -67,7 +65,7 @@ namespace Microsoft.Net.Http.Server
{ {
lock (_prefixes) lock (_prefixes)
{ {
if (_webListener.IsListening) if (_urlGroup != null)
{ {
UnregisterAllPrefixes(); UnregisterAllPrefixes();
} }
@ -106,9 +104,9 @@ namespace Microsoft.Net.Http.Server
if (pair.Value.Equals(item)) if (pair.Value.Equals(item))
{ {
id = pair.Key; id = pair.Key;
if (_webListener.IsListening) if (_urlGroup != null)
{ {
_webListener.UrlGroup.UnregisterPrefix(pair.Value.FullPrefix); _urlGroup.UnregisterPrefix(pair.Value.FullPrefix);
} }
} }
} }
@ -134,15 +132,16 @@ namespace Microsoft.Net.Http.Server
return GetEnumerator(); return GetEnumerator();
} }
internal void RegisterAllPrefixes() internal void RegisterAllPrefixes(UrlGroup urlGroup)
{ {
lock (_prefixes) lock (_prefixes)
{ {
_urlGroup = urlGroup;
// go through the uri list and register for each one of them // go through the uri list and register for each one of them
foreach (var pair in _prefixes) foreach (var pair in _prefixes)
{ {
// We'll get this index back on each request and use it to look up the prefix to calculate PathBase. // We'll get this index back on each request and use it to look up the prefix to calculate PathBase.
_webListener.UrlGroup.RegisterPrefix(pair.Value.FullPrefix, pair.Key); _urlGroup.RegisterPrefix(pair.Value.FullPrefix, pair.Key);
} }
} }
} }
@ -155,7 +154,7 @@ namespace Microsoft.Net.Http.Server
foreach (var prefix in _prefixes.Values) foreach (var prefix in _prefixes.Values)
{ {
// ignore possible failures // ignore possible failures
_webListener.UrlGroup.UnregisterPrefix(prefix.FullPrefix); _urlGroup.UnregisterPrefix(prefix.FullPrefix);
} }
} }
} }

View File

@ -24,7 +24,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -36,8 +35,6 @@ namespace Microsoft.Net.Http.Server
/// </summary> /// </summary>
public sealed class WebListener : IDisposable public sealed class WebListener : IDisposable
{ {
private const long DefaultRequestQueueLength = 1000; // Http.sys default.
// Win8# 559317 fixed a bug in Http.sys's HttpReceiveClientCertificate method. // Win8# 559317 fixed a bug in Http.sys's HttpReceiveClientCertificate method.
// Without this fix IOCP callbacks were not being called although ERROR_IO_PENDING was // Without this fix IOCP callbacks were not being called although ERROR_IO_PENDING was
// returned from HttpReceiveClientCertificate when using the // returned from HttpReceiveClientCertificate when using the
@ -53,49 +50,39 @@ namespace Microsoft.Net.Http.Server
// 0.5 seconds per request. Respond with a 400 Bad Request. // 0.5 seconds per request. Respond with a 400 Bad Request.
private const int UnknownHeaderLimit = 1000; private const int UnknownHeaderLimit = 1000;
private ILogger _logger;
private volatile State _state; // m_State is set only within lock blocks, but often read outside locks. private volatile State _state; // m_State is set only within lock blocks, but often read outside locks.
private bool _ignoreWriteExceptions;
private ServerSession _serverSession; private ServerSession _serverSession;
private UrlGroup _urlGroup; private UrlGroup _urlGroup;
private RequestQueue _requestQueue; private RequestQueue _requestQueue;
private TimeoutManager _timeoutManager;
private AuthenticationManager _authManager;
private DisconnectListener _disconnectListener; private DisconnectListener _disconnectListener;
private object _internalLock; private object _internalLock;
private UrlPrefixCollection _urlPrefixes;
// The native request queue
private long? _requestQueueLength;
public WebListener() public WebListener()
: this(null) : this(new WebListenerSettings())
{ {
} }
public WebListener(ILoggerFactory factory) public WebListener(WebListenerSettings settings)
{ {
if (settings == null)
{
throw new ArgumentNullException(nameof(settings));
}
if (!HttpApi.Supported) if (!HttpApi.Supported)
{ {
throw new PlatformNotSupportedException(); throw new PlatformNotSupportedException();
} }
_logger = LogHelper.CreateLogger(factory, typeof(WebListener)); Debug.Assert(HttpApi.ApiVersion == HttpApi.HTTP_API_VERSION.Version20, "Invalid Http api version");
Debug.Assert(HttpApi.ApiVersion == Settings = settings;
HttpApi.HTTP_API_VERSION.Version20, "Invalid Http api version");
_state = State.Stopped; _state = State.Stopped;
_internalLock = new object(); _internalLock = new object();
_urlPrefixes = new UrlPrefixCollection(this);
_timeoutManager = new TimeoutManager(this);
_authManager = new AuthenticationManager(this);
// V2 initialization sequence: // V2 initialization sequence:
// 1. Create server session // 1. Create server session
// 2. Create url group // 2. Create url group
@ -107,11 +94,11 @@ namespace Microsoft.Net.Http.Server
{ {
_serverSession = new ServerSession(); _serverSession = new ServerSession();
_urlGroup = new UrlGroup(_serverSession, _logger); _urlGroup = new UrlGroup(_serverSession, Logger);
_requestQueue = new RequestQueue(_urlGroup, _logger); _requestQueue = new RequestQueue(_urlGroup, Logger);
_disconnectListener = new DisconnectListener(_requestQueue, _logger); _disconnectListener = new DisconnectListener(_requestQueue, Logger);
} }
catch (Exception exception) catch (Exception exception)
{ {
@ -119,7 +106,7 @@ namespace Microsoft.Net.Http.Server
_requestQueue?.Dispose(); _requestQueue?.Dispose();
_urlGroup?.Dispose(); _urlGroup?.Dispose();
_serverSession?.Dispose(); _serverSession?.Dispose();
LogHelper.LogException(_logger, ".Ctor", exception); LogHelper.LogException(Logger, ".Ctor", exception);
throw; throw;
} }
} }
@ -133,7 +120,7 @@ namespace Microsoft.Net.Http.Server
internal ILogger Logger internal ILogger Logger
{ {
get { return _logger; } get { return Settings.Logger; }
} }
internal UrlGroup UrlGroup internal UrlGroup UrlGroup
@ -151,66 +138,13 @@ namespace Microsoft.Net.Http.Server
get { return _disconnectListener; } get { return _disconnectListener; }
} }
// TODO: https://github.com/aspnet/WebListener/issues/173 public WebListenerSettings Settings { get; }
internal bool IgnoreWriteExceptions
{
get { return _ignoreWriteExceptions; }
set
{
CheckDisposed();
_ignoreWriteExceptions = value;
}
}
public UrlPrefixCollection UrlPrefixes
{
get { return _urlPrefixes; }
}
/// <summary>
/// Exposes the Http.Sys timeout configurations. These may also be configured in the registry.
/// </summary>
public TimeoutManager TimeoutManager
{
get { return _timeoutManager; }
}
/// <summary>
/// Http.Sys authentication settings.
/// </summary>
public AuthenticationManager AuthenticationManager
{
get { return _authManager; }
}
public bool IsListening public bool IsListening
{ {
get { return _state == State.Started; } get { return _state == State.Started; }
} }
/// <summary>
/// Sets the maximum number of requests that will be queued up in Http.Sys.
/// </summary>
/// <param name="limit"></param>
public void SetRequestQueueLimit(long limit)
{
CheckDisposed();
if (limit <= 0)
{
throw new ArgumentOutOfRangeException("limit", limit, string.Empty);
}
// Don't try to change it if the new limit is the same
if ((!_requestQueueLength.HasValue && limit == DefaultRequestQueueLength)
|| (_requestQueueLength.HasValue && limit == _requestQueueLength.Value))
{
return;
}
_requestQueueLength = limit;
_requestQueue.SetLengthLimit(_requestQueueLength.Value);
}
/// <summary> /// <summary>
/// Start accepting incoming requests. /// Start accepting incoming requests.
/// </summary> /// </summary>
@ -218,7 +152,7 @@ namespace Microsoft.Net.Http.Server
{ {
CheckDisposed(); CheckDisposed();
LogHelper.LogInfo(_logger, "Start"); LogHelper.LogInfo(Logger, "Start");
// Make sure there are no race conditions between Start/Stop/Abort/Close/Dispose. // Make sure there are no race conditions between Start/Stop/Abort/Close/Dispose.
// Start needs to setup all resources. Abort/Stop must not interfere while Start is // Start needs to setup all resources. Abort/Stop must not interfere while Start is
@ -233,12 +167,16 @@ namespace Microsoft.Net.Http.Server
return; return;
} }
Settings.Authentication.SetUrlGroupSecurity(UrlGroup);
Settings.Timeouts.SetUrlGroupTimeouts(UrlGroup);
Settings.SetRequestQueueLimit(RequestQueue);
_requestQueue.AttachToUrlGroup(); _requestQueue.AttachToUrlGroup();
// All resources are set up correctly. Now add all prefixes. // All resources are set up correctly. Now add all prefixes.
try try
{ {
_urlPrefixes.RegisterAllPrefixes(); Settings.UrlPrefixes.RegisterAllPrefixes(UrlGroup);
} }
catch (WebListenerException) catch (WebListenerException)
{ {
@ -254,7 +192,7 @@ namespace Microsoft.Net.Http.Server
// Make sure the HttpListener instance can't be used if Start() failed. // Make sure the HttpListener instance can't be used if Start() failed.
_state = State.Disposed; _state = State.Disposed;
DisposeInternal(); DisposeInternal();
LogHelper.LogException(_logger, "Start", exception); LogHelper.LogException(Logger, "Start", exception);
throw; throw;
} }
} }
@ -272,7 +210,7 @@ namespace Microsoft.Net.Http.Server
return; return;
} }
_urlPrefixes.UnregisterAllPrefixes(); Settings.UrlPrefixes.UnregisterAllPrefixes();
_state = State.Stopped; _state = State.Stopped;
@ -281,7 +219,7 @@ namespace Microsoft.Net.Http.Server
} }
catch (Exception exception) catch (Exception exception)
{ {
LogHelper.LogException(_logger, "Stop", exception); LogHelper.LogException(Logger, "Stop", exception);
throw; throw;
} }
} }
@ -309,14 +247,14 @@ namespace Microsoft.Net.Http.Server
{ {
return; return;
} }
LogHelper.LogInfo(_logger, "Dispose"); LogHelper.LogInfo(Logger, "Dispose");
Stop(); Stop();
DisposeInternal(); DisposeInternal();
} }
catch (Exception exception) catch (Exception exception)
{ {
LogHelper.LogException(_logger, "Dispose", exception); LogHelper.LogException(Logger, "Dispose", exception);
throw; throw;
} }
finally finally
@ -371,7 +309,7 @@ namespace Microsoft.Net.Http.Server
} }
catch (Exception exception) catch (Exception exception)
{ {
LogHelper.LogException(_logger, "GetContextAsync", exception); LogHelper.LogException(Logger, "GetContextAsync", exception);
throw; throw;
} }
@ -392,10 +330,10 @@ namespace Microsoft.Net.Http.Server
internal unsafe bool ValidateAuth(NativeRequestContext requestMemory) internal unsafe bool ValidateAuth(NativeRequestContext requestMemory)
{ {
var requestV2 = (HttpApi.HTTP_REQUEST_V2*)requestMemory.RequestBlob; var requestV2 = (HttpApi.HTTP_REQUEST_V2*)requestMemory.RequestBlob;
if (!AuthenticationManager.AllowAnonymous && !AuthenticationManager.CheckAuthenticated(requestV2->pRequestInfo)) if (!Settings.Authentication.AllowAnonymous && !AuthenticationManager.CheckAuthenticated(requestV2->pRequestInfo))
{ {
SendError(requestMemory.RequestBlob->RequestId, HttpStatusCode.Unauthorized, SendError(requestMemory.RequestBlob->RequestId, HttpStatusCode.Unauthorized,
AuthenticationManager.GenerateChallenges(AuthenticationManager.AuthenticationSchemes)); AuthenticationManager.GenerateChallenges(Settings.Authentication.Schemes));
return false; return false;
} }
return true; return true;
@ -509,7 +447,7 @@ namespace Microsoft.Net.Http.Server
} }
} }
internal void CheckDisposed() private void CheckDisposed()
{ {
if (_state == State.Disposed) if (_state == State.Disposed)
{ {

View File

@ -0,0 +1,114 @@
// Copyright (c) Microsoft Open Technologies, Inc.
// All Rights Reserved
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF
// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR
// NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing
// permissions and limitations under the License.
using System;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions.Internal;
namespace Microsoft.Net.Http.Server
{
public class WebListenerSettings
{
private const long DefaultRequestQueueLength = 1000; // Http.sys default.
// The native request queue
private long _requestQueueLength = DefaultRequestQueueLength;
private RequestQueue _requestQueue;
private ILogger _logger = NullLogger.Instance;
public WebListenerSettings()
{
}
/// <summary>
/// The logger that will be used to create the WebListener instance. This should not be changed
/// after creating the listener.
/// </summary>
public ILogger Logger
{
get { return _logger; }
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_logger = value;
}
}
/// <summary>
/// The url prefixes to register with Http.Sys. These may be modified at any time prior to disposing
/// the listener.
/// </summary>
public UrlPrefixCollection UrlPrefixes { get; } = new UrlPrefixCollection();
/// <summary>
/// Http.Sys authentication settings. These may be modified at any time prior to disposing
/// the listener.
/// </summary>
public AuthenticationManager Authentication { get; } = new AuthenticationManager();
/// <summary>
/// Exposes the Http.Sys timeout configurations. These may also be configured in the registry.
/// These may be modified at any time prior to disposing the listener.
/// </summary>
public TimeoutManager Timeouts { get; } = new TimeoutManager();
// TODO: https://github.com/aspnet/WebListener/issues/173
/// <summary>
/// Gets or Sets if response body writes that fail due to client disconnects should throw exceptions or
/// complete normally. The default is true.
/// </summary>
internal bool IgnoreWriteExceptions { get; set; } = true;
/// <summary>
/// Gets or sets the maximum number of requests that will be queued up in Http.Sys.
/// </summary>
public long RequestQueueLimit
{
get
{
return _requestQueueLength;
}
set
{
if (value <= 0)
{
throw new ArgumentOutOfRangeException(nameof(value), value, string.Empty);
}
if (_requestQueue != null)
{
_requestQueue.SetLengthLimit(_requestQueueLength);
}
// Only store it if it succeeds or hasn't started yet
_requestQueueLength = value;
}
}
internal void SetRequestQueueLimit(RequestQueue requestQueue)
{
_requestQueue = requestQueue;
if (_requestQueueLength != DefaultRequestQueueLength)
{
_requestQueue.SetLengthLimit(_requestQueueLength);
}
}
}
}

View File

@ -197,7 +197,7 @@ namespace Microsoft.AspNetCore.Server.WebListener
foreach (string path in new[] { "/", "/11", "/2/3", "/2", "/11/2" }) foreach (string path in new[] { "/", "/11", "/2/3", "/2", "/11/2" })
{ {
server.Listener.UrlPrefixes.Add(UrlPrefix.Create(rootUri.Scheme, rootUri.Host, rootUri.Port, path)); server.Listener.Settings.UrlPrefixes.Add(UrlPrefix.Create(rootUri.Scheme, rootUri.Host, rootUri.Port, path));
} }
server.Start(new DummyApplication(app)); server.Start(new DummyApplication(app));

View File

@ -256,8 +256,8 @@ namespace Microsoft.AspNetCore.Server.WebListener
using (Utilities.CreateHttpServer(out address, httpContext => Task.FromResult(0))) { } using (Utilities.CreateHttpServer(out address, httpContext => Task.FromResult(0))) { }
var server = new MessagePump(Options.Create(new WebListenerOptions()), new LoggerFactory()); var server = new MessagePump(Options.Create(new WebListenerOptions()), new LoggerFactory());
server.Listener.UrlPrefixes.Add(UrlPrefix.Create(address)); server.Listener.Settings.UrlPrefixes.Add(UrlPrefix.Create(address));
server.Listener.SetRequestQueueLimit(1001); server.Listener.Settings.RequestQueueLimit = 1001;
using (server) using (server)
{ {

View File

@ -67,8 +67,8 @@ namespace Microsoft.AspNetCore.Server.WebListener
var server = new MessagePump(Options.Create(new WebListenerOptions()), new LoggerFactory()); var server = new MessagePump(Options.Create(new WebListenerOptions()), new LoggerFactory());
server.Features.Get<IServerAddressesFeature>().Addresses.Add(baseAddress); server.Features.Get<IServerAddressesFeature>().Addresses.Add(baseAddress);
server.Listener.AuthenticationManager.AuthenticationSchemes = authType; server.Listener.Settings.Authentication.Schemes = authType;
server.Listener.AuthenticationManager.AllowAnonymous = allowAnonymous; server.Listener.Settings.Authentication.AllowAnonymous = allowAnonymous;
try try
{ {
server.Start(new DummyApplication(app)); server.Start(new DummyApplication(app));

View File

@ -136,7 +136,7 @@ namespace Microsoft.Net.Http.Server
var uriBuilder = new UriBuilder(root); var uriBuilder = new UriBuilder(root);
foreach (string path in new[] { "/", "/11", "/2/3", "/2", "/11/2" }) foreach (string path in new[] { "/", "/11", "/2/3", "/2", "/11/2" })
{ {
server.UrlPrefixes.Add(UrlPrefix.Create(uriBuilder.Scheme, uriBuilder.Host, uriBuilder.Port, path)); server.Settings.UrlPrefixes.Add(UrlPrefix.Create(uriBuilder.Scheme, uriBuilder.Host, uriBuilder.Port, path));
} }
server.Start(); server.Start();

View File

@ -171,7 +171,7 @@ namespace Microsoft.Net.Http.Server
string address; string address;
using (var server = Utilities.CreateHttpServer(out address)) using (var server = Utilities.CreateHttpServer(out address))
{ {
server.SetRequestQueueLimit(1001); server.Settings.RequestQueueLimit = 1001;
var responseTask = SendRequestAsync(address); var responseTask = SendRequestAsync(address);
var context = await server.AcceptAsync(); var context = await server.AcceptAsync();
@ -199,7 +199,7 @@ namespace Microsoft.Net.Http.Server
Assert.Equal(string.Empty, response); Assert.Equal(string.Empty, response);
address += "pathbase/"; address += "pathbase/";
server.UrlPrefixes.Add(address); server.Settings.UrlPrefixes.Add(address);
responseTask = SendRequestAsync(address); responseTask = SendRequestAsync(address);
@ -220,7 +220,7 @@ namespace Microsoft.Net.Http.Server
using (var server = Utilities.CreateHttpServer(out address)) using (var server = Utilities.CreateHttpServer(out address))
{ {
address += "pathbase/"; address += "pathbase/";
server.UrlPrefixes.Add(address); server.Settings.UrlPrefixes.Add(address);
var responseTask = SendRequestAsync(address); var responseTask = SendRequestAsync(address);
var context = await server.AcceptAsync(); var context = await server.AcceptAsync();
@ -231,7 +231,7 @@ namespace Microsoft.Net.Http.Server
var response = await responseTask; var response = await responseTask;
Assert.Equal(string.Empty, response); Assert.Equal(string.Empty, response);
Assert.True(server.UrlPrefixes.Remove(address)); Assert.True(server.Settings.UrlPrefixes.Remove(address));
responseTask = SendRequestAsync(address); responseTask = SendRequestAsync(address);

View File

@ -17,8 +17,8 @@ namespace Microsoft.Net.Http.Server
internal static WebListener CreateHttpAuthServer(AuthenticationSchemes authScheme, bool allowAnonymos, out string baseAddress) internal static WebListener CreateHttpAuthServer(AuthenticationSchemes authScheme, bool allowAnonymos, out string baseAddress)
{ {
var listener = CreateHttpServer(out baseAddress); var listener = CreateHttpServer(out baseAddress);
listener.AuthenticationManager.AuthenticationSchemes = authScheme; listener.Settings.Authentication.Schemes = authScheme;
listener.AuthenticationManager.AllowAnonymous = allowAnonymos; listener.Settings.Authentication.AllowAnonymous = allowAnonymos;
return listener; return listener;
} }
@ -45,7 +45,7 @@ namespace Microsoft.Net.Http.Server
root = prefix.Scheme + "://" + prefix.Host + ":" + prefix.Port; root = prefix.Scheme + "://" + prefix.Host + ":" + prefix.Port;
baseAddress = prefix.ToString(); baseAddress = prefix.ToString();
var listener = new WebListener(); var listener = new WebListener();
listener.UrlPrefixes.Add(prefix); listener.Settings.UrlPrefixes.Add(prefix);
try try
{ {
listener.Start(); listener.Start();
@ -61,7 +61,6 @@ namespace Microsoft.Net.Http.Server
throw new Exception("Failed to locate a free port."); throw new Exception("Failed to locate a free port.");
} }
internal static WebListener CreateHttpsServer() internal static WebListener CreateHttpsServer()
{ {
return CreateServer("https", "localhost", 9090, string.Empty); return CreateServer("https", "localhost", 9090, string.Empty);
@ -70,7 +69,7 @@ namespace Microsoft.Net.Http.Server
internal static WebListener CreateServer(string scheme, string host, int port, string path) internal static WebListener CreateServer(string scheme, string host, int port, string path)
{ {
WebListener listener = new WebListener(); WebListener listener = new WebListener();
listener.UrlPrefixes.Add(UrlPrefix.Create(scheme, host, port, path)); listener.Settings.UrlPrefixes.Add(UrlPrefix.Create(scheme, host, port, path));
listener.Start(); listener.Start();
return listener; return listener;
} }