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 23, 2015
1 parent 285da61 commit 70be7b2
Show file tree
Hide file tree
Showing 16 changed files with 1,123 additions and 3 deletions.
7 changes: 6 additions & 1 deletion src/Microsoft.AspNet.Hosting/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using Microsoft.AspNet.Hosting.Startup;
using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.Server.Features;
using Microsoft.Dnx.Runtime;
Expand Down Expand Up @@ -36,7 +37,11 @@ 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).UseServices(services =>
{
services.AddTransient<IStartupLoader, FailSafeStartupLoader>();
}).Build();

using (var app = host.Start())
{
var hostingEnv = app.Services.GetRequiredService<IHostingEnvironment>();
Expand Down
159 changes: 159 additions & 0 deletions src/Microsoft.AspNet.Hosting/Startup/FailSafeStartupLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// 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.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;
using Microsoft.Dnx.Runtime;
using Microsoft.Framework.DependencyInjection;

namespace Microsoft.AspNet.Hosting.Startup
{
/// <summary>
/// Catches common startup exceptions and shows an error page.
/// </summary>
public class FailSafeStartupLoader : IStartupLoader
{
private readonly IStartupLoader _nestedLoader;
private readonly IServiceProvider _services;
private readonly IRuntimeEnvironment _runtimeEnv;
private Exception _startupException;

public FailSafeStartupLoader(IServiceProvider services, IHostingEnvironment hostingEnv, IRuntimeEnvironment runtimeEnv)
{
_nestedLoader = new StartupLoader(services, hostingEnv);
_services = services;
_runtimeEnv = runtimeEnv;
ShowDetailedErrors = hostingEnv.IsDevelopment();
}

// Internal for testing
internal bool ShowDetailedErrors { get; set; }

public Type FindStartupType(string startupAssemblyName, IList<string> diagnosticMessages)
{
try
{
var type = _nestedLoader.FindStartupType(startupAssemblyName, diagnosticMessages);
if (type == null)
{
_startupException = new ArgumentException(
diagnosticMessages.Aggregate("Failed to find a startup type for the web application.", (a, b) => a + "\r\n" + b),
startupAssemblyName);
}
return type ?? typeof(FailSafeStartup);
}
catch (Exception ex)
{
_startupException = ex;
return typeof(FailSafeStartup);
}
}

public StartupMethods LoadMethods(Type startupType, IList<string> diagnosticMessages)
{
if (typeof(FailSafeStartup).Equals(startupType))
{
var errorText = _startupException?.ToString();
return new StartupMethods(new FailSafeStartup(ShowDetailedErrors, _runtimeEnv, _startupException).Configure);
}

try
{
var methods = _nestedLoader.LoadMethods(startupType, diagnosticMessages);
if (methods == null)
{
var exception = new ArgumentException(
diagnosticMessages.Aggregate("Failed to find a startup entry point for the web application.", (a, b) => a + "\r\n" + b),
startupType.ToString());
return new StartupMethods(new FailSafeStartup(ShowDetailedErrors, _runtimeEnv, exception).Configure);
}
var wrapper = new FailSafeStartupWrapper(ShowDetailedErrors, _runtimeEnv, methods.ConfigureServicesDelegate, methods.ConfigureDelegate);
return new StartupMethods(wrapper.Configure, wrapper.ConfigureServices);
}
catch (Exception ex)
{
return new StartupMethods(new FailSafeStartup(ShowDetailedErrors, _runtimeEnv, ex).Configure);
}
}

internal class FailSafeStartup
{
private readonly byte[] _errorBytes;

public FailSafeStartup(bool showDetails, IRuntimeEnvironment env, Exception ex)
{
_errorBytes = StartupErrorUtilities.GenerateErrorHtml(showDetails, env, ex);
}

public void Configure(IApplicationBuilder app)
{
app.Run(SendError);
}

public Task SendError(HttpContext context)
{
context.Response.StatusCode = 500;
context.Response.Headers["Cache-Control"] = "private, max-age=0";
// TODO: Check the Accept header and only send HTML if it's allowed. Otherwise send an empty response (or JSON?).
context.Response.ContentType = "text/html; charset=utf-8";
context.Response.ContentLength = _errorBytes.Length;
return context.Response.Body.WriteAsync(_errorBytes, 0, _errorBytes.Length);
}
}

internal class FailSafeStartupWrapper
{
private readonly bool _showDetails;
private readonly Action<IApplicationBuilder> _configureDelegate;
private readonly Func<IServiceCollection, IServiceProvider> _configureServicesDelegate;
private readonly IRuntimeEnvironment _runtimeEnv;
private Exception _configureServicesException;

public FailSafeStartupWrapper(bool showDetails, IRuntimeEnvironment runtimeEnv, Func<IServiceCollection, IServiceProvider> configureServicesDelegate, Action<IApplicationBuilder> configureDelegate)
{
_showDetails = showDetails;
_runtimeEnv = runtimeEnv;
_configureDelegate = configureDelegate;
_configureServicesDelegate = configureServicesDelegate;
}

internal IServiceProvider ConfigureServices(IServiceCollection services)
{
try
{
return _configureServicesDelegate(services);
}
catch (Exception ex)
{
_configureServicesException = ex;
return services.BuildServiceProvider();
}
}

internal void Configure(IApplicationBuilder app)
{
if (_configureServicesException != null)
{
new FailSafeStartup(_showDetails, _runtimeEnv, _configureServicesException).Configure(app);
return;
}
try
{
// Create our own builder so that we can discard it if we fail halfway through configure.
var safeApp = app.New();
_configureDelegate(safeApp);
var builtApp = safeApp.Build();
app.Run(builtApp);
}
catch (Exception ex)
{
new FailSafeStartup(_showDetails, _runtimeEnv, ex).Configure(app);
}
}
}
}
}
Loading

0 comments on commit 70be7b2

Please sign in to comment.