Refactoring IServerFactory #395
This commit is contained in:
parent
21373740c7
commit
3933a1904e
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,20 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http.Features;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Microsoft.AspNet.Hosting.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a factory for creating servers.
|
||||
/// </summary>
|
||||
public interface IServerFactory
|
||||
{
|
||||
IFeatureCollection Initialize(IConfiguration configuration);
|
||||
IDisposable Start(IFeatureCollection serverFeatures, Func<IFeatureCollection, Task> application);
|
||||
/// <summary>
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@
|
|||
"url": "git://github.com/aspnet/hosting"
|
||||
},
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Http.Features": "1.0.0-*",
|
||||
"Microsoft.AspNet.Http.Abstractions": "1.0.0-*",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "1.0.0-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"net451": {},
|
||||
"dotnet5.4": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ namespace Microsoft.AspNet.Hosting.Internal
|
|||
// Only one of these should be set
|
||||
internal IServerFactory ServerFactory { get; set; }
|
||||
internal string ServerFactoryLocation { get; set; }
|
||||
private IFeatureCollection _serverFeatures;
|
||||
internal IServer Server { get; set; }
|
||||
|
||||
public HostingEngine(
|
||||
IServiceCollection appServices,
|
||||
|
|
@ -87,15 +87,13 @@ namespace Microsoft.AspNet.Hosting.Internal
|
|||
var application = BuildApplication();
|
||||
|
||||
var logger = _applicationServices.GetRequiredService<ILogger<HostingEngine>>();
|
||||
var contextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>();
|
||||
var diagnosticSource = _applicationServices.GetRequiredService<DiagnosticSource>();
|
||||
|
||||
logger.Starting();
|
||||
|
||||
var server = ServerFactory.Start(_serverFeatures,
|
||||
async features =>
|
||||
Server.Start(
|
||||
async httpContext =>
|
||||
{
|
||||
var httpContext = contextFactory.Create(features);
|
||||
httpContext.ApplicationServices = _applicationServices;
|
||||
|
||||
if (diagnosticSource.IsEnabled("Microsoft.AspNet.Hosting.BeginRequest"))
|
||||
|
|
@ -135,11 +133,11 @@ namespace Microsoft.AspNet.Hosting.Internal
|
|||
_applicationLifetime.NotifyStarted();
|
||||
logger.Started();
|
||||
|
||||
return new Application(ApplicationServices, _serverFeatures, new Disposable(() =>
|
||||
return new Application(ApplicationServices, Server.Features, new Disposable(() =>
|
||||
{
|
||||
logger.Shutdown();
|
||||
_applicationLifetime.StopApplication();
|
||||
server.Dispose();
|
||||
Server.Dispose();
|
||||
_applicationLifetime.NotifyStopped();
|
||||
(_applicationServices as IDisposable)?.Dispose();
|
||||
}));
|
||||
|
|
@ -191,7 +189,7 @@ namespace Microsoft.AspNet.Hosting.Internal
|
|||
EnsureServer();
|
||||
|
||||
var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
|
||||
var builder = builderFactory.CreateBuilder(_serverFeatures);
|
||||
var builder = builderFactory.CreateBuilder(Server.Features);
|
||||
builder.ApplicationServices = _applicationServices;
|
||||
|
||||
var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>();
|
||||
|
|
@ -246,21 +244,21 @@ namespace Microsoft.AspNet.Hosting.Internal
|
|||
|
||||
private void EnsureServer()
|
||||
{
|
||||
if (ServerFactory == null)
|
||||
if (Server == null)
|
||||
{
|
||||
// Blow up if we don't have a server set at this point
|
||||
if (ServerFactoryLocation == null)
|
||||
if (ServerFactory == 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);
|
||||
}
|
||||
|
||||
if (_serverFeatures == null)
|
||||
{
|
||||
_serverFeatures = ServerFactory.Initialize(_config);
|
||||
var addresses = _serverFeatures?.Get<IServerAddressesFeature>()?.Addresses;
|
||||
Server = ServerFactory.CreateServer(_config);
|
||||
var addresses = Server.Features?.Get<IServerAddressesFeature>()?.Addresses;
|
||||
if (addresses != null && !addresses.IsReadOnly)
|
||||
{
|
||||
var port = _config[ServerPort];
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ namespace Microsoft.AspNet.Hosting
|
|||
// Only one of these should be set
|
||||
private string _serverFactoryLocation;
|
||||
private IServerFactory _serverFactory;
|
||||
private IServer _server;
|
||||
|
||||
public WebHostBuilder()
|
||||
: this(config: new ConfigurationBuilder().Build())
|
||||
|
|
@ -133,6 +134,7 @@ namespace Microsoft.AspNet.Hosting
|
|||
var engine = new HostingEngine(hostingServices, startupLoader, _config, _captureStartupErrors);
|
||||
|
||||
// Only one of these should be set, but they are used in priority
|
||||
engine.Server = _server;
|
||||
engine.ServerFactory = _serverFactory;
|
||||
engine.ServerFactoryLocation = _config[ServerKey] ?? _config[OldServerKey] ?? _serverFactoryLocation;
|
||||
|
||||
|
|
@ -161,7 +163,18 @@ namespace Microsoft.AspNet.Hosting
|
|||
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)
|
||||
{
|
||||
|
|
@ -172,8 +185,13 @@ namespace Microsoft.AspNet.Hosting
|
|||
return this;
|
||||
}
|
||||
|
||||
public WebHostBuilder UseServer(IServerFactory factory)
|
||||
public WebHostBuilder UseServerFactory(IServerFactory factory)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
_serverFactory = factory;
|
||||
return this;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,21 +23,27 @@ namespace Microsoft.AspNet.TestHost
|
|||
/// </summary>
|
||||
public class ClientHandler : HttpMessageHandler
|
||||
{
|
||||
private readonly Func<IFeatureCollection, Task> _next;
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly PathString _pathBase;
|
||||
private readonly IHttpContextFactory _factory;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new handler.
|
||||
/// </summary>
|
||||
/// <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)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(next));
|
||||
}
|
||||
if (httpContextFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContextFactory));
|
||||
}
|
||||
|
||||
_next = next;
|
||||
_factory = httpContextFactory;
|
||||
|
||||
// PathString.StartsWithSegments that we use below requires the base path to not end in a slash.
|
||||
if (pathBase.HasValue && pathBase.Value.EndsWith("/"))
|
||||
|
|
@ -63,7 +69,7 @@ namespace Microsoft.AspNet.TestHost
|
|||
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 body = await requestContent.ReadAsStreamAsync();
|
||||
if (body.CanSeek)
|
||||
|
|
@ -79,7 +85,7 @@ namespace Microsoft.AspNet.TestHost
|
|||
{
|
||||
try
|
||||
{
|
||||
await _next(state.HttpContext.Features);
|
||||
await _next(state.HttpContext);
|
||||
state.CompleteResponse();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -88,6 +94,7 @@ namespace Microsoft.AspNet.TestHost
|
|||
}
|
||||
finally
|
||||
{
|
||||
state.ServerCleanup();
|
||||
registration.Dispose();
|
||||
}
|
||||
});
|
||||
|
|
@ -102,14 +109,16 @@ namespace Microsoft.AspNet.TestHost
|
|||
private ResponseStream _responseStream;
|
||||
private ResponseFeature _responseFeature;
|
||||
private CancellationTokenSource _requestAbortedSource;
|
||||
private IHttpContextFactory _factory;
|
||||
private bool _pipelineFinished;
|
||||
|
||||
internal RequestState(HttpRequestMessage request, PathString pathBase)
|
||||
internal RequestState(HttpRequestMessage request, PathString pathBase, IHttpContextFactory factory)
|
||||
{
|
||||
_request = request;
|
||||
_responseTcs = new TaskCompletionSource<HttpResponseMessage>();
|
||||
_requestAbortedSource = new CancellationTokenSource();
|
||||
_pipelineFinished = false;
|
||||
_factory = factory;
|
||||
|
||||
if (request.RequestUri.IsDefaultPort)
|
||||
{
|
||||
|
|
@ -120,7 +129,8 @@ namespace Microsoft.AspNet.TestHost
|
|||
request.Headers.Host = request.RequestUri.GetComponents(UriComponents.HostAndPort, UriFormat.UriEscaped);
|
||||
}
|
||||
|
||||
HttpContext = new DefaultHttpContext();
|
||||
HttpContext = _factory.Create(new FeatureCollection());
|
||||
|
||||
HttpContext.Features.Set<IHttpRequestFeature>(new RequestFeature());
|
||||
_responseFeature = new ResponseFeature();
|
||||
HttpContext.Features.Set<IHttpResponseFeature>(_responseFeature);
|
||||
|
|
@ -228,6 +238,14 @@ namespace Microsoft.AspNet.TestHost
|
|||
_responseStream.Abort(exception);
|
||||
_responseTcs.TrySetException(exception);
|
||||
}
|
||||
|
||||
internal void ServerCleanup()
|
||||
{
|
||||
if (HttpContext != null)
|
||||
{
|
||||
_factory.Dispose(HttpContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
using System;
|
||||
|
|
@ -14,22 +14,26 @@ using Microsoft.Extensions.DependencyInjection;
|
|||
|
||||
namespace Microsoft.AspNet.TestHost
|
||||
{
|
||||
public class TestServer : IServerFactory, IDisposable
|
||||
public class TestServer : IServer
|
||||
{
|
||||
private const string DefaultEnvironmentName = "Development";
|
||||
private const string ServerName = nameof(TestServer);
|
||||
private static readonly IFeatureCollection ServerInfo = new FeatureCollection();
|
||||
private Func<IFeatureCollection, Task> _appDelegate;
|
||||
private RequestDelegate _appDelegate;
|
||||
private IDisposable _appInstance;
|
||||
private bool _disposed = false;
|
||||
private IHttpContextFactory _httpContextFactory;
|
||||
|
||||
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/");
|
||||
|
||||
IFeatureCollection IServer.Features { get; }
|
||||
|
||||
public static TestServer Create()
|
||||
{
|
||||
return Create(config: null, configureApp: null, configureServices: null);
|
||||
|
|
@ -95,7 +99,7 @@ namespace Microsoft.AspNet.TestHost
|
|||
public HttpMessageHandler CreateHandler()
|
||||
{
|
||||
var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress);
|
||||
return new ClientHandler(Invoke, pathBase);
|
||||
return new ClientHandler(Invoke, pathBase, _httpContextFactory);
|
||||
}
|
||||
|
||||
public HttpClient CreateClient()
|
||||
|
|
@ -106,7 +110,7 @@ namespace Microsoft.AspNet.TestHost
|
|||
public WebSocketClient CreateWebSocketClient()
|
||||
{
|
||||
var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress);
|
||||
return new WebSocketClient(Invoke, pathBase);
|
||||
return new WebSocketClient(Invoke, pathBase, _httpContextFactory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -119,25 +123,13 @@ namespace Microsoft.AspNet.TestHost
|
|||
return new RequestBuilder(this, path);
|
||||
}
|
||||
|
||||
public IFeatureCollection Initialize(IConfiguration configuration)
|
||||
{
|
||||
return ServerInfo;
|
||||
}
|
||||
|
||||
public IDisposable Start(IFeatureCollection serverInformation, Func<IFeatureCollection, Task> application)
|
||||
{
|
||||
_appDelegate = application;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Task Invoke(IFeatureCollection featureCollection)
|
||||
public Task Invoke(HttpContext context)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(GetType().FullName);
|
||||
}
|
||||
return _appDelegate(featureCollection);
|
||||
return _appDelegate(context);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
@ -145,5 +137,10 @@ namespace Microsoft.AspNet.TestHost
|
|||
_disposed = true;
|
||||
_appInstance.Dispose();
|
||||
}
|
||||
|
||||
void IServer.Start(RequestDelegate requestDelegate)
|
||||
{
|
||||
_appDelegate = requestDelegate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,25 +8,31 @@ using System.Net.WebSockets;
|
|||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http.Features;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Features;
|
||||
using Microsoft.AspNet.Http.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.TestHost
|
||||
{
|
||||
public class WebSocketClient
|
||||
{
|
||||
private readonly Func<IFeatureCollection, Task> _next;
|
||||
private readonly RequestDelegate _next;
|
||||
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)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(next));
|
||||
}
|
||||
if (httpContextFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContextFactory));
|
||||
}
|
||||
|
||||
_next = next;
|
||||
_httpContextFactory = httpContextFactory;
|
||||
|
||||
// PathString.StartsWithSegments that we use below requires the base path to not end in a slash.
|
||||
if (pathBase.HasValue && pathBase.Value.EndsWith("/"))
|
||||
|
|
@ -52,7 +58,7 @@ namespace Microsoft.AspNet.TestHost
|
|||
|
||||
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)
|
||||
{
|
||||
|
|
@ -64,7 +70,7 @@ namespace Microsoft.AspNet.TestHost
|
|||
{
|
||||
try
|
||||
{
|
||||
await _next(state.FeatureCollection);
|
||||
await _next(state.HttpContext);
|
||||
state.PipelineComplete();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -84,18 +90,18 @@ namespace Microsoft.AspNet.TestHost
|
|||
{
|
||||
private TaskCompletionSource<WebSocket> _clientWebSocketTcs;
|
||||
private WebSocket _serverWebSocket;
|
||||
private IHttpContextFactory _factory;
|
||||
|
||||
public IFeatureCollection FeatureCollection { get; private set; }
|
||||
public HttpContext HttpContext { get; private set; }
|
||||
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>();
|
||||
|
||||
// HttpContext
|
||||
FeatureCollection = new FeatureCollection();
|
||||
HttpContext = new DefaultHttpContext(FeatureCollection);
|
||||
HttpContext = _factory.Create(new FeatureCollection());
|
||||
|
||||
// Request
|
||||
HttpContext.Features.Set<IHttpRequestFeature>(new RequestFeature());
|
||||
|
|
@ -147,6 +153,10 @@ namespace Microsoft.AspNet.TestHost
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
if (HttpContext != null)
|
||||
{
|
||||
_factory.Dispose(HttpContext);
|
||||
}
|
||||
if (_serverWebSocket != null)
|
||||
{
|
||||
_serverWebSocket.Dispose();
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ using Microsoft.AspNet.Hosting.Server;
|
|||
using Microsoft.AspNet.Hosting.Startup;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Features;
|
||||
using Microsoft.AspNet.Http.Internal;
|
||||
using Microsoft.AspNet.Server.Features;
|
||||
using Microsoft.AspNet.Testing.xunit;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
|
@ -26,10 +27,33 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNet.Hosting
|
||||
{
|
||||
public class HostingEngineTests : IServerFactory
|
||||
public class HostingEngineTests : IServerFactory, IServer
|
||||
{
|
||||
private readonly IList<StartInstance> _startInstances = new List<StartInstance>();
|
||||
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()
|
||||
{
|
||||
|
|
@ -431,26 +455,36 @@ namespace Microsoft.AspNet.Hosting
|
|||
return new WebHostBuilder(config ?? new ConfigurationBuilder().Build());
|
||||
}
|
||||
|
||||
public IFeatureCollection Initialize(IConfiguration configuration)
|
||||
public void Start(RequestDelegate requestDelegate)
|
||||
{
|
||||
var features = new FeatureCollection();
|
||||
features.Set<IServerAddressesFeature>(new ServerAddressesFeature());
|
||||
return features;
|
||||
}
|
||||
|
||||
public IDisposable Start(IFeatureCollection serverFeatures, Func<IFeatureCollection, Task> application)
|
||||
{
|
||||
var startInstance = new StartInstance(application);
|
||||
var startInstance = new StartInstance(requestDelegate);
|
||||
_startInstances.Add(startInstance);
|
||||
application(_featuresSupportedByThisHost);
|
||||
return startInstance;
|
||||
requestDelegate(new DefaultHttpContext(Features));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Hosting.Fakes;
|
||||
using Microsoft.AspNet.Hosting.Internal;
|
||||
using Microsoft.AspNet.Hosting.Server;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Features;
|
||||
using Microsoft.AspNet.Http.Internal;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
|
@ -42,11 +42,11 @@ namespace Microsoft.AspNet.Hosting
|
|||
public async Task StartupMissing_Fallback()
|
||||
{
|
||||
var builder = CreateWebHostBuilder();
|
||||
var serverFactory = new TestServerFactory();
|
||||
var engine = builder.UseServer(serverFactory).UseStartup("MissingStartupAssembly").Build();
|
||||
var server = new TestServer();
|
||||
var engine = builder.UseServer(server).UseStartup("MissingStartupAssembly").Build();
|
||||
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()
|
||||
{
|
||||
var builder = CreateWebHostBuilder();
|
||||
var serverFactory = new TestServerFactory();
|
||||
var engine = builder.UseServer(serverFactory).UseStartup<StartupStaticCtorThrows>().Build();
|
||||
var server = new TestServer();
|
||||
var engine = builder.UseServer(server).UseStartup<StartupStaticCtorThrows>().Build();
|
||||
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()
|
||||
{
|
||||
var builder = CreateWebHostBuilder();
|
||||
var serverFactory = new TestServerFactory();
|
||||
var engine = builder.UseServer(serverFactory).UseStartup<StartupCtorThrows>().Build();
|
||||
var server = new TestServer();
|
||||
var engine = builder.UseServer(server).UseStartup<StartupCtorThrows>().Build();
|
||||
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()
|
||||
{
|
||||
var builder = CreateWebHostBuilder();
|
||||
var serverFactory = new TestServerFactory();
|
||||
var engine = builder.UseServer(serverFactory).UseStartup<StartupThrowTypeLoadException>().Build();
|
||||
var server = new TestServer();
|
||||
var engine = builder.UseServer(server).UseStartup<StartupThrowTypeLoadException>().Build();
|
||||
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()
|
||||
{
|
||||
var builder = CreateWebHostBuilder();
|
||||
var serverFactory = new TestServerFactory();
|
||||
var engine = builder.UseServer(serverFactory).UseStartup<StartupCtorThrows>().Build();
|
||||
var server = new TestServer();
|
||||
var engine = builder.UseServer(server).UseStartup<StartupCtorThrows>().Build();
|
||||
using (engine.Start())
|
||||
{
|
||||
var service = engine.ApplicationServices.GetService<IApplicationLifetime>();
|
||||
var service = engine.ApplicationServices.GetServices<IApplicationLifetime>();
|
||||
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()
|
||||
{
|
||||
var builder = CreateWebHostBuilder();
|
||||
var serverFactory = new TestServerFactory();
|
||||
var engine = builder.UseServer(serverFactory).UseStartup<StartupConfigureServicesThrows>().Build();
|
||||
var server = new TestServer();
|
||||
var engine = builder.UseServer(server).UseStartup<StartupConfigureServicesThrows>().Build();
|
||||
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()
|
||||
{
|
||||
var builder = CreateWebHostBuilder();
|
||||
var serverFactory = new TestServerFactory();
|
||||
var engine = builder.UseServer(serverFactory).UseStartup<StartupConfigureServicesThrows>().Build();
|
||||
var server = new TestServer();
|
||||
var engine = builder.UseServer(server).UseStartup<StartupConfigureServicesThrows>().Build();
|
||||
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);
|
||||
}
|
||||
|
||||
private async Task AssertResponseContains(Func<IFeatureCollection, Task> app, string expectedText)
|
||||
private async Task AssertResponseContains(RequestDelegate app, string expectedText)
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Response.Body = new MemoryStream();
|
||||
await app(httpContext.Features);
|
||||
await app(httpContext);
|
||||
httpContext.Response.Body.Seek(0, SeekOrigin.Begin);
|
||||
var bodyText = new StreamReader(httpContext.Response.Body).ReadToEnd();
|
||||
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;
|
||||
return new Disposable();
|
||||
}
|
||||
|
||||
private class Disposable : IDisposable
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
RequestDelegate = requestDelegate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,13 +17,13 @@ namespace Microsoft.AspNet.TestHost
|
|||
{
|
||||
public class ClientHandlerTests
|
||||
{
|
||||
private IHttpContextFactory _httpContextFactory = new HttpContextFactory(new HttpContextAccessor());
|
||||
|
||||
[Fact]
|
||||
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);
|
||||
Assert.Equal("HTTP/1.1", context.Request.Protocol);
|
||||
Assert.Equal("GET", context.Request.Method);
|
||||
|
|
@ -40,7 +40,7 @@ namespace Microsoft.AspNet.TestHost
|
|||
Assert.Equal("example.com", context.Request.Host.Value);
|
||||
|
||||
return Task.FromResult(0);
|
||||
}, new PathString("/A/Path/"));
|
||||
}, new PathString("/A/Path/"), _httpContextFactory);
|
||||
var httpClient = new HttpClient(handler);
|
||||
return httpClient.GetAsync("https://example.com/A/Path/and/file.txt?and=query");
|
||||
}
|
||||
|
|
@ -48,14 +48,13 @@ namespace Microsoft.AspNet.TestHost
|
|||
[Fact]
|
||||
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.Path.Value);
|
||||
|
||||
return Task.FromResult(0);
|
||||
}, new PathString(""));
|
||||
}, new PathString(""), _httpContextFactory);
|
||||
var httpClient = new HttpClient(handler);
|
||||
return httpClient.GetAsync("https://example.com/");
|
||||
}
|
||||
|
|
@ -64,15 +63,14 @@ namespace Microsoft.AspNet.TestHost
|
|||
public async Task ResubmitRequestWorks()
|
||||
{
|
||||
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);
|
||||
Assert.Equal(11, read);
|
||||
|
||||
context.Response.Headers["TestHeader"] = "TestValue:" + requestCount++;
|
||||
return Task.FromResult(0);
|
||||
}, PathString.Empty);
|
||||
}, PathString.Empty, _httpContextFactory);
|
||||
|
||||
HttpMessageInvoker invoker = new HttpMessageInvoker(handler);
|
||||
HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Post, "https://example.com/");
|
||||
|
|
@ -88,13 +86,11 @@ namespace Microsoft.AspNet.TestHost
|
|||
[Fact]
|
||||
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";
|
||||
return Task.FromResult(0);
|
||||
}, PathString.Empty);
|
||||
}, PathString.Empty, _httpContextFactory);
|
||||
var httpClient = new HttpClient(handler);
|
||||
HttpResponseMessage response = await httpClient.GetAsync("https://example.com/");
|
||||
Assert.Equal("TestValue", response.Headers.GetValues("TestHeader").First());
|
||||
|
|
@ -104,11 +100,11 @@ namespace Microsoft.AspNet.TestHost
|
|||
public async Task BlockingMiddlewareShouldNotBlockClient()
|
||||
{
|
||||
ManualResetEvent block = new ManualResetEvent(false);
|
||||
var handler = new ClientHandler(env =>
|
||||
var handler = new ClientHandler(context =>
|
||||
{
|
||||
block.WaitOne();
|
||||
return Task.FromResult(0);
|
||||
}, PathString.Empty);
|
||||
}, PathString.Empty, _httpContextFactory);
|
||||
var httpClient = new HttpClient(handler);
|
||||
Task<HttpResponseMessage> task = httpClient.GetAsync("https://example.com/");
|
||||
Assert.False(task.IsCompleted);
|
||||
|
|
@ -121,14 +117,13 @@ namespace Microsoft.AspNet.TestHost
|
|||
public async Task HeadersAvailableBeforeBodyFinished()
|
||||
{
|
||||
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";
|
||||
await context.Response.WriteAsync("BodyStarted,");
|
||||
block.WaitOne();
|
||||
await context.Response.WriteAsync("BodyFinished");
|
||||
}, PathString.Empty);
|
||||
}, PathString.Empty, _httpContextFactory);
|
||||
var httpClient = new HttpClient(handler);
|
||||
HttpResponseMessage response = await httpClient.GetAsync("https://example.com/",
|
||||
HttpCompletionOption.ResponseHeadersRead);
|
||||
|
|
@ -141,14 +136,13 @@ namespace Microsoft.AspNet.TestHost
|
|||
public async Task FlushSendsHeaders()
|
||||
{
|
||||
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.Body.Flush();
|
||||
block.WaitOne();
|
||||
await context.Response.WriteAsync("BodyFinished");
|
||||
}, PathString.Empty);
|
||||
}, PathString.Empty, _httpContextFactory);
|
||||
var httpClient = new HttpClient(handler);
|
||||
HttpResponseMessage response = await httpClient.GetAsync("https://example.com/",
|
||||
HttpCompletionOption.ResponseHeadersRead);
|
||||
|
|
@ -161,14 +155,13 @@ namespace Microsoft.AspNet.TestHost
|
|||
public async Task ClientDisposalCloses()
|
||||
{
|
||||
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.Body.Flush();
|
||||
block.WaitOne();
|
||||
return Task.FromResult(0);
|
||||
}, PathString.Empty);
|
||||
}, PathString.Empty, _httpContextFactory);
|
||||
var httpClient = new HttpClient(handler);
|
||||
HttpResponseMessage response = await httpClient.GetAsync("https://example.com/",
|
||||
HttpCompletionOption.ResponseHeadersRead);
|
||||
|
|
@ -187,14 +180,13 @@ namespace Microsoft.AspNet.TestHost
|
|||
public async Task ClientCancellationAborts()
|
||||
{
|
||||
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.Body.Flush();
|
||||
block.WaitOne();
|
||||
return Task.FromResult(0);
|
||||
}, PathString.Empty);
|
||||
}, PathString.Empty, _httpContextFactory);
|
||||
var httpClient = new HttpClient(handler);
|
||||
HttpResponseMessage response = await httpClient.GetAsync("https://example.com/",
|
||||
HttpCompletionOption.ResponseHeadersRead);
|
||||
|
|
@ -213,10 +205,10 @@ namespace Microsoft.AspNet.TestHost
|
|||
[Fact]
|
||||
public Task ExceptionBeforeFirstWriteIsReported()
|
||||
{
|
||||
var handler = new ClientHandler(env =>
|
||||
var handler = new ClientHandler(context =>
|
||||
{
|
||||
throw new InvalidOperationException("Test Exception");
|
||||
}, PathString.Empty);
|
||||
}, PathString.Empty, _httpContextFactory);
|
||||
var httpClient = new HttpClient(handler);
|
||||
return Assert.ThrowsAsync<InvalidOperationException>(() => httpClient.GetAsync("https://example.com/",
|
||||
HttpCompletionOption.ResponseHeadersRead));
|
||||
|
|
@ -227,14 +219,13 @@ namespace Microsoft.AspNet.TestHost
|
|||
public async Task ExceptionAfterFirstWriteIsReported()
|
||||
{
|
||||
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";
|
||||
await context.Response.WriteAsync("BodyStarted");
|
||||
block.WaitOne();
|
||||
throw new InvalidOperationException("Test Exception");
|
||||
}, PathString.Empty);
|
||||
}, PathString.Empty, _httpContextFactory);
|
||||
var httpClient = new HttpClient(handler);
|
||||
HttpResponseMessage response = await httpClient.GetAsync("https://example.com/",
|
||||
HttpCompletionOption.ResponseHeadersRead);
|
||||
|
|
|
|||
Loading…
Reference in New Issue