166 lines
7.6 KiB
C#
166 lines
7.6 KiB
C#
using System;
|
|
using System.Net;
|
|
using System.Runtime.ExceptionServices;
|
|
using System.Threading.Tasks;
|
|
using System.Web;
|
|
using System.Web.Util;
|
|
using Swan.Logging;
|
|
|
|
namespace EmbedIO
|
|
{
|
|
/// <summary>
|
|
/// Provides standard handlers for unhandled exceptions at both module and server level.
|
|
/// </summary>
|
|
/// <seealso cref="IWebServer.OnUnhandledException"/>
|
|
/// <seealso cref="IWebModule.OnUnhandledException"/>
|
|
public static class ExceptionHandler
|
|
{
|
|
/// <summary>
|
|
/// The name of the response header used by the <see cref="EmptyResponseWithHeaders" />
|
|
/// handler to transmit the type of the exception to the client.
|
|
/// </summary>
|
|
public const string ExceptionTypeHeaderName = "X-Exception-Type";
|
|
|
|
/// <summary>
|
|
/// The name of the response header used by the <see cref="EmptyResponseWithHeaders" />
|
|
/// handler to transmit the message of the exception to the client.
|
|
/// </summary>
|
|
public const string ExceptionMessageHeaderName = "X-Exception-Message";
|
|
|
|
/// <summary>
|
|
/// Gets or sets the contact information to include in exception responses.
|
|
/// </summary>
|
|
public static string? ContactInformation { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether to include stack traces
|
|
/// in exception responses.
|
|
/// </summary>
|
|
public static bool IncludeStackTraces { get; set; }
|
|
|
|
/// <summary>
|
|
/// <para>Gets the default handler used by <see cref="WebServerBase{TOptions}"/>.</para>
|
|
/// <para>This is the same as <see cref="HtmlResponse"/>.</para>
|
|
/// </summary>
|
|
public static ExceptionHandlerCallback Default { get; } = HtmlResponse;
|
|
|
|
/// <summary>
|
|
/// Sends an empty <c>500 Internal Server Error</c> response.
|
|
/// </summary>
|
|
/// <param name="context">An <see cref="IHttpContext" /> interface representing the context of the request.</param>
|
|
/// <param name="exception">The unhandled exception.</param>
|
|
/// <returns>A <see cref="Task" /> representing the ongoing operation.</returns>
|
|
#pragma warning disable CA1801 // Unused parameter
|
|
public static Task EmptyResponse(IHttpContext context, Exception exception)
|
|
#pragma warning restore CA1801
|
|
{
|
|
context.Response.SetEmptyResponse((int)HttpStatusCode.InternalServerError);
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
/// <summary>
|
|
/// <para>Sends an empty <c>500 Internal Server Error</c> response,
|
|
/// with the following additional headers:</para>
|
|
/// <list type="table">
|
|
/// <listheader>
|
|
/// <term>Header</term>
|
|
/// <description>Value</description>
|
|
/// </listheader>
|
|
/// <item>
|
|
/// <term><c>X-Exception-Type</c></term>
|
|
/// <description>The name (without namespace) of the type of exception that was thrown.</description>
|
|
/// </item>
|
|
/// <item>
|
|
/// <term><c>X-Exception-Message</c></term>
|
|
/// <description>The <see cref="Exception.Message">Message</see> property of the exception.</description>
|
|
/// </item>
|
|
/// </list>
|
|
/// <para>The aforementioned header names are available as the <see cref="ExceptionTypeHeaderName" /> and
|
|
/// <see cref="ExceptionMessageHeaderName" /> properties, respectively.</para>
|
|
/// </summary>
|
|
/// <param name="context">An <see cref="IHttpContext" /> interface representing the context of the request.</param>
|
|
/// <param name="exception">The unhandled exception.</param>
|
|
/// <returns>A <see cref="Task" /> representing the ongoing operation.</returns>
|
|
public static Task EmptyResponseWithHeaders(IHttpContext context, Exception exception)
|
|
{
|
|
context.Response.SetEmptyResponse((int)HttpStatusCode.InternalServerError);
|
|
context.Response.Headers[ExceptionTypeHeaderName] = Uri.EscapeDataString(exception.GetType().Name);
|
|
context.Response.Headers[ExceptionMessageHeaderName] = Uri.EscapeDataString(exception.Message);
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends a <c>500 Internal Server Error</c> response with a HTML payload
|
|
/// briefly describing the error, including contact information and/or a stack trace
|
|
/// if specified via the <see cref="ContactInformation"/> and <see cref="IncludeStackTraces"/>
|
|
/// properties, respectively.
|
|
/// </summary>
|
|
/// <param name="context">An <see cref="IHttpContext" /> interface representing the context of the request.</param>
|
|
/// <param name="exception">The unhandled exception.</param>
|
|
/// <returns>A <see cref="Task" /> representing the ongoing operation.</returns>
|
|
public static Task HtmlResponse(IHttpContext context, Exception exception)
|
|
=> context.SendStandardHtmlAsync(
|
|
(int)HttpStatusCode.InternalServerError,
|
|
text =>
|
|
{
|
|
text.Write("<p>The server has encountered an error and was not able to process your request.</p>");
|
|
text.Write("<p>Please contact the server administrator");
|
|
|
|
if (!string.IsNullOrEmpty(ContactInformation))
|
|
text.Write(" ({0})", WebUtility.HtmlEncode(ContactInformation));
|
|
|
|
text.Write(", informing them of the time this error occurred and the action(s) you performed that resulted in this error.</p>");
|
|
text.Write("<p>The following information may help them in finding out what happened and restoring full functionality.</p>");
|
|
text.Write(
|
|
"<p><strong>Exception type:</strong> {0}<p><strong>Message:</strong> {1}",
|
|
WebUtility.HtmlEncode(exception.GetType().FullName ?? "<unknown>"),
|
|
WebUtility.HtmlEncode(exception.Message));
|
|
|
|
if (IncludeStackTraces)
|
|
{
|
|
text.Write(
|
|
"</p><p><strong>Stack trace:</strong></p><br><pre>{0}</pre>",
|
|
WebUtility.HtmlEncode(exception.StackTrace));
|
|
}
|
|
});
|
|
|
|
internal static async Task Handle(string logSource, IHttpContext context, Exception exception, ExceptionHandlerCallback? handler, HttpExceptionHandlerCallback? httpHandler)
|
|
{
|
|
if (handler == null)
|
|
{
|
|
ExceptionDispatchInfo.Capture(exception).Throw();
|
|
return;
|
|
}
|
|
|
|
exception.Log(logSource, $"[{context.Id}] Unhandled exception.");
|
|
|
|
try
|
|
{
|
|
context.Response.SetEmptyResponse((int)HttpStatusCode.InternalServerError);
|
|
context.Response.DisableCaching();
|
|
await handler(context, exception)
|
|
.ConfigureAwait(false);
|
|
}
|
|
catch (OperationCanceledException) when (context.CancellationToken.IsCancellationRequested)
|
|
{
|
|
throw;
|
|
}
|
|
catch (HttpListenerException)
|
|
{
|
|
throw;
|
|
}
|
|
catch (Exception httpException) when (httpException is IHttpException httpException1)
|
|
{
|
|
if (httpHandler == null)
|
|
throw;
|
|
|
|
await httpHandler(context, httpException1).ConfigureAwait(false);
|
|
}
|
|
catch (Exception exception2)
|
|
{
|
|
exception2.Log(logSource, $"[{context.Id}] Unhandled exception while handling exception.");
|
|
}
|
|
}
|
|
}
|
|
}
|