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.
// 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);
}
}

View File

@ -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": {}
}
}
}

View File

@ -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];

View File

@ -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;
}

View File

@ -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);
}
}
}
}
}

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.
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;
}
}
}

View File

@ -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();

View File

@ -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;
}

View File

@ -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;
}
}
}

View File

@ -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);