// 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.Buffers; using System.Diagnostics; using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport { /// /// In-memory TestServer /// _memoryPool; private readonly RequestDelegate _app; private readonly InMemoryTransportFactory _transportFactory; private readonly IWebHost _host; public TestServer(RequestDelegate app) : this(app, new TestServiceContext()) { } public TestServer(RequestDelegate app, TestServiceContext context) : this(app, context, new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))) { // The endpoint is ignored, but this ensures no cert loading happens for HTTPS endpoints. } public TestServer(RequestDelegate app, TestServiceContext context, ListenOptions listenOptions) : this(app, context, options => options.ListenOptions.Add(listenOptions), _ => { }) { } public TestServer(RequestDelegate app, TestServiceContext context, Action configureListenOptions) : this(app, context, options => { var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) { KestrelServerOptions = options }; configureListenOptions(listenOptions); options.ListenOptions.Add(listenOptions); }, _ => { }) { } public TestServer(RequestDelegate app, TestServiceContext context, Action configureKestrel, Action configureServices) { _app = app; Context = context; _memoryPool = context.MemoryPoolFactory(); _transportFactory = new InMemoryTransportFactory(); HttpClientSlim = new InMemoryHttpClientSlim(this); var hostBuilder = new WebHostBuilder() .ConfigureServices(services => { configureServices(services); services.AddSingleton(this); services.AddSingleton(context.LoggerFactory); services.AddSingleton(sp => { context.ServerOptions.ApplicationServices = sp; configureKestrel(context.ServerOptions); return new KestrelServer(_transportFactory, context); }); }); _host = hostBuilder.Build(); _host.Start(); } public int Port => 0; public TestServiceContext Context { get; } public InMemoryHttpClientSlim HttpClientSlim { get; } public InMemoryConnection CreateConnection() { var transportConnection = new InMemoryTransportConnection(_memoryPool); _ = HandleConnection(transportConnection); return new InMemoryConnection(transportConnection); } public Task StopAsync() { return _host.StopAsync(); } public void Dispose() { _host.Dispose(); _memoryPool.Dispose(); } void IStartup.Configure(IApplicationBuilder app) { app.Run(_app); } IServiceProvider IStartup.ConfigureServices(IServiceCollection services) { return services.BuildServiceProvider(); } private async Task HandleConnection(InMemoryTransportConnection transportConnection) { try { var middlewareTask = _transportFactory.ConnectionDispatcher.OnConnection(transportConnection); var transportTask = CancellationTokenAsTask(transportConnection.ConnectionClosed); await transportTask; await middlewareTask; transportConnection.Dispose(); } catch (Exception ex) { Debug.Assert(false, $"Unexpected exception: {ex}."); } } private static Task CancellationTokenAsTask(CancellationToken token) { if (token.IsCancellationRequested) { return Task.CompletedTask; } var tcs = new TaskCompletionSource(); token.Register(() => tcs.SetResult(null)); return tcs.Task; } } }