From 1175ce3f97fdebc6fdb489ce65deaac59c7b7f87 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 07:36:22 -0600 Subject: [PATCH 1/7] Add Exception Middleware --- .../Models/ExceptionDtos/ExceptionDto.cs | 14 +++++ .../ApiApplicationBuilderExtensions.cs | 11 ++++ .../Middleware/ExceptionMiddleware.cs | 60 +++++++++++++++++++ Jellyfin.Server/Startup.cs | 2 + 4 files changed, 87 insertions(+) create mode 100644 Jellyfin.Api/Models/ExceptionDtos/ExceptionDto.cs create mode 100644 Jellyfin.Server/Middleware/ExceptionMiddleware.cs diff --git a/Jellyfin.Api/Models/ExceptionDtos/ExceptionDto.cs b/Jellyfin.Api/Models/ExceptionDtos/ExceptionDto.cs new file mode 100644 index 000000000..d2b48d4ae --- /dev/null +++ b/Jellyfin.Api/Models/ExceptionDtos/ExceptionDto.cs @@ -0,0 +1,14 @@ +namespace Jellyfin.Api.Models.ExceptionDtos +{ + /// + /// Exception Dto. + /// Used for graceful handling of API exceptions. + /// + public class ExceptionDto + { + /// + /// Gets or sets exception message. + /// + public string Message { get; set; } + } +} diff --git a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs index db06eb455..6c105ab65 100644 --- a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs @@ -1,3 +1,4 @@ +using Jellyfin.Server.Middleware; using Microsoft.AspNetCore.Builder; namespace Jellyfin.Server.Extensions @@ -23,5 +24,15 @@ namespace Jellyfin.Server.Extensions c.SwaggerEndpoint("/swagger/v1/swagger.json", "Jellyfin API V1"); }); } + + /// + /// Adds exception middleware to the application pipeline. + /// + /// The application builder. + /// The updated application builder. + public static IApplicationBuilder UseExceptionMiddleware(this IApplicationBuilder applicationBuilder) + { + return applicationBuilder.UseMiddleware(); + } } } diff --git a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs new file mode 100644 index 000000000..39aace95d --- /dev/null +++ b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs @@ -0,0 +1,60 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using Jellyfin.Api.Models.ExceptionDtos; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Middleware +{ + /// + /// Exception Middleware. + /// + public class ExceptionMiddleware + { + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// Next request delegate. + /// Instance of the interface. + public ExceptionMiddleware(RequestDelegate next, ILoggerFactory loggerFactory) + { + _next = next ?? throw new ArgumentNullException(nameof(next)); + _logger = loggerFactory.CreateLogger() ?? + throw new ArgumentNullException(nameof(loggerFactory)); + } + + /// + /// Invoke request. + /// + /// Request context. + /// Task. + public async Task Invoke(HttpContext context) + { + try + { + await _next(context).ConfigureAwait(false); + } + catch (Exception ex) + { + if (context.Response.HasStarted) + { + _logger.LogWarning("The response has already started, the exception middleware will not be executed."); + throw; + } + + var exceptionBody = new ExceptionDto { Message = ex.Message }; + var exceptionJson = JsonSerializer.Serialize(exceptionBody); + + context.Response.Clear(); + context.Response.StatusCode = StatusCodes.Status500InternalServerError; + // TODO switch between PascalCase and camelCase + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(exceptionJson).ConfigureAwait(false); + } + } + } +} diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs index 4d7d56e9d..7a632f6c4 100644 --- a/Jellyfin.Server/Startup.cs +++ b/Jellyfin.Server/Startup.cs @@ -58,6 +58,8 @@ namespace Jellyfin.Server app.UseDeveloperExceptionPage(); } + app.UseExceptionMiddleware(); + app.UseWebSockets(); app.UseResponseCompression(); From 14361c68cf71bc810d282901a764d2f8d5858eea Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 08:38:31 -0600 Subject: [PATCH 2/7] Add ProducesResponseType to base controller --- Jellyfin.Api/BaseJellyfinApiController.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Jellyfin.Api/BaseJellyfinApiController.cs b/Jellyfin.Api/BaseJellyfinApiController.cs index 1f4508e6c..f69175986 100644 --- a/Jellyfin.Api/BaseJellyfinApiController.cs +++ b/Jellyfin.Api/BaseJellyfinApiController.cs @@ -1,3 +1,5 @@ +using Jellyfin.Api.Models.ExceptionDtos; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace Jellyfin.Api @@ -7,6 +9,7 @@ namespace Jellyfin.Api /// [ApiController] [Route("[controller]")] + [ProducesResponseType(typeof(ExceptionDto), StatusCodes.Status500InternalServerError)] public class BaseJellyfinApiController : ControllerBase { } From 3ef8448a518e673feae0c70c2682d60e4632c0cd Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 09:09:05 -0600 Subject: [PATCH 3/7] Return to previous exception handle implementation --- Jellyfin.Api/BaseJellyfinApiController.cs | 3 - .../Models/ExceptionDtos/ExceptionDto.cs | 14 --- .../Middleware/ExceptionMiddleware.cs | 86 ++++++++++++++++--- 3 files changed, 73 insertions(+), 30 deletions(-) delete mode 100644 Jellyfin.Api/Models/ExceptionDtos/ExceptionDto.cs diff --git a/Jellyfin.Api/BaseJellyfinApiController.cs b/Jellyfin.Api/BaseJellyfinApiController.cs index f69175986..1f4508e6c 100644 --- a/Jellyfin.Api/BaseJellyfinApiController.cs +++ b/Jellyfin.Api/BaseJellyfinApiController.cs @@ -1,5 +1,3 @@ -using Jellyfin.Api.Models.ExceptionDtos; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace Jellyfin.Api @@ -9,7 +7,6 @@ namespace Jellyfin.Api /// [ApiController] [Route("[controller]")] - [ProducesResponseType(typeof(ExceptionDto), StatusCodes.Status500InternalServerError)] public class BaseJellyfinApiController : ControllerBase { } diff --git a/Jellyfin.Api/Models/ExceptionDtos/ExceptionDto.cs b/Jellyfin.Api/Models/ExceptionDtos/ExceptionDto.cs deleted file mode 100644 index d2b48d4ae..000000000 --- a/Jellyfin.Api/Models/ExceptionDtos/ExceptionDto.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Jellyfin.Api.Models.ExceptionDtos -{ - /// - /// Exception Dto. - /// Used for graceful handling of API exceptions. - /// - public class ExceptionDto - { - /// - /// Gets or sets exception message. - /// - public string Message { get; set; } - } -} diff --git a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs index 39aace95d..0d9dac89f 100644 --- a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs +++ b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs @@ -1,7 +1,9 @@ using System; -using System.Text.Json; +using System.IO; using System.Threading.Tasks; -using Jellyfin.Api.Models.ExceptionDtos; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Net; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -14,17 +16,22 @@ namespace Jellyfin.Server.Middleware { private readonly RequestDelegate _next; private readonly ILogger _logger; + private readonly IServerConfigurationManager _configuration; /// /// Initializes a new instance of the class. /// /// Next request delegate. /// Instance of the interface. - public ExceptionMiddleware(RequestDelegate next, ILoggerFactory loggerFactory) + /// Instance of the interface. + public ExceptionMiddleware( + RequestDelegate next, + ILoggerFactory loggerFactory, + IServerConfigurationManager serverConfigurationManager) { - _next = next ?? throw new ArgumentNullException(nameof(next)); - _logger = loggerFactory.CreateLogger() ?? - throw new ArgumentNullException(nameof(loggerFactory)); + _next = next; + _logger = loggerFactory.CreateLogger(); + _configuration = serverConfigurationManager; } /// @@ -46,15 +53,68 @@ namespace Jellyfin.Server.Middleware throw; } - var exceptionBody = new ExceptionDto { Message = ex.Message }; - var exceptionJson = JsonSerializer.Serialize(exceptionBody); + ex = GetActualException(ex); + _logger.LogError(ex, "Error processing request: {0}", ex.Message); + context.Response.StatusCode = GetStatusCode(ex); + context.Response.ContentType = "text/plain"; - context.Response.Clear(); - context.Response.StatusCode = StatusCodes.Status500InternalServerError; - // TODO switch between PascalCase and camelCase - context.Response.ContentType = "application/json"; - await context.Response.WriteAsync(exceptionJson).ConfigureAwait(false); + var errorContent = NormalizeExceptionMessage(ex.Message); + await context.Response.WriteAsync(errorContent).ConfigureAwait(false); } } + + private static Exception GetActualException(Exception ex) + { + if (ex is AggregateException agg) + { + var inner = agg.InnerException; + if (inner != null) + { + return GetActualException(inner); + } + + var inners = agg.InnerExceptions; + if (inners.Count > 0) + { + return GetActualException(inners[0]); + } + } + + return ex; + } + + private static int GetStatusCode(Exception ex) + { + switch (ex) + { + case ArgumentException _: return StatusCodes.Status400BadRequest; + case SecurityException _: return StatusCodes.Status401Unauthorized; + case DirectoryNotFoundException _: + case FileNotFoundException _: + case ResourceNotFoundException _: return StatusCodes.Status404NotFound; + case MethodNotAllowedException _: return StatusCodes.Status405MethodNotAllowed; + default: return StatusCodes.Status500InternalServerError; + } + } + + private string NormalizeExceptionMessage(string msg) + { + if (msg == null) + { + return string.Empty; + } + + // Strip any information we don't want to reveal + msg = msg.Replace( + _configuration.ApplicationPaths.ProgramSystemPath, + string.Empty, + StringComparison.OrdinalIgnoreCase); + msg = msg.Replace( + _configuration.ApplicationPaths.ProgramDataPath, + string.Empty, + StringComparison.OrdinalIgnoreCase); + + return msg; + } } } From c6eebca335d09d6a6c627205e126448ab5441f37 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 23 Apr 2020 07:29:28 -0600 Subject: [PATCH 4/7] Apply suggestions and add URL to log message --- .../Middleware/ExceptionMiddleware.cs | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs index 0d9dac89f..ecc76594e 100644 --- a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs +++ b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Net.Mime; using System.Threading.Tasks; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; @@ -22,15 +23,15 @@ namespace Jellyfin.Server.Middleware /// Initializes a new instance of the class. /// /// Next request delegate. - /// Instance of the interface. + /// Instance of the interface. /// Instance of the interface. public ExceptionMiddleware( RequestDelegate next, - ILoggerFactory loggerFactory, + ILogger logger, IServerConfigurationManager serverConfigurationManager) { _next = next; - _logger = loggerFactory.CreateLogger(); + _logger = logger; _configuration = serverConfigurationManager; } @@ -54,9 +55,14 @@ namespace Jellyfin.Server.Middleware } ex = GetActualException(ex); - _logger.LogError(ex, "Error processing request: {0}", ex.Message); + _logger.LogError( + ex, + "Error processing request: {ExceptionMessage}. URL {Method} {Url}. ", + ex.Message, + context.Request.Method, + context.Request.Path); context.Response.StatusCode = GetStatusCode(ex); - context.Response.ContentType = "text/plain"; + context.Response.ContentType = MediaTypeNames.Text.Plain; var errorContent = NormalizeExceptionMessage(ex.Message); await context.Response.WriteAsync(errorContent).ConfigureAwait(false); @@ -105,16 +111,14 @@ namespace Jellyfin.Server.Middleware } // Strip any information we don't want to reveal - msg = msg.Replace( - _configuration.ApplicationPaths.ProgramSystemPath, - string.Empty, - StringComparison.OrdinalIgnoreCase); - msg = msg.Replace( - _configuration.ApplicationPaths.ProgramDataPath, - string.Empty, - StringComparison.OrdinalIgnoreCase); - - return msg; + return msg.Replace( + _configuration.ApplicationPaths.ProgramSystemPath, + string.Empty, + StringComparison.OrdinalIgnoreCase) + .Replace( + _configuration.ApplicationPaths.ProgramDataPath, + string.Empty, + StringComparison.OrdinalIgnoreCase); } } } From 3c34d956088430da08bdd812c05d6a87c3bf9d25 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 23 Apr 2020 21:23:29 -0600 Subject: [PATCH 5/7] Address comments --- .../Middleware/ExceptionMiddleware.cs | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs index ecc76594e..6ebe01503 100644 --- a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs +++ b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs @@ -1,8 +1,10 @@ using System; using System.IO; using System.Net.Mime; +using System.Net.Sockets; using System.Threading.Tasks; using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; using Microsoft.AspNetCore.Http; @@ -55,15 +57,35 @@ namespace Jellyfin.Server.Middleware } ex = GetActualException(ex); - _logger.LogError( - ex, - "Error processing request: {ExceptionMessage}. URL {Method} {Url}. ", - ex.Message, - context.Request.Method, - context.Request.Path); + + bool ignoreStackTrace = + ex is SocketException + || ex is IOException + || ex is OperationCanceledException + || ex is SecurityException + || ex is AuthenticationException + || ex is FileNotFoundException; + + if (ignoreStackTrace) + { + _logger.LogError( + "Error processing request: {ExceptionMessage}. URL {Method} {Url}.", + ex.Message.TrimEnd('.'), + context.Request.Method, + context.Request.Path); + } + else + { + _logger.LogError( + ex, + "Error processing request. URL {Method} {Url}.", + ex.Message.TrimEnd('.'), + context.Request.Method, + context.Request.Path); + } + context.Response.StatusCode = GetStatusCode(ex); context.Response.ContentType = MediaTypeNames.Text.Plain; - var errorContent = NormalizeExceptionMessage(ex.Message); await context.Response.WriteAsync(errorContent).ConfigureAwait(false); } From be50fae38a27878cb520e20ed7956a72d7b05a84 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 23 Apr 2020 21:24:40 -0600 Subject: [PATCH 6/7] Address comments --- .../Extensions/ApiApplicationBuilderExtensions.cs | 10 ---------- Jellyfin.Server/Startup.cs | 3 ++- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs index 6c105ab65..0bd654c7d 100644 --- a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs @@ -24,15 +24,5 @@ namespace Jellyfin.Server.Extensions c.SwaggerEndpoint("/swagger/v1/swagger.json", "Jellyfin API V1"); }); } - - /// - /// Adds exception middleware to the application pipeline. - /// - /// The application builder. - /// The updated application builder. - public static IApplicationBuilder UseExceptionMiddleware(this IApplicationBuilder applicationBuilder) - { - return applicationBuilder.UseMiddleware(); - } } } diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs index 7a632f6c4..b17357fc3 100644 --- a/Jellyfin.Server/Startup.cs +++ b/Jellyfin.Server/Startup.cs @@ -1,4 +1,5 @@ using Jellyfin.Server.Extensions; +using Jellyfin.Server.Middleware; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using Microsoft.AspNetCore.Builder; @@ -58,7 +59,7 @@ namespace Jellyfin.Server app.UseDeveloperExceptionPage(); } - app.UseExceptionMiddleware(); + app.UseMiddleware(); app.UseWebSockets(); From b8508a57d8320085c01a7e2d4656b233169584f2 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 23 Apr 2020 21:38:40 -0600 Subject: [PATCH 7/7] oop --- Jellyfin.Server/Middleware/ExceptionMiddleware.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs index 6ebe01503..0d79bbfaf 100644 --- a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs +++ b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs @@ -79,7 +79,6 @@ namespace Jellyfin.Server.Middleware _logger.LogError( ex, "Error processing request. URL {Method} {Url}.", - ex.Message.TrimEnd('.'), context.Request.Method, context.Request.Path); }