// 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.Diagnostics.Tracing; using System.Linq; using System.Threading; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Hosting.Builder; using Microsoft.AspNet.Hosting.Server; using Microsoft.AspNet.Hosting.Startup; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Features; using Microsoft.AspNet.Http.Features.Internal; using Microsoft.AspNet.Server.Features; using Microsoft.Dnx.Runtime; using Microsoft.Framework.Configuration; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.Logging; namespace Microsoft.AspNet.Hosting.Internal { public class HostingEngine : IHostingEngine { // This is defined by IIS's HttpPlatformHandler. private static readonly string ServerPort = "HTTP_PLATFORM_PORT"; private static readonly string DetailedErrors = "Hosting:DetailedErrors"; private readonly IServiceCollection _applicationServiceCollection; private readonly IStartupLoader _startupLoader; private readonly ApplicationLifetime _applicationLifetime; private readonly IConfiguration _config; private readonly bool _captureStartupErrors; private IServiceProvider _applicationServices; // Only one of these should be set internal string StartupAssemblyName { get; set; } internal StartupMethods Startup { get; set; } internal Type StartupType { get; set; } // Only one of these should be set internal IServerFactory ServerFactory { get; set; } internal string ServerFactoryLocation { get; set; } private IFeatureCollection _serverInstance; public HostingEngine( IServiceCollection appServices, IStartupLoader startupLoader, IConfiguration config, bool captureStartupErrors) { if (appServices == null) { throw new ArgumentNullException(nameof(appServices)); } if (startupLoader == null) { throw new ArgumentNullException(nameof(startupLoader)); } if (config == null) { throw new ArgumentNullException(nameof(config)); } _config = config; _applicationServiceCollection = appServices; _startupLoader = startupLoader; _captureStartupErrors = captureStartupErrors; _applicationLifetime = new ApplicationLifetime(); } public IServiceProvider ApplicationServices { get { EnsureApplicationServices(); return _applicationServices; } } public virtual IApplication Start() { var application = BuildApplication(); var logger = _applicationServices.GetRequiredService>(); var contextFactory = _applicationServices.GetRequiredService(); var contextAccessor = _applicationServices.GetRequiredService(); var telemetrySource = _applicationServices.GetRequiredService(); var server = ServerFactory.Start(_serverInstance, async features => { var httpContext = contextFactory.CreateHttpContext(features); httpContext.ApplicationServices = _applicationServices; var requestIdentifier = GetRequestIdentifier(httpContext); if (telemetrySource.IsEnabled("Microsoft.AspNet.Hosting.BeginRequest")) { telemetrySource.WriteTelemetry("Microsoft.AspNet.Hosting.BeginRequest", new { httpContext = httpContext }); } try { using (logger.BeginScope("Request Id: {RequestId}", requestIdentifier)) { contextAccessor.HttpContext = httpContext; await application(httpContext); } } catch (Exception ex) { if (telemetrySource.IsEnabled("Microsoft.AspNet.Hosting.UnhandledException")) { telemetrySource.WriteTelemetry("Microsoft.AspNet.Hosting.UnhandledException", new { httpContext = httpContext, exception = ex }); } throw; } if (telemetrySource.IsEnabled("Microsoft.AspNet.Hosting.EndRequest")) { telemetrySource.WriteTelemetry("Microsoft.AspNet.Hosting.EndRequest", new { httpContext = httpContext }); } }); _applicationLifetime.NotifyStarted(); return new Application(ApplicationServices, _serverInstance, new Disposable(() => { _applicationLifetime.NotifyStopping(); server.Dispose(); _applicationLifetime.NotifyStopped(); (_applicationServices as IDisposable)?.Dispose(); })); } private void EnsureApplicationServices() { if (_applicationServices == null) { EnsureStartup(); _applicationServiceCollection.AddInstance(_applicationLifetime); _applicationServices = Startup.ConfigureServicesDelegate(_applicationServiceCollection); } } private void EnsureStartup() { if (Startup != null) { return; } if (StartupType == null) { var diagnosticTypeMessages = new List(); StartupType = _startupLoader.FindStartupType(StartupAssemblyName, diagnosticTypeMessages); if (StartupType == null) { throw new ArgumentException( diagnosticTypeMessages.Aggregate("Failed to find a startup type for the web application.", (a, b) => a + "\r\n" + b), StartupAssemblyName); } } var diagnosticMessages = new List(); Startup = _startupLoader.LoadMethods(StartupType, diagnosticMessages); if (Startup == null) { throw new ArgumentException( diagnosticMessages.Aggregate("Failed to find a startup entry point for the web application.", (a, b) => a + "\r\n" + b), StartupAssemblyName); } } private RequestDelegate BuildApplication() { try { EnsureApplicationServices(); EnsureServer(); var builderFactory = _applicationServices.GetRequiredService(); var builder = builderFactory.CreateBuilder(_serverInstance); builder.ApplicationServices = _applicationServices; var startupFilters = _applicationServices.GetService>(); var configure = Startup.ConfigureDelegate; foreach (var filter in startupFilters) { configure = filter.Configure(configure); } configure(builder); return builder.Build(); } catch (Exception ex) { if (!_captureStartupErrors) { throw; } // EnsureApplicationServices may have failed due to a missing or throwing Startup class. if (_applicationServices == null) { _applicationServices = _applicationServiceCollection.BuildServiceProvider(); } EnsureServer(); // Write errors to standard out so they can be retrieved when not in development mode. Console.Out.WriteLine("Application startup exception: " + ex.ToString()); var logger = _applicationServices.GetRequiredService>(); logger.LogError("Application startup exception", ex); // Generate an HTML error page. var runtimeEnv = _applicationServices.GetRequiredService(); var hostingEnv = _applicationServices.GetRequiredService(); var showDetailedErrors = hostingEnv.IsDevelopment() || string.Equals("true", _config[DetailedErrors], StringComparison.OrdinalIgnoreCase) || string.Equals("1", _config[DetailedErrors], StringComparison.OrdinalIgnoreCase); var errorBytes = StartupExceptionPage.GenerateErrorHtml(showDetailedErrors, runtimeEnv, ex); return context => { context.Response.StatusCode = 500; context.Response.Headers["Cache-Control"] = "private, max-age=0"; context.Response.ContentType = "text/html; charset=utf-8"; context.Response.ContentLength = errorBytes.Length; return context.Response.Body.WriteAsync(errorBytes, 0, errorBytes.Length); }; } } private void EnsureServer() { if (ServerFactory == null) { // 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().LoadServerFactory(ServerFactoryLocation); } if (_serverInstance == null) { _serverInstance = ServerFactory.Initialize(_config); var addresses = _serverInstance?.Get()?.Addresses; if (addresses != null && !addresses.IsReadOnly) { var port = _config[ServerPort]; if (!string.IsNullOrEmpty(port)) { addresses.Add("http://localhost:" + port); } // Provide a default address if there aren't any configured. if (addresses.Count == 0) { addresses.Add("http://localhost:5000"); } } } } private string GetRequestIdentifier(HttpContext httpContext) { var requestIdentifierFeature = httpContext.Features.Get(); if (requestIdentifierFeature == null) { requestIdentifierFeature = new HttpRequestIdentifierFeature() { TraceIdentifier = Guid.NewGuid().ToString() }; httpContext.Features.Set(requestIdentifierFeature); } return requestIdentifierFeature.TraceIdentifier; } private class Disposable : IDisposable { private Action _dispose; public Disposable(Action dispose) { _dispose = dispose; } public void Dispose() { Interlocked.Exchange(ref _dispose, () => { }).Invoke(); } } } }