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:
parent
102dd03149
commit
48f0a76ea9
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
Loading…
Reference in New Issue