This repository has been archived by the owner on Dec 19, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 307
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#77 Catch startup exceptions and show them in the browser.
- Loading branch information
Showing
16 changed files
with
1,123 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
159 changes: 159 additions & 0 deletions
159
src/Microsoft.AspNet.Hosting/Startup/FailSafeStartupLoader.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.