280 lines
9.8 KiB
C#
280 lines
9.8 KiB
C#
// 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.Diagnostics.CodeAnalysis;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.AspNet.Builder;
|
|
using Microsoft.AspNet.Diagnostics.Views;
|
|
using Microsoft.AspNet.Http;
|
|
using Microsoft.Framework.Runtime;
|
|
|
|
namespace Microsoft.AspNet.Diagnostics
|
|
{
|
|
/// <summary>
|
|
/// Captures synchronous and asynchronous exceptions from the pipeline and generates HTML error responses.
|
|
/// </summary>
|
|
public class ErrorPageMiddleware
|
|
{
|
|
private readonly RequestDelegate _next;
|
|
private readonly ErrorPageOptions _options;
|
|
private static bool IsMono = Type.GetType("Mono.Runtime") != null;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ErrorPageMiddleware"/> class
|
|
/// </summary>
|
|
/// <param name="next"></param>
|
|
/// <param name="options"></param>
|
|
/// <param name="isDevMode"></param>
|
|
public ErrorPageMiddleware(RequestDelegate next, ErrorPageOptions options, bool isDevMode)
|
|
{
|
|
if (next == null)
|
|
{
|
|
throw new ArgumentNullException("next");
|
|
}
|
|
if (options == null)
|
|
{
|
|
throw new ArgumentNullException("options");
|
|
}
|
|
if (isDevMode)
|
|
{
|
|
options.SetDefaultVisibility(isVisible: true);
|
|
}
|
|
_next = next;
|
|
_options = options;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Process an individual request.
|
|
/// </summary>
|
|
/// <param name="context"></param>
|
|
/// <returns></returns>
|
|
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "For diagnostics")]
|
|
public async Task Invoke(HttpContext context)
|
|
{
|
|
try
|
|
{
|
|
await _next(context);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
try
|
|
{
|
|
await DisplayException(context, ex);
|
|
return;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// If there's a Exception while generating the error page, re-throw the original exception.
|
|
}
|
|
throw;
|
|
}
|
|
}
|
|
|
|
// Assumes the response headers have not been sent. If they have, still attempt to write to the body.
|
|
private Task DisplayException(HttpContext context, Exception ex)
|
|
{
|
|
var compilationException = ex as ICompilationException;
|
|
if (compilationException != null)
|
|
{
|
|
return DisplayCompilationException(context, ex, compilationException);
|
|
}
|
|
|
|
return DisplayRuntimeException(context, ex);
|
|
}
|
|
|
|
private Task DisplayCompilationException(HttpContext context,
|
|
Exception ex,
|
|
ICompilationException compilationException)
|
|
{
|
|
var model = new CompilationErrorPageModel()
|
|
{
|
|
Options = _options,
|
|
};
|
|
|
|
foreach (var compilationFailure in compilationException.CompilationFailures)
|
|
{
|
|
var stackFrames = new List<StackFrame>();
|
|
var errorDetails = new ErrorDetails
|
|
{
|
|
StackFrames = stackFrames
|
|
};
|
|
var fileContent = compilationFailure.SourceFileContent
|
|
.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
|
|
|
|
foreach (var item in compilationFailure.Messages)
|
|
{
|
|
var frame = new StackFrame
|
|
{
|
|
File = compilationFailure.SourceFilePath,
|
|
Line = item.StartLine,
|
|
Function = string.Empty
|
|
};
|
|
|
|
if (_options.ShowSourceCode)
|
|
{
|
|
ReadFrameContent(frame, fileContent, item.StartLine, item.EndLine);
|
|
frame.ErrorDetails = item.Message;
|
|
}
|
|
|
|
stackFrames.Add(frame);
|
|
}
|
|
|
|
model.ErrorDetails.Add(errorDetails);
|
|
}
|
|
|
|
var errorPage = new CompilationErrorPage
|
|
{
|
|
Model = model
|
|
};
|
|
|
|
return errorPage.ExecuteAsync(context);
|
|
}
|
|
|
|
private Task DisplayRuntimeException(HttpContext context, Exception ex)
|
|
{
|
|
var request = context.Request;
|
|
|
|
ErrorPageModel model = new ErrorPageModel()
|
|
{
|
|
Options = _options,
|
|
};
|
|
|
|
if (_options.ShowExceptionDetails)
|
|
{
|
|
model.ErrorDetails = GetErrorDetails(ex, _options.ShowSourceCode).Reverse();
|
|
}
|
|
if (_options.ShowQuery)
|
|
{
|
|
model.Query = request.Query;
|
|
}/* TODO:
|
|
if (_options.ShowCookies)
|
|
{
|
|
model.Cookies = request.Cookies;
|
|
}*/
|
|
if (_options.ShowHeaders)
|
|
{
|
|
model.Headers = request.Headers;
|
|
}/* TODO:
|
|
if (_options.ShowEnvironment)
|
|
{
|
|
model.Environment = context;
|
|
}*/
|
|
|
|
var errorPage = new ErrorPage(model);
|
|
return errorPage.ExecuteAsync(context);
|
|
}
|
|
|
|
private IEnumerable<ErrorDetails> GetErrorDetails(Exception ex, bool showSource)
|
|
{
|
|
for (Exception scan = ex; scan != null; scan = scan.InnerException)
|
|
{
|
|
yield return new ErrorDetails
|
|
{
|
|
Error = scan,
|
|
StackFrames = StackFrames(scan, showSource)
|
|
};
|
|
}
|
|
}
|
|
|
|
private IEnumerable<StackFrame> StackFrames(Exception ex, bool showSource)
|
|
{
|
|
var stackTrace = ex.StackTrace;
|
|
if (!string.IsNullOrEmpty(stackTrace))
|
|
{
|
|
var heap = new Chunk { Text = stackTrace + Environment.NewLine, End = stackTrace.Length + Environment.NewLine.Length };
|
|
for (Chunk line = heap.Advance(Environment.NewLine); line.HasValue; line = heap.Advance(Environment.NewLine))
|
|
{
|
|
yield return StackFrame(line, showSource);
|
|
}
|
|
}
|
|
}
|
|
|
|
private StackFrame StackFrame(Chunk line, bool showSource)
|
|
{
|
|
line.Advance(" at ");
|
|
string function = line.Advance(" in ").ToString();
|
|
|
|
//exception message line format differences in .net and mono
|
|
//On .net : at ConsoleApplication.Program.Main(String[] args) in D:\Program.cs:line 16
|
|
//On Mono : at ConsoleApplication.Program.Main(String[] args) in d:\Program.cs:16
|
|
string file = !IsMono ?
|
|
line.Advance(":line ").ToString() :
|
|
line.Advance(":").ToString();
|
|
|
|
int lineNumber = line.ToInt32();
|
|
|
|
return string.IsNullOrEmpty(file)
|
|
? LoadFrame(string.IsNullOrEmpty(function) ? line.ToString() : function, string.Empty, 0, showSource)
|
|
: LoadFrame(function, file, lineNumber, showSource);
|
|
}
|
|
|
|
private StackFrame LoadFrame(string function, string file, int lineNumber, bool showSource)
|
|
{
|
|
var frame = new StackFrame { Function = function, File = file, Line = lineNumber };
|
|
if (showSource && File.Exists(file))
|
|
{
|
|
IEnumerable<string> code = File.ReadLines(file);
|
|
ReadFrameContent(frame, code, lineNumber, lineNumber);
|
|
}
|
|
return frame;
|
|
}
|
|
|
|
private void ReadFrameContent(StackFrame frame,
|
|
IEnumerable<string> code,
|
|
int startLineNumber,
|
|
int endLineNumber)
|
|
{
|
|
frame.PreContextLine = Math.Max(startLineNumber - _options.SourceCodeLineCount, 1);
|
|
frame.PreContextCode = code.Skip(frame.PreContextLine - 1).Take(startLineNumber - frame.PreContextLine).ToArray();
|
|
frame.ContextCode = code.Skip(startLineNumber - 1).Take(1 + Math.Max(0, endLineNumber - startLineNumber));
|
|
frame.PostContextCode = code.Skip(startLineNumber).Take(_options.SourceCodeLineCount).ToArray();
|
|
}
|
|
|
|
internal class Chunk
|
|
{
|
|
public string Text { get; set; }
|
|
public int Start { get; set; }
|
|
public int End { get; set; }
|
|
|
|
public bool HasValue
|
|
{
|
|
get { return Text != null; }
|
|
}
|
|
|
|
public Chunk Advance(string delimiter)
|
|
{
|
|
int indexOf = HasValue ? Text.IndexOf(delimiter, Start, End - Start, StringComparison.Ordinal) : -1;
|
|
if (indexOf < 0)
|
|
{
|
|
return new Chunk();
|
|
}
|
|
|
|
var chunk = new Chunk { Text = Text, Start = Start, End = indexOf };
|
|
Start = indexOf + delimiter.Length;
|
|
return chunk;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return HasValue ? Text.Substring(Start, End - Start) : string.Empty;
|
|
}
|
|
|
|
public int ToInt32()
|
|
{
|
|
int value;
|
|
return HasValue && Int32.TryParse(
|
|
Text.Substring(Start, End - Start),
|
|
NumberStyles.Integer,
|
|
CultureInfo.InvariantCulture,
|
|
out value) ? value : 0;
|
|
}
|
|
}
|
|
}
|
|
}
|