Skip to content
This repository has been archived by the owner on Dec 19, 2018. It is now read-only.

Commit

Permalink
#77 Catch startup exceptions and show them in the browser.
Browse files Browse the repository at this point in the history
  • Loading branch information
Tratcher committed Sep 25, 2015
1 parent 520fc2b commit a9e7948
Show file tree
Hide file tree
Showing 16 changed files with 1,000 additions and 33 deletions.
108 changes: 80 additions & 28 deletions src/Microsoft.AspNet.Hosting/Internal/HostingEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
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;
Expand All @@ -24,11 +25,13 @@ 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;

Expand All @@ -45,7 +48,8 @@ public class HostingEngine : IHostingEngine
public HostingEngine(
IServiceCollection appServices,
IStartupLoader startupLoader,
IConfiguration config)
IConfiguration config,
bool captureStartupErrors)
{
if (appServices == null)
{
Expand All @@ -65,6 +69,7 @@ public HostingEngine(
_config = config;
_applicationServiceCollection = appServices;
_startupLoader = startupLoader;
_captureStartupErrors = captureStartupErrors;
_applicationLifetime = new ApplicationLifetime();
}

Expand All @@ -79,8 +84,6 @@ public IServiceProvider ApplicationServices

public virtual IApplication Start()
{
EnsureApplicationServices();

var application = BuildApplication();

var logger = _applicationServices.GetRequiredService<ILogger<HostingEngine>>();
Expand Down Expand Up @@ -175,48 +178,97 @@ private void EnsureStartup()

private RequestDelegate BuildApplication()
{
if (ServerFactory == null)
try
{
// Blow up if we don't have a server set at this point
if (ServerFactoryLocation == null)
EnsureApplicationServices();
EnsureServer();

var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
var builder = builderFactory.CreateBuilder(_serverInstance);
builder.ApplicationServices = _applicationServices;

var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>();
var configure = Startup.ConfigureDelegate;
foreach (var filter in startupFilters)
{
throw new InvalidOperationException("IHostingBuilder.UseServer() is required for " + nameof(Start) + "()");
configure = filter.Configure(configure);
}

ServerFactory = _applicationServices.GetRequiredService<IServerLoader>().LoadServerFactory(ServerFactoryLocation);
}

_serverInstance = ServerFactory.Initialize(_config);
var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
var builder = builderFactory.CreateBuilder(_serverInstance);
builder.ApplicationServices = _applicationServices;
configure(builder);

var addresses = builder.ServerFeatures?.Get<IServerAddressesFeature>()?.Addresses;
if (addresses != null && !addresses.IsReadOnly)
return builder.Build();
}
catch (Exception ex)
{
var port = _config[ServerPort];
if (!string.IsNullOrEmpty(port))
if (!_captureStartupErrors)
{
addresses.Add("http://localhost:" + port);
throw;
}

// Provide a default address if there aren't any configured.
if (addresses.Count == 0)
// EnsureApplicationServices may have failed due to a missing or throwing Startup class.
if (_applicationServices == null)
{
addresses.Add("http://localhost:5000");
_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<ILogger<HostingEngine>>();
logger.LogError("Application startup exception", ex);

// Generate an HTML error page.
var runtimeEnv = _applicationServices.GetRequiredService<IRuntimeEnvironment>();
var hostingEnv = _applicationServices.GetRequiredService<IHostingEnvironment>();
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);
};
}
}

var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>();
var configure = Startup.ConfigureDelegate;
foreach (var filter in startupFilters)
private void EnsureServer()
{
if (ServerFactory == null)
{
configure = filter.Configure(configure);
// 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);
}

configure(builder);
if (_serverInstance == null)
{
_serverInstance = ServerFactory.Initialize(_config);
var addresses = _serverInstance?.Get<IServerAddressesFeature>()?.Addresses;
if (addresses != null && !addresses.IsReadOnly)
{
var port = _config[ServerPort];
if (!string.IsNullOrEmpty(port))
{
addresses.Add("http://localhost:" + port);
}

return builder.Build();
// Provide a default address if there aren't any configured.
if (addresses.Count == 0)
{
addresses.Add("http://localhost:5000");
}
}
}
}

private string GetRequestIdentifier(HttpContext httpContext)
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.AspNet.Hosting/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public void Main(string[] args)
builder.AddCommandLine(args);
var config = builder.Build();

var host = new WebHostBuilder(_serviceProvider, config).Build();
var host = new WebHostBuilder(_serviceProvider, config, captureStartupErrors: true).Build();
using (var app = host.Start())
{
var hostingEnv = app.Services.GetRequiredService<IHostingEnvironment>();
Expand Down
Loading

0 comments on commit a9e7948

Please sign in to comment.