Support plain text exception formatting (#9342)

- If there's no Accept: text/html then print the exception.ToString as plaintext
This commit is contained in:
David Fowler 2019-04-16 18:34:07 -07:00 committed by GitHub
parent 102dd03149
commit 48f0a76ea9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 84 additions and 5 deletions

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.Internal;
@ -17,11 +18,12 @@ using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.StackTrace.Sources;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Diagnostics
{
/// <summary>
/// Captures synchronous and asynchronous exceptions from the pipeline and generates HTML error responses.
/// Captures synchronous and asynchronous exceptions from the pipeline and generates error responses.
/// </summary>
public class DeveloperExceptionPageMiddleware
{
@ -32,6 +34,7 @@ namespace Microsoft.AspNetCore.Diagnostics
private readonly DiagnosticSource _diagnosticSource;
private readonly ExceptionDetailsProvider _exceptionDetailsProvider;
private readonly Func<ErrorContext, Task> _exceptionHandler;
private static readonly MediaTypeHeaderValue _textPlainMediaType = new MediaTypeHeaderValue("text/html");
/// <summary>
/// Initializes a new instance of the <see cref="DeveloperExceptionPageMiddleware"/> class
@ -127,13 +130,34 @@ namespace Microsoft.AspNetCore.Diagnostics
// Assumes the response headers have not been sent. If they have, still attempt to write to the body.
private Task DisplayException(ErrorContext errorContext)
{
var compilationException = errorContext.Exception as ICompilationException;
if (compilationException != null)
var httpContext = errorContext.HttpContext;
var headers = httpContext.Request.GetTypedHeaders();
var acceptHeader = headers.Accept;
// If the client does not ask for HTML just format the exception as plain text
if (acceptHeader == null || !acceptHeader.Any(h => h.IsSubsetOf(_textPlainMediaType)))
{
return DisplayCompilationException(errorContext.HttpContext, compilationException);
httpContext.Response.ContentType = "text/plain";
var sb = new StringBuilder();
sb.AppendLine(errorContext.Exception.ToString());
sb.AppendLine();
sb.AppendLine("HEADERS");
sb.AppendLine("=======");
foreach (var pair in httpContext.Request.Headers)
{
sb.AppendLine($"{pair.Key}: {pair.Value}");
}
return httpContext.Response.WriteAsync(sb.ToString());
}
return DisplayRuntimeException(errorContext.HttpContext, errorContext.Exception);
if (errorContext.Exception is ICompilationException compilationException)
{
return DisplayCompilationException(httpContext, compilationException);
}
return DisplayRuntimeException(httpContext, errorContext.Exception);
}
private Task DisplayCompilationException(

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
@ -45,6 +46,58 @@ namespace Microsoft.AspNetCore.Diagnostics
Assert.Null(listener.DiagnosticHandledException?.Exception);
}
[Fact]
public async Task ErrorPageWithAcceptHeaderForHtmlReturnsHtml()
{
// Arrange
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseDeveloperExceptionPage();
app.Run(context =>
{
throw new Exception("Test exception");
});
});
var server = new TestServer(builder);
// Act
var client = server.CreateClient();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/html"));
var response = await client.GetAsync("/path");
// Assert
var responseText = await response.Content.ReadAsStringAsync();
Assert.Equal("text/html", response.Content.Headers.ContentType.MediaType);
Assert.Contains("<html", responseText);
Assert.Contains("Test exception", responseText);
}
[Fact]
public async Task ErrorPageWithoutAcceptHeaderForHtmlReturnsPlainText()
{
// Arrange
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseDeveloperExceptionPage();
app.Run(context =>
{
throw new Exception("Test exception");
});
});
var server = new TestServer(builder);
// Act
var response = await server.CreateClient().GetAsync("/path");
// Assert
var responseText = await response.Content.ReadAsStringAsync();
Assert.Equal("text/plain", response.Content.Headers.ContentType.MediaType);
Assert.Contains("Test exception", responseText);
Assert.DoesNotContain("<html", responseText);
}
[Fact]
public async Task ExceptionPageFiltersAreApplied()
{

View File

@ -39,6 +39,8 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Client = factory
.WithWebHostBuilder(builder => builder.ConfigureLogging(l => l.Services.AddSingleton<ILoggerFactory>(loggerProvider)))
.CreateDefaultClient();
// These tests want to verify runtime compilation and formatting in the HTML of the error page
Client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/html"));
}
public HttpClient Client { get; }