Refactoring IServerFactory #395

This commit is contained in:
John Luo 2015-10-20 15:43:01 -07:00
parent 21373740c7
commit 3933a1904e
11 changed files with 240 additions and 151 deletions

View File

@ -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.
using System;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Features;
namespace Microsoft.AspNet.Hosting.Server
{
/// <summary>
/// Represents a server.
/// </summary>
public interface IServer : IDisposable
{
/// <summary>
/// A collection of HTTP features of the server.
/// </summary>
IFeatureCollection Features { get; }
/// <summary>
/// Start the server with the given function that processes an HTTP request.
/// </summary>
/// <param name="requestDelegate">A function that processes an HTTP request.</param>
void Start(RequestDelegate requestDelegate);
}
}

View File

@ -1,16 +1,20 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Http.Features;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
namespace Microsoft.AspNet.Hosting.Server namespace Microsoft.AspNet.Hosting.Server
{ {
/// <summary>
/// Represents a factory for creating servers.
/// </summary>
public interface IServerFactory public interface IServerFactory
{ {
IFeatureCollection Initialize(IConfiguration configuration); /// <summary>
IDisposable Start(IFeatureCollection serverFeatures, Func<IFeatureCollection, Task> application); /// Creates <see cref="IServer"/> based on the given configuration.
/// </summary>
/// <param name="configuration">An instance of <see cref="IConfiguration"/>.</param>
/// <returns>The created server.</returns>
IServer CreateServer(IConfiguration configuration);
} }
} }

View File

@ -6,11 +6,11 @@
"url": "git://github.com/aspnet/hosting" "url": "git://github.com/aspnet/hosting"
}, },
"dependencies": { "dependencies": {
"Microsoft.AspNet.Http.Features": "1.0.0-*", "Microsoft.AspNet.Http.Abstractions": "1.0.0-*",
"Microsoft.Extensions.Configuration.Abstractions": "1.0.0-*" "Microsoft.Extensions.Configuration.Abstractions": "1.0.0-*"
}, },
"frameworks": { "frameworks": {
"net451": {}, "net451": {},
"dotnet5.4": {} "dotnet5.4": {}
} }
} }

View File

