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 { /// /// Provides standard handlers for unhandled exceptions at both module and server level. /// /// /// public static class ExceptionHandler { /// /// The name of the response header used by the /// handler to transmit the type of the exception to the client. /// public const string ExceptionTypeHeaderName = "X-Exception-Type"; /// /// The name of the response header used by the /// handler to transmit the message of the exception to the client. /// public const string ExceptionMessageHeaderName = "X-Exception-Message"; /// /// Gets or sets the contact information to include in exception responses. /// public static string? ContactInformation { get; set; } /// /// Gets or sets a value indicating whether to include stack traces /// in exception responses. /// public static bool IncludeStackTraces { get; set; } /// /// Gets the default handler used by . /// This is the same as . /// public static ExceptionHandlerCallback Default { get; } = HtmlResponse; /// /// Sends an empty 500 Internal Server Error response. /// /// An interface representing the context of the request. /// The unhandled exception. /// A representing the ongoing operation. #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; } /// /// Sends an empty 500 Internal Server Error response, /// with the following additional headers: /// /// /// Header /// Value /// /// /// X-Exception-Type /// The name (without namespace) of the type of exception that was thrown. /// /// /// X-Exception-Message /// The Message property of the exception. /// /// /// The aforementioned header names are available as the and /// properties, respectively. /// /// An interface representing the context of the request. /// The unhandled exception. /// A representing the ongoing operation. 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; } /// /// Sends a 500 Internal Server Error response with a HTML payload /// briefly describing the error, including contact information and/or a stack trace /// if specified via the and /// properties, respectively. /// /// An interface representing the context of the request. /// The unhandled exception. /// A representing the ongoing operation. public static Task HtmlResponse(IHttpContext context, Exception exception) => context.SendStandardHtmlAsync( (int)HttpStatusCode.InternalServerError, text => { text.Write("

The server has encountered an error and was not able to process your request.

"); text.Write("

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.

"); text.Write("

The following information may help them in finding out what happened and restoring full functionality.

"); text.Write( "

Exception type: {0}

Message: {1}", WebUtility.HtmlEncode(exception.GetType().FullName ?? ""), WebUtility.HtmlEncode(exception.Message)); if (IncludeStackTraces) { text.Write( "

Stack trace:


{0}
", 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."); } } } }