ExceptionMiddleware.cs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. using System;
  2. using System.IO;
  3. using System.Net.Mime;
  4. using System.Net.Sockets;
  5. using System.Threading.Tasks;
  6. using MediaBrowser.Common.Extensions;
  7. using MediaBrowser.Controller.Authentication;
  8. using MediaBrowser.Controller.Configuration;
  9. using MediaBrowser.Controller.Net;
  10. using Microsoft.AspNetCore.Hosting;
  11. using Microsoft.AspNetCore.Http;
  12. using Microsoft.Extensions.Hosting;
  13. using Microsoft.Extensions.Logging;
  14. namespace Jellyfin.Api.Middleware;
  15. /// <summary>
  16. /// Exception Middleware.
  17. /// </summary>
  18. public class ExceptionMiddleware
  19. {
  20. private readonly RequestDelegate _next;
  21. private readonly ILogger<ExceptionMiddleware> _logger;
  22. private readonly IServerConfigurationManager _configuration;
  23. private readonly IWebHostEnvironment _hostEnvironment;
  24. /// <summary>
  25. /// Initializes a new instance of the <see cref="ExceptionMiddleware"/> class.
  26. /// </summary>
  27. /// <param name="next">Next request delegate.</param>
  28. /// <param name="logger">Instance of the <see cref="ILogger{ExceptionMiddleware}"/> interface.</param>
  29. /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
  30. /// <param name="hostEnvironment">Instance of the <see cref="IWebHostEnvironment"/> interface.</param>
  31. public ExceptionMiddleware(
  32. RequestDelegate next,
  33. ILogger<ExceptionMiddleware> logger,
  34. IServerConfigurationManager serverConfigurationManager,
  35. IWebHostEnvironment hostEnvironment)
  36. {
  37. _next = next;
  38. _logger = logger;
  39. _configuration = serverConfigurationManager;
  40. _hostEnvironment = hostEnvironment;
  41. }
  42. /// <summary>
  43. /// Invoke request.
  44. /// </summary>
  45. /// <param name="context">Request context.</param>
  46. /// <returns>Task.</returns>
  47. public async Task Invoke(HttpContext context)
  48. {
  49. try
  50. {
  51. await _next(context).ConfigureAwait(false);
  52. }
  53. catch (Exception ex)
  54. {
  55. if (context.Response.HasStarted)
  56. {
  57. _logger.LogWarning("The response has already started, the exception middleware will not be executed.");
  58. throw;
  59. }
  60. ex = GetActualException(ex);
  61. bool ignoreStackTrace =
  62. ex is SocketException
  63. || ex is IOException
  64. || ex is OperationCanceledException
  65. || ex is SecurityException
  66. || ex is AuthenticationException
  67. || ex is FileNotFoundException;
  68. if (ignoreStackTrace)
  69. {
  70. _logger.LogError(
  71. "Error processing request: {ExceptionMessage}. URL {Method} {Url}.",
  72. ex.Message.TrimEnd('.'),
  73. context.Request.Method,
  74. context.Request.Path);
  75. }
  76. else
  77. {
  78. _logger.LogError(
  79. ex,
  80. "Error processing request. URL {Method} {Url}.",
  81. context.Request.Method,
  82. context.Request.Path);
  83. }
  84. context.Response.StatusCode = GetStatusCode(ex);
  85. context.Response.ContentType = MediaTypeNames.Text.Plain;
  86. // Don't send exception unless the server is in a Development environment
  87. var errorContent = _hostEnvironment.IsDevelopment()
  88. ? NormalizeExceptionMessage(ex.Message)
  89. : "Error processing request.";
  90. await context.Response.WriteAsync(errorContent).ConfigureAwait(false);
  91. }
  92. }
  93. private static Exception GetActualException(Exception ex)
  94. {
  95. if (ex is AggregateException agg)
  96. {
  97. var inner = agg.InnerException;
  98. if (inner is not null)
  99. {
  100. return GetActualException(inner);
  101. }
  102. var inners = agg.InnerExceptions;
  103. if (inners.Count > 0)
  104. {
  105. return GetActualException(inners[0]);
  106. }
  107. }
  108. return ex;
  109. }
  110. private static int GetStatusCode(Exception ex)
  111. {
  112. return ex switch
  113. {
  114. ArgumentException => StatusCodes.Status400BadRequest,
  115. AuthenticationException => StatusCodes.Status401Unauthorized,
  116. SecurityException => StatusCodes.Status403Forbidden,
  117. DirectoryNotFoundException => StatusCodes.Status404NotFound,
  118. FileNotFoundException => StatusCodes.Status404NotFound,
  119. ResourceNotFoundException => StatusCodes.Status404NotFound,
  120. MethodNotAllowedException => StatusCodes.Status405MethodNotAllowed,
  121. _ => StatusCodes.Status500InternalServerError
  122. };
  123. }
  124. private string NormalizeExceptionMessage(string msg)
  125. {
  126. // Strip any information we don't want to reveal
  127. return msg.Replace(
  128. _configuration.ApplicationPaths.ProgramSystemPath,
  129. string.Empty,
  130. StringComparison.OrdinalIgnoreCase)
  131. .Replace(
  132. _configuration.ApplicationPaths.ProgramDataPath,
  133. string.Empty,
  134. StringComparison.OrdinalIgnoreCase);
  135. }
  136. }