@ -42,7 +42,7 @@ namespace Microsoft.AspNet.Hosting.Internal
// Only one of these should be set // Only one of these should be set
internal IServerFactory ServerFactory { get; set; } internal IServerFactory ServerFactory { get; set; }
internal string ServerFactoryLocation { get; set; } internal string ServerFactoryLocation { get; set; }
private IFeatureCollection _serverFeatures; internal IServer Server { get; set; }
public HostingEngine( public HostingEngine(
IServiceCollection appServices, IServiceCollection appServices,
@ -87,15 +87,13 @@ namespace Microsoft.AspNet.Hosting.Internal
var application = BuildApplication(); var application = BuildApplication();
var logger = _applicationServices.GetRequiredService<ILogger<HostingEngine>>(); var logger = _applicationServices.GetRequiredService<ILogger<HostingEngine>>();
var contextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>();
var diagnosticSource = _applicationServices.GetRequiredService<DiagnosticSource>(); var diagnosticSource = _applicationServices.GetRequiredService<DiagnosticSource>();
logger.Starting(); logger.Starting();
var server = ServerFactory.Start(_serverFeatures, Server.Start(
async features => async httpContext =>
{ {
var httpContext = contextFactory.Create(features);
httpContext.ApplicationServices = _applicationServices; httpContext.ApplicationServices = _applicationServices;
if (diagnosticSource.IsEnabled("Microsoft.AspNet.Hosting.BeginRequest")) if (diagnosticSource.IsEnabled("Microsoft.AspNet.Hosting.BeginRequest"))
@ -135,11 +133,11 @@ namespace Microsoft.AspNet.Hosting.Internal
_applicationLifetime.NotifyStarted(); _applicationLifetime.NotifyStarted();
logger.Started(); logger.Started();
return new Application(ApplicationServices, _serverFeatures, new Disposable(() => return new Application(ApplicationServices, Server.Features, new Disposable(() =>
{ {
logger.Shutdown(); logger.Shutdown();
_applicationLifetime.StopApplication(); _applicationLifetime.StopApplication();
server.Dispose(); Server.Dispose();
_applicationLifetime.NotifyStopped(); _applicationLifetime.NotifyStopped();
(_applicationServices as IDisposable)?.Dispose(); (_applicationServices as IDisposable)?.Dispose();
})); }));
@ -191,7 +189,7 @@ namespace Microsoft.AspNet.Hosting.Internal
EnsureServer(); EnsureServer();
var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>(); var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
var builder = builderFactory.CreateBuilder(_serverFeatures); var builder = builderFactory.CreateBuilder(Server.Features);
builder.ApplicationServices = _applicationServices; builder.ApplicationServices = _applicationServices;
var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>(); var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>();
@ -246,21 +244,21 @@ namespace Microsoft.AspNet.Hosting.Internal
private void EnsureServer() private void EnsureServer()
{ {
if (ServerFactory == null) if (Server == null)
{ {
// Blow up if we don't have a server set at this point if (ServerFactory == null)
if (ServerFactoryLocation == null)
{ {
throw new InvalidOperationException("IHostingBuilder.UseServer() is required for " + nameof(Start) + "()"); // Blow up if we don't have a server set at this point
if (ServerFactoryLocation == null)
{
throw new InvalidOperationException("IHostingBuilder.UseServer() is required for " + nameof(Start) + "()");
}
ServerFactory = _applicationServices.GetRequiredService<IServerLoader>().LoadServerFactory(ServerFactoryLocation);
} }
ServerFactory = _applicationServices.GetRequiredService<IServerLoader>().LoadServerFactory(ServerFactoryLocation); Server = ServerFactory.CreateServer(_config);
} var addresses = Server.Features?.Get<IServerAddressesFeature>()?.Addresses;
if (_serverFeatures == null)
{
_serverFeatures = ServerFactory.Initialize(_config);
var addresses = _serverFeatures?.Get<IServerAddressesFeature>()?.Addresses;
if (addresses != null && !addresses.IsReadOnly) if (addresses != null && !addresses.IsReadOnly)
{ {
var port = _config[ServerPort]; var port = _config[ServerPort];

View File

@ -42,6 +42,7 @@ namespace Microsoft.AspNet.Hosting
// Only one of these should be set // Only one of these should be set
private string _serverFactoryLocation; private string _serverFactoryLocation;
private IServerFactory _serverFactory; private IServerFactory _serverFactory;
private IServer _server;
public WebHostBuilder() public WebHostBuilder()
: this(config: new ConfigurationBuilder().Build()) : this(config: new ConfigurationBuilder().Build())
@ -133,6 +134,7 @@ namespace Microsoft.AspNet.Hosting
var engine = new HostingEngine(hostingServices, startupLoader, _config, _captureStartupErrors); var engine = new HostingEngine(hostingServices, startupLoader, _config, _captureStartupErrors);
// Only one of these should be set, but they are used in priority // Only one of these should be set, but they are used in priority
engine.Server = _server;
engine.ServerFactory = _serverFactory; engine.ServerFactory = _serverFactory;
engine.ServerFactoryLocation = _config[ServerKey] ?? _config[OldServerKey] ?? _serverFactoryLocation; engine.ServerFactoryLocation = _config[ServerKey] ?? _config[OldServerKey] ?? _serverFactoryLocation;
@ -161,7 +163,18 @@ namespace Microsoft.AspNet.Hosting
return this; return this;
} }
public WebHostBuilder UseServer(string assemblyName) public WebHostBuilder UseServer(IServer server)
{
if (server == null)
{
throw new ArgumentNullException(nameof(server));
}
_server = server;
return this;
}
public WebHostBuilder UseServerFactory(string assemblyName)
{ {
if (assemblyName == null) if (assemblyName == null)
{ {
@ -172,8 +185,13 @@ namespace Microsoft.AspNet.Hosting
return this; return this;
} }
public WebHostBuilder UseServer(IServerFactory factory) public WebHostBuilder UseServerFactory(IServerFactory factory)
{ {
if (factory == null)
{
throw new ArgumentNullException(nameof(factory));
}
_serverFactory = factory; _serverFactory = factory;
return this; return this;
} }

View File

@ -23,21 +23,27 @@ namespace Microsoft.AspNet.TestHost
/// </summary> /// </summary>
public class ClientHandler : HttpMessageHandler public class ClientHandler : HttpMessageHandler
{ {
private readonly Func<IFeatureCollection, Task> _next; private readonly RequestDelegate _next;
private readonly PathString _pathBase; private readonly PathString _pathBase;
private readonly IHttpContextFactory _factory;
/// <summary> /// <summary>
/// Create a new handler. /// Create a new handler.
/// </summary> /// </summary>
/// <param name="next">The pipeline entry point.</param> /// <param name="next">The pipeline entry point.</param>
public ClientHandler(Func<IFeatureCollection, Task> next, PathString pathBase) public ClientHandler(RequestDelegate next, PathString pathBase, IHttpContextFactory httpContextFactory)
{ {
if (next == null) if (next == null)
{ {
throw new ArgumentNullException(nameof(next)); throw new ArgumentNullException(nameof(next));
} }
if (httpContextFactory == null)
{
throw new ArgumentNullException(nameof(httpContextFactory));
}
_next = next; _next = next;
_factory = httpContextFactory;
// PathString.StartsWithSegments that we use below requires the base path to not end in a slash. // PathString.StartsWithSegments that we use below requires the base path to not end in a slash.
if (pathBase.HasValue && pathBase.Value.EndsWith("/")) if (pathBase.HasValue && pathBase.Value.EndsWith("/"))
@ -63,7 +69,7 @@ namespace Microsoft.AspNet.TestHost
throw new ArgumentNullException(nameof(request)); throw new ArgumentNullException(nameof(request));
} }
var state = new RequestState(request, _pathBase); var state = new RequestState(request, _pathBase, _factory);
var requestContent = request.Content ?? new StreamContent(Stream.Null); var requestContent = request.Content ?? new StreamContent(Stream.Null);
var body = await requestContent.ReadAsStreamAsync(); var body = await requestContent.ReadAsStreamAsync();
if (body.CanSeek) if (body.CanSeek)
@ -79,7 +85,7 @@ namespace Microsoft.AspNet.TestHost
{ {
try try
{ {
await _next(state.HttpContext.Features); await _next(state.HttpContext);
state.CompleteResponse(); state.CompleteResponse();
} }
catch (Exception ex) catch (Exception ex)
@ -88,6 +94,7 @@ namespace Microsoft.AspNet.TestHost
} }
finally finally
{ {
state.ServerCleanup();
registration.Dispose(); registration.Dispose();
} }
}); });
@ -102,14 +109,16 @@ namespace Microsoft.AspNet.TestHost
private ResponseStream _responseStream; private ResponseStream _responseStream;
private ResponseFeature _responseFeature; private ResponseFeature _responseFeature;
private CancellationTokenSource _requestAbortedSource; private CancellationTokenSource _requestAbortedSource;
private IHttpContextFactory _factory;
private bool _pipelineFinished; private bool _pipelineFinished;
internal RequestState(HttpRequestMessage request, PathString pathBase) internal RequestState(HttpRequestMessage request, PathString pathBase, IHttpContextFactory factory)
{ {
_request = request; _request = request;
_responseTcs = new TaskCompletionSource<HttpResponseMessage>(); _responseTcs = new TaskCompletionSource<HttpResponseMessage>();
_requestAbortedSource = new CancellationTokenSource(); _requestAbortedSource = new CancellationTokenSource();
_pipelineFinished = false; _pipelineFinished = false;
_factory = factory;
if (request.RequestUri.IsDefaultPort) if (request.RequestUri.IsDefaultPort)
{ {
@ -120,7 +129,8 @@ namespace Microsoft.AspNet.TestHost
request.Headers.Host = request.RequestUri.GetComponents(UriComponents.HostAndPort, UriFormat.UriEscaped); request.Headers.Host = request.RequestUri.GetComponents(UriComponents.HostAndPort, UriFormat.UriEscaped);
} }
HttpContext = new DefaultHttpContext(); HttpContext = _factory.Create(new FeatureCollection());
HttpContext.Features.Set<IHttpRequestFeature>(new RequestFeature()); HttpContext.Features.Set<IHttpRequestFeature>(new RequestFeature());
_responseFeature = new ResponseFeature(); _responseFeature = new ResponseFeature();
HttpContext.Features.Set<IHttpResponseFeature>(_responseFeature); HttpContext.Features.Set<IHttpResponseFeature>(_responseFeature);
@ -228,6 +238,14 @@ namespace Microsoft.AspNet.TestHost
_responseStream.Abort(exception); _responseStream.Abort(exception);
_responseTcs.TrySetException(exception); _responseTcs.TrySetException(exception);
} }
internal void ServerCleanup()
{
if (HttpContext != null)
{
_factory.Dispose(HttpContext);
}
}
} }
} }
} }

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
@ -14,22 +14,26 @@ using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNet.TestHost namespace Microsoft.AspNet.TestHost
{ {
public class TestServer : IServerFactory, IDisposable public class TestServer : IServer
{ {
private const string DefaultEnvironmentName = "Development"; private const string DefaultEnvironmentName = "Development";
private const string ServerName = nameof(TestServer); private const string ServerName = nameof(TestServer);
private static readonly IFeatureCollection ServerInfo = new FeatureCollection(); private RequestDelegate _appDelegate;
private Func<IFeatureCollection, Task> _appDelegate;
private IDisposable _appInstance; private IDisposable _appInstance;
private bool _disposed = false; private bool _disposed = false;
private IHttpContextFactory _httpContextFactory;
public TestServer(WebHostBuilder builder) public TestServer(WebHostBuilder builder)
{ {
_appInstance = builder.UseServer(this).Build().Start(); var hostingEngine = builder.UseServer(this).Build();
_httpContextFactory = hostingEngine.ApplicationServices.GetService<IHttpContextFactory>();
_appInstance = hostingEngine.Start();
} }
public Uri BaseAddress { get; set; } = new Uri("http://localhost/"); public Uri BaseAddress { get; set; } = new Uri("http://localhost/");
IFeatureCollection IServer.Features { get; }
public static TestServer Create() public static TestServer Create()
{ {
return Create(config: null, configureApp: null, configureServices: null); return Create(config: null, configureApp: null, configureServices: null);
@ -95,7 +99,7 @@ namespace Microsoft.AspNet.TestHost
public HttpMessageHandler CreateHandler() public HttpMessageHandler CreateHandler()
{ {
var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress); var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress);
return new ClientHandler(Invoke, pathBase); return new ClientHandler(Invoke, pathBase, _httpContextFactory);
} }
public HttpClient CreateClient() public HttpClient CreateClient()
@ -106,7 +110,7 @@ namespace Microsoft.AspNet.TestHost
public WebSocketClient CreateWebSocketClient() public WebSocketClient CreateWebSocketClient()
{ {
var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress); var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress);
return new WebSocketClient(Invoke, pathBase); return new WebSocketClient(Invoke, pathBase, _httpContextFactory);
} }
/// <summary> /// <summary>
@ -119,25 +123,13 @@ namespace Microsoft.AspNet.TestHost
return new RequestBuilder(this, path); return new RequestBuilder(this, path);
} }
public IFeatureCollection Initialize(IConfiguration configuration) public Task Invoke(HttpContext context)
{
return ServerInfo;
}
public IDisposable Start(IFeatureCollection serverInformation, Func<IFeatureCollection, Task> application)
{
_appDelegate = application;
return this;
}
public Task Invoke(IFeatureCollection featureCollection)
{ {
if (_disposed) if (_disposed)
{ {
throw new ObjectDisposedException(GetType().FullName); throw new ObjectDisposedException(GetType().FullName);
} }
return _appDelegate(featureCollection); return _appDelegate(context);
} }
public void Dispose() public void Dispose()
@ -145,5 +137,10 @@ namespace Microsoft.AspNet.TestHost
_disposed = true; _disposed = true;
_appInstance.Dispose(); _appInstance.Dispose();
} }
void IServer.Start(RequestDelegate requestDelegate)
{
_appDelegate = requestDelegate;
}
} }
} }

View File

@ -8,25 +8,31 @@ using System.Net.WebSockets;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Http.Internal;
namespace Microsoft.AspNet.TestHost namespace Microsoft.AspNet.TestHost
{ {
public class WebSocketClient public class WebSocketClient
{ {
private readonly Func<IFeatureCollection, Task> _next; private readonly RequestDelegate _next;
private readonly PathString _pathBase; private readonly PathString _pathBase;
private readonly IHttpContextFactory _httpContextFactory;
internal WebSocketClient(Func<IFeatureCollection, Task> next, PathString pathBase) internal WebSocketClient(RequestDelegate next, PathString pathBase, IHttpContextFactory httpContextFactory)
{ {
if (next == null) if (next == null)
{ {
throw new ArgumentNullException(nameof(next)); throw new ArgumentNullException(nameof(next));
} }
if (httpContextFactory == null)
{
throw new ArgumentNullException(nameof(httpContextFactory));
}
_next = next; _next = next;
_httpContextFactory = httpContextFactory;
// PathString.StartsWithSegments that we use below requires the base path to not end in a slash. // PathString.StartsWithSegments that we use below requires the base path to not end in a slash.
if (pathBase.HasValue && pathBase.Value.EndsWith("/")) if (pathBase.HasValue && pathBase.Value.EndsWith("/"))
@ -52,7 +58,7 @@ namespace Microsoft.AspNet.TestHost
public async Task<WebSocket> ConnectAsync(Uri uri, CancellationToken cancellationToken) public async Task<WebSocket> ConnectAsync(Uri uri, CancellationToken cancellationToken)
{ {
var state = new RequestState(uri, _pathBase, cancellationToken); var state = new RequestState(uri, _pathBase, cancellationToken, _httpContextFactory);
if (ConfigureRequest != null) if (ConfigureRequest != null)
{ {
@ -64,7 +70,7 @@ namespace Microsoft.AspNet.TestHost
{ {
try try
{ {
await _next(state.FeatureCollection); await _next(state.HttpContext);
state.PipelineComplete(); state.PipelineComplete();
} }
catch (Exception ex) catch (Exception ex)
@ -84,18 +90,18 @@ namespace Microsoft.AspNet.TestHost
{ {
private TaskCompletionSource<WebSocket> _clientWebSocketTcs; private TaskCompletionSource<WebSocket> _clientWebSocketTcs;
private WebSocket _serverWebSocket; private WebSocket _serverWebSocket;
private IHttpContextFactory _factory;
public IFeatureCollection FeatureCollection { get; private set; }
public HttpContext HttpContext { get; private set; } public HttpContext HttpContext { get; private set; }
public Task<WebSocket> WebSocketTask { get { return _clientWebSocketTcs.Task; } } public Task<WebSocket> WebSocketTask { get { return _clientWebSocketTcs.Task; } }
public RequestState(Uri uri, PathString pathBase, CancellationToken cancellationToken) public RequestState(Uri uri, PathString pathBase, CancellationToken cancellationToken, IHttpContextFactory factory)
{ {
_factory = factory;
_clientWebSocketTcs = new TaskCompletionSource<WebSocket>(); _clientWebSocketTcs = new TaskCompletionSource<WebSocket>();
// HttpContext // HttpContext
FeatureCollection = new FeatureCollection(); HttpContext = _factory.Create(new FeatureCollection());
HttpContext = new DefaultHttpContext(FeatureCollection);
// Request // Request
HttpContext.Features.Set<IHttpRequestFeature>(new RequestFeature()); HttpContext.Features.Set<IHttpRequestFeature>(new RequestFeature());
@ -147,6 +153,10 @@ namespace Microsoft.AspNet.TestHost
public void Dispose() public void Dispose()
{ {
if (HttpContext != null)
{
_factory.Dispose(HttpContext);
}
if (_serverWebSocket != null) if (_serverWebSocket != null)
{ {
_serverWebSocket.Dispose(); _serverWebSocket.Dispose();

View File

@ -14,6 +14,7 @@ using Microsoft.AspNet.Hosting.Server;
using Microsoft.AspNet.Hosting.Startup; using Microsoft.AspNet.Hosting.Startup;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Features; using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Server.Features; using Microsoft.AspNet.Server.Features;
using Microsoft.AspNet.Testing.xunit; using Microsoft.AspNet.Testing.xunit;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
@ -26,10 +27,33 @@ using Xunit;
namespace Microsoft.AspNet.Hosting namespace Microsoft.AspNet.Hosting
{ {
public class HostingEngineTests : IServerFactory public class HostingEngineTests : IServerFactory, IServer
{ {
private readonly IList<StartInstance> _startInstances = new List<StartInstance>(); private readonly IList<StartInstance> _startInstances = new List<StartInstance>();
private IFeatureCollection _featuresSupportedByThisHost = NewFeatureCollection(); private IFeatureCollection _featuresSupportedByThisHost = NewFeatureCollection();
private IFeatureCollection _instanceFeaturesSupportedByThisHost;
public IFeatureCollection Features {
get
{
var features = new FeatureCollection();
foreach (var feature in _featuresSupportedByThisHost)
{
features[feature.Key] = feature.Value;
}
if (_instanceFeaturesSupportedByThisHost != null)
{
foreach (var feature in _instanceFeaturesSupportedByThisHost)
{
features[feature.Key] = feature.Value;
}
}
return features;
}
}
static IFeatureCollection NewFeatureCollection() static IFeatureCollection NewFeatureCollection()
{ {
@ -431,26 +455,36 @@ namespace Microsoft.AspNet.Hosting
return new WebHostBuilder(config ?? new ConfigurationBuilder().Build()); return new WebHostBuilder(config ?? new ConfigurationBuilder().Build());
} }
public IFeatureCollection Initialize(IConfiguration configuration) public void Start(RequestDelegate requestDelegate)
{ {
var features = new FeatureCollection(); var startInstance = new StartInstance(requestDelegate);
features.Set<IServerAddressesFeature>(new ServerAddressesFeature());
return features;
}
public IDisposable Start(IFeatureCollection serverFeatures, Func<IFeatureCollection, Task> application)
{
var startInstance = new StartInstance(application);
_startInstances.Add(startInstance); _startInstances.Add(startInstance);
application(_featuresSupportedByThisHost); requestDelegate(new DefaultHttpContext(Features));
return startInstance;
} }
public class StartInstance : IDisposable public void Dispose()
{ {
private readonly Func<IFeatureCollection, Task> _application; if (_startInstances != null)
{
foreach (var startInstance in _startInstances)
{
startInstance.Dispose();
}
}
}
public StartInstance(Func<IFeatureCollection, Task> application) public IServer CreateServer(IConfiguration configuration)
{
_instanceFeaturesSupportedByThisHost = new FeatureCollection();
_instanceFeaturesSupportedByThisHost.Set<IServerAddressesFeature>(new ServerAddressesFeature());
return this;
}
private class StartInstance : IDisposable
{
private readonly RequestDelegate _application;
public StartInstance(RequestDelegate application)
{ {
_application = application; _application = application;
} }

View File

@ -1,13 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Hosting.Fakes; using Microsoft.AspNet.Hosting.Fakes;
using Microsoft.AspNet.Hosting.Internal; using Microsoft.AspNet.Hosting.Internal;
using Microsoft.AspNet.Hosting.Server; using Microsoft.AspNet.Hosting.Server;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Features; using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Http.Internal;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
@ -42,11 +42,11 @@ namespace Microsoft.AspNet.Hosting
public async Task StartupMissing_Fallback() public async Task StartupMissing_Fallback()
{ {
var builder = CreateWebHostBuilder(); var builder = CreateWebHostBuilder();
var serverFactory = new TestServerFactory(); var server = new TestServer();
var engine = builder.UseServer(serverFactory).UseStartup("MissingStartupAssembly").Build(); var engine = builder.UseServer(server).UseStartup("MissingStartupAssembly").Build();
using (engine.Start()) using (engine.Start())
{ {
await AssertResponseContains(serverFactory.Application, "MissingStartupAssembly"); await AssertResponseContains(server.RequestDelegate, "MissingStartupAssembly");
} }
} }
@ -54,11 +54,11 @@ namespace Microsoft.AspNet.Hosting
public async Task StartupStaticCtorThrows_Fallback() public async Task StartupStaticCtorThrows_Fallback()
{ {
var builder = CreateWebHostBuilder(); var builder = CreateWebHostBuilder();
var serverFactory = new TestServerFactory(); var server = new TestServer();
var engine = builder.UseServer(serverFactory).UseStartup<StartupStaticCtorThrows>().Build(); var engine = builder.UseServer(server).UseStartup<StartupStaticCtorThrows>().Build();
using (engine.Start()) using (engine.Start())
{ {
await AssertResponseContains(serverFactory.Application, "Exception from static constructor"); await AssertResponseContains(server.RequestDelegate, "Exception from static constructor");
} }
} }
@ -66,11 +66,11 @@ namespace Microsoft.AspNet.Hosting
public async Task StartupCtorThrows_Fallback() public async Task StartupCtorThrows_Fallback()
{ {
var builder = CreateWebHostBuilder(); var builder = CreateWebHostBuilder();
var serverFactory = new TestServerFactory(); var server = new TestServer();
var engine = builder.UseServer(serverFactory).UseStartup<StartupCtorThrows>().Build(); var engine = builder.UseServer(server).UseStartup<StartupCtorThrows>().Build();
using (engine.Start()) using (engine.Start())
{ {
await AssertResponseContains(serverFactory.Application, "Exception from constructor"); await AssertResponseContains(server.RequestDelegate, "Exception from constructor");
} }
} }
@ -78,11 +78,11 @@ namespace Microsoft.AspNet.Hosting
public async Task StartupCtorThrows_TypeLoadException() public async Task StartupCtorThrows_TypeLoadException()
{ {
var builder = CreateWebHostBuilder(); var builder = CreateWebHostBuilder();
var serverFactory = new TestServerFactory(); var server = new TestServer();
var engine = builder.UseServer(serverFactory).UseStartup<StartupThrowTypeLoadException>().Build(); var engine = builder.UseServer(server).UseStartup<StartupThrowTypeLoadException>().Build();
using (engine.Start()) using (engine.Start())
{ {
await AssertResponseContains(serverFactory.Application, "Message from the LoaderException</span>"); await AssertResponseContains(server.RequestDelegate, "Message from the LoaderException</span>");
} }
} }
@ -90,13 +90,13 @@ namespace Microsoft.AspNet.Hosting
public async Task IApplicationLifetimeRegisteredEvenWhenStartupCtorThrows_Fallback() public async Task IApplicationLifetimeRegisteredEvenWhenStartupCtorThrows_Fallback()
{ {
var builder = CreateWebHostBuilder(); var builder = CreateWebHostBuilder();
var serverFactory = new TestServerFactory(); var server = new TestServer();
var engine = builder.UseServer(serverFactory).UseStartup<StartupCtorThrows>().Build(); var engine = builder.UseServer(server).UseStartup<StartupCtorThrows>().Build();
using (engine.Start()) using (engine.Start())
{ {
var service = engine.ApplicationServices.GetService<IApplicationLifetime>(); var service = engine.ApplicationServices.GetServices<IApplicationLifetime>();
Assert.NotNull(service); Assert.NotNull(service);
await AssertResponseContains(serverFactory.Application, "Exception from constructor"); await AssertResponseContains(server.RequestDelegate, "Exception from constructor");
} }
} }
@ -104,11 +104,11 @@ namespace Microsoft.AspNet.Hosting
public async Task StartupConfigureServicesThrows_Fallback() public async Task StartupConfigureServicesThrows_Fallback()
{ {
var builder = CreateWebHostBuilder(); var builder = CreateWebHostBuilder();
var serverFactory = new TestServerFactory(); var server = new TestServer();
var engine = builder.UseServer(serverFactory).UseStartup<StartupConfigureServicesThrows>().Build(); var engine = builder.UseServer(server).UseStartup<StartupConfigureServicesThrows>().Build();
using (engine.Start()) using (engine.Start())
{ {
await AssertResponseContains(serverFactory.Application, "Exception from ConfigureServices"); await AssertResponseContains(server.RequestDelegate, "Exception from ConfigureServices");
} }
} }
@ -116,11 +116,11 @@ namespace Microsoft.AspNet.Hosting
public async Task StartupConfigureThrows_Fallback() public async Task StartupConfigureThrows_Fallback()
{ {
var builder = CreateWebHostBuilder(); var builder = CreateWebHostBuilder();
var serverFactory = new TestServerFactory(); var server = new TestServer();
var engine = builder.UseServer(serverFactory).UseStartup<StartupConfigureServicesThrows>().Build(); var engine = builder.UseServer(server).UseStartup<StartupConfigureServicesThrows>().Build();
using (engine.Start()) using (engine.Start())
{ {
await AssertResponseContains(serverFactory.Application, "Exception from Configure"); await AssertResponseContains(server.RequestDelegate, "Exception from Configure");
} }
} }
@ -137,36 +137,29 @@ namespace Microsoft.AspNet.Hosting
return new WebHostBuilder(config, captureStartupErrors: true); return new WebHostBuilder(config, captureStartupErrors: true);
} }
private async Task AssertResponseContains(Func<IFeatureCollection, Task> app, string expectedText) private async Task AssertResponseContains(RequestDelegate app, string expectedText)
{ {
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
httpContext.Response.Body = new MemoryStream(); httpContext.Response.Body = new MemoryStream();
await app(httpContext.Features); await app(httpContext);
httpContext.Response.Body.Seek(0, SeekOrigin.Begin); httpContext.Response.Body.Seek(0, SeekOrigin.Begin);
var bodyText = new StreamReader(httpContext.Response.Body).ReadToEnd(); var bodyText = new StreamReader(httpContext.Response.Body).ReadToEnd();
Assert.Contains(expectedText, bodyText); Assert.Contains(expectedText, bodyText);
} }
private class TestServerFactory : IServerFactory private class TestServer : IServer
{ {
public Func<IFeatureCollection, Task> Application { get; set; } IFeatureCollection IServer.Features { get; }
public RequestDelegate RequestDelegate { get; private set; }
public IFeatureCollection Initialize(IConfiguration configuration) public void Dispose()
{ {
return new FeatureCollection();
} }
public IDisposable Start(IFeatureCollection serverFeatures, Func<IFeatureCollection, Task> application) public void Start(RequestDelegate requestDelegate)
{ {
Application = application; RequestDelegate = requestDelegate;
return new Disposable();
}
private class Disposable : IDisposable
{
public void Dispose()
{
}
} }
} }
} }

View File

@ -17,13 +17,13 @@ namespace Microsoft.AspNet.TestHost
{ {
public class ClientHandlerTests public class ClientHandlerTests
{ {
private IHttpContextFactory _httpContextFactory = new HttpContextFactory(new HttpContextAccessor());
[Fact] [Fact]
public Task ExpectedKeysAreAvailable() public Task ExpectedKeysAreAvailable()
{ {
var handler = new ClientHandler(env => var handler = new ClientHandler(context =>
{ {
var context = new DefaultHttpContext((IFeatureCollection)env);
// TODO: Assert.True(context.RequestAborted.CanBeCanceled); // TODO: Assert.True(context.RequestAborted.CanBeCanceled);
Assert.Equal("HTTP/1.1", context.Request.Protocol); Assert.Equal("HTTP/1.1", context.Request.Protocol);
Assert.Equal("GET", context.Request.Method); Assert.Equal("GET", context.Request.Method);
@ -40,7 +40,7 @@ namespace Microsoft.AspNet.TestHost
Assert.Equal("example.com", context.Request.Host.Value); Assert.Equal("example.com", context.Request.Host.Value);
return Task.FromResult(0); return Task.FromResult(0);
}, new PathString("/A/Path/")); }, new PathString("/A/Path/"), _httpContextFactory);
var httpClient = new HttpClient(handler); var httpClient = new HttpClient(handler);
return httpClient.GetAsync("https://example.com/A/Path/and/file.txt?and=query"); return httpClient.GetAsync("https://example.com/A/Path/and/file.txt?and=query");
} }
@ -48,14 +48,13 @@ namespace Microsoft.AspNet.TestHost
[Fact] [Fact]
public Task SingleSlashNotMovedToPathBase() public Task SingleSlashNotMovedToPathBase()
{ {
var handler = new ClientHandler(env => var handler = new ClientHandler(context =>
{ {
var context = new DefaultHttpContext((IFeatureCollection)env);
Assert.Equal("", context.Request.PathBase.Value); Assert.Equal("", context.Request.PathBase.Value);
Assert.Equal("/", context.Request.Path.Value); Assert.Equal("/", context.Request.Path.Value);
return Task.FromResult(0); return Task.FromResult(0);
}, new PathString("")); }, new PathString(""), _httpContextFactory);
var httpClient = new HttpClient(handler); var httpClient = new HttpClient(handler);
return httpClient.GetAsync("https://example.com/"); return httpClient.GetAsync("https://example.com/");
} }
@ -64,15 +63,14 @@ namespace Microsoft.AspNet.TestHost
public async Task ResubmitRequestWorks() public async Task ResubmitRequestWorks()
{ {
int requestCount = 1; int requestCount = 1;
var handler = new ClientHandler(env => var handler = new ClientHandler(context =>
{ {
var context = new DefaultHttpContext((IFeatureCollection)env);
int read = context.Request.Body.Read(new byte[100], 0, 100); int read = context.Request.Body.Read(new byte[100], 0, 100);
Assert.Equal(11, read); Assert.Equal(11, read);
context.Response.Headers["TestHeader"] = "TestValue:" + requestCount++; context.Response.Headers["TestHeader"] = "TestValue:" + requestCount++;
return Task.FromResult(0); return Task.FromResult(0);
}, PathString.Empty); }, PathString.Empty, _httpContextFactory);
HttpMessageInvoker invoker = new HttpMessageInvoker(handler); HttpMessageInvoker invoker = new HttpMessageInvoker(handler);
HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Post, "https://example.com/"); HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Post, "https://example.com/");
@ -88,13 +86,11 @@ namespace Microsoft.AspNet.TestHost
[Fact] [Fact]
public async Task MiddlewareOnlySetsHeaders() public async Task MiddlewareOnlySetsHeaders()
{ {
var handler = new ClientHandler(env => var handler = new ClientHandler(context =>
{ {
var context = new DefaultHttpContext((IFeatureCollection)env);
context.Response.Headers["TestHeader"] = "TestValue"; context.Response.Headers["TestHeader"] = "TestValue";
return Task.FromResult(0); return Task.FromResult(0);
}, PathString.Empty); }, PathString.Empty, _httpContextFactory);
var httpClient = new HttpClient(handler); var httpClient = new HttpClient(handler);
HttpResponseMessage response = await httpClient.GetAsync("https://example.com/"); HttpResponseMessage response = await httpClient.GetAsync("https://example.com/");
Assert.Equal("TestValue", response.Headers.GetValues("TestHeader").First()); Assert.Equal("TestValue", response.Headers.GetValues("TestHeader").First());
@ -104,11 +100,11 @@ namespace Microsoft.AspNet.TestHost
public async Task BlockingMiddlewareShouldNotBlockClient() public async Task BlockingMiddlewareShouldNotBlockClient()
{ {
ManualResetEvent block = new ManualResetEvent(false); ManualResetEvent block = new ManualResetEvent(false);
var handler = new ClientHandler(env => var handler = new ClientHandler(context =>
{ {
block.WaitOne(); block.WaitOne();
return Task.FromResult(0); return Task.FromResult(0);
}, PathString.Empty); }, PathString.Empty, _httpContextFactory);
var httpClient = new HttpClient(handler); var httpClient = new HttpClient(handler);
Task<HttpResponseMessage> task = httpClient.GetAsync("https://example.com/"); Task<HttpResponseMessage> task = httpClient.GetAsync("https://example.com/");
Assert.False(task.IsCompleted); Assert.False(task.IsCompleted);
@ -121,14 +117,13 @@ namespace Microsoft.AspNet.TestHost
public async Task HeadersAvailableBeforeBodyFinished() public async Task HeadersAvailableBeforeBodyFinished()
{ {
ManualResetEvent block = new ManualResetEvent(false); ManualResetEvent block = new ManualResetEvent(false);
var handler = new ClientHandler(async env => var handler = new ClientHandler(async context =>
{ {
var context = new DefaultHttpContext((IFeatureCollection)env);
context.Response.Headers["TestHeader"] = "TestValue"; context.Response.Headers["TestHeader"] = "TestValue";
await context.Response.WriteAsync("BodyStarted,"); await context.Response.WriteAsync("BodyStarted,");
block.WaitOne(); block.WaitOne();
await context.Response.WriteAsync("BodyFinished"); await context.Response.WriteAsync("BodyFinished");
}, PathString.Empty); }, PathString.Empty, _httpContextFactory);
var httpClient = new HttpClient(handler); var httpClient = new HttpClient(handler);
HttpResponseMessage response = await httpClient.GetAsync("https://example.com/", HttpResponseMessage response = await httpClient.GetAsync("https://example.com/",
HttpCompletionOption.ResponseHeadersRead); HttpCompletionOption.ResponseHeadersRead);
@ -141,14 +136,13 @@ namespace Microsoft.AspNet.TestHost
public async Task FlushSendsHeaders() public async Task FlushSendsHeaders()
{ {
ManualResetEvent block = new ManualResetEvent(false); ManualResetEvent block = new ManualResetEvent(false);
var handler = new ClientHandler(async env => var handler = new ClientHandler(async context =>
{ {
var context = new DefaultHttpContext((IFeatureCollection)env);
context.Response.Headers["TestHeader"] = "TestValue"; context.Response.Headers["TestHeader"] = "TestValue";
context.Response.Body.Flush(); context.Response.Body.Flush();
block.WaitOne(); block.WaitOne();
await context.Response.WriteAsync("BodyFinished"); await context.Response.WriteAsync("BodyFinished");
}, PathString.Empty); }, PathString.Empty, _httpContextFactory);
var httpClient = new HttpClient(handler); var httpClient = new HttpClient(handler);
HttpResponseMessage response = await httpClient.GetAsync("https://example.com/", HttpResponseMessage response = await httpClient.GetAsync("https://example.com/",
HttpCompletionOption.ResponseHeadersRead); HttpCompletionOption.ResponseHeadersRead);
@ -161,14 +155,13 @@ namespace Microsoft.AspNet.TestHost
public async Task ClientDisposalCloses() public async Task ClientDisposalCloses()
{ {
ManualResetEvent block = new ManualResetEvent(false); ManualResetEvent block = new ManualResetEvent(false);
var handler = new ClientHandler(env => var handler = new ClientHandler(context =>
{ {
var context = new DefaultHttpContext((IFeatureCollection)env);
context.Response.Headers["TestHeader"] = "TestValue"; context.Response.Headers["TestHeader"] = "TestValue";
context.Response.Body.Flush(); context.Response.Body.Flush();
block.WaitOne(); block.WaitOne();
return Task.FromResult(0); return Task.FromResult(0);
}, PathString.Empty); }, PathString.Empty, _httpContextFactory);
var httpClient = new HttpClient(handler); var httpClient = new HttpClient(handler);
HttpResponseMessage response = await httpClient.GetAsync("https://example.com/", HttpResponseMessage response = await httpClient.GetAsync("https://example.com/",
HttpCompletionOption.ResponseHeadersRead); HttpCompletionOption.ResponseHeadersRead);
@ -187,14 +180,13 @@ namespace Microsoft.AspNet.TestHost
public async Task ClientCancellationAborts() public async Task ClientCancellationAborts()
{ {
ManualResetEvent block = new ManualResetEvent(false); ManualResetEvent block = new ManualResetEvent(false);
var handler = new ClientHandler(env => var handler = new ClientHandler(context =>
{ {
var context = new DefaultHttpContext((IFeatureCollection)env);
context.Response.Headers["TestHeader"] = "TestValue"; context.Response.Headers["TestHeader"] = "TestValue";
context.Response.Body.Flush(); context.Response.Body.Flush();
block.WaitOne(); block.WaitOne();
return Task.FromResult(0); return Task.FromResult(0);
}, PathString.Empty); }, PathString.Empty, _httpContextFactory);
var httpClient = new HttpClient(handler); var httpClient = new HttpClient(handler);
HttpResponseMessage response = await httpClient.GetAsync("https://example.com/", HttpResponseMessage response = await httpClient.GetAsync("https://example.com/",
HttpCompletionOption.ResponseHeadersRead); HttpCompletionOption.ResponseHeadersRead);
@ -213,10 +205,10 @@ namespace Microsoft.AspNet.TestHost
[Fact] [Fact]
public Task ExceptionBeforeFirstWriteIsReported() public Task ExceptionBeforeFirstWriteIsReported()
{ {
var handler = new ClientHandler(env => var handler = new ClientHandler(context =>
{ {
throw new InvalidOperationException("Test Exception"); throw new InvalidOperationException("Test Exception");
}, PathString.Empty); }, PathString.Empty, _httpContextFactory);
var httpClient = new HttpClient(handler); var httpClient = new HttpClient(handler);
return Assert.ThrowsAsync<InvalidOperationException>(() => httpClient.GetAsync("https://example.com/", return Assert.ThrowsAsync<InvalidOperationException>(() => httpClient.GetAsync("https://example.com/",
HttpCompletionOption.ResponseHeadersRead)); HttpCompletionOption.ResponseHeadersRead));
@ -227,14 +219,13 @@ namespace Microsoft.AspNet.TestHost
public async Task ExceptionAfterFirstWriteIsReported() public async Task ExceptionAfterFirstWriteIsReported()
{ {
ManualResetEvent block = new ManualResetEvent(false); ManualResetEvent block = new ManualResetEvent(false);
var handler = new ClientHandler(async env => var handler = new ClientHandler(async context =>
{ {
var context = new DefaultHttpContext((IFeatureCollection)env);
context.Response.Headers["TestHeader"] = "TestValue"; context.Response.Headers["TestHeader"] = "TestValue";
await context.Response.WriteAsync("BodyStarted"); await context.Response.WriteAsync("BodyStarted");
block.WaitOne(); block.WaitOne();
throw new InvalidOperationException("Test Exception"); throw new InvalidOperationException("Test Exception");
}, PathString.Empty); }, PathString.Empty, _httpContextFactory);
var httpClient = new HttpClient(handler); var httpClient = new HttpClient(handler);
HttpResponseMessage response = await httpClient.GetAsync("https://example.com/", HttpResponseMessage response = await httpClient.GetAsync("https://example.com/",
HttpCompletionOption.ResponseHeadersRead); HttpCompletionOption.ResponseHeadersRead);