aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs5
-rw-r--r--Jellyfin.Server/Filters/CachingOpenApiProvider.cs89
2 files changed, 93 insertions, 1 deletions
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index 08c1a5065..04dd19eda 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -33,9 +33,11 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Models;
+using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;
using AuthenticationSchemes = Jellyfin.Api.Constants.AuthenticationSchemes;
@@ -259,7 +261,8 @@ namespace Jellyfin.Server.Extensions
c.OperationFilter<FileRequestFilter>();
c.OperationFilter<ParameterObsoleteFilter>();
c.DocumentFilter<AdditionalModelFilter>();
- });
+ })
+ .Replace(ServiceDescriptor.Transient<ISwaggerProvider, CachingOpenApiProvider>());
}
private static void AddPolicy(this AuthorizationOptions authorizationOptions, string policyName, IAuthorizationRequirement authorizationRequirement)
diff --git a/Jellyfin.Server/Filters/CachingOpenApiProvider.cs b/Jellyfin.Server/Filters/CachingOpenApiProvider.cs
new file mode 100644
index 000000000..4169f2fb3
--- /dev/null
+++ b/Jellyfin.Server/Filters/CachingOpenApiProvider.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Threading;
+using Microsoft.AspNetCore.Mvc.ApiExplorer;
+using Microsoft.Extensions.Caching.Memory;
+using Microsoft.Extensions.Options;
+using Microsoft.OpenApi.Models;
+using Swashbuckle.AspNetCore.Swagger;
+using Swashbuckle.AspNetCore.SwaggerGen;
+
+namespace Jellyfin.Server.Filters;
+
+/// <summary>
+/// OpenApi provider with caching.
+/// </summary>
+internal sealed class CachingOpenApiProvider : ISwaggerProvider
+{
+ private const string CacheKey = "openapi.json";
+
+ private static readonly MemoryCacheEntryOptions _cacheOptions = new() { SlidingExpiration = TimeSpan.FromMinutes(5) };
+ private static readonly SemaphoreSlim _lock = new(1, 1);
+ private static readonly TimeSpan _lockTimeout = TimeSpan.FromSeconds(1);
+
+ private readonly IMemoryCache _memoryCache;
+ private readonly SwaggerGenerator _swaggerGenerator;
+ private readonly SwaggerGeneratorOptions _swaggerGeneratorOptions;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CachingOpenApiProvider"/> class.
+ /// </summary>
+ /// <param name="optionsAccessor">The options accessor.</param>
+ /// <param name="apiDescriptionsProvider">The api descriptions provider.</param>
+ /// <param name="schemaGenerator">The schema generator.</param>
+ /// <param name="memoryCache">The memory cache.</param>
+ public CachingOpenApiProvider(
+ IOptions<SwaggerGeneratorOptions> optionsAccessor,
+ IApiDescriptionGroupCollectionProvider apiDescriptionsProvider,
+ ISchemaGenerator schemaGenerator,
+ IMemoryCache memoryCache)
+ {
+ _swaggerGeneratorOptions = optionsAccessor.Value;
+ _swaggerGenerator = new SwaggerGenerator(_swaggerGeneratorOptions, apiDescriptionsProvider, schemaGenerator);
+ _memoryCache = memoryCache;
+ }
+
+ /// <inheritdoc />
+ public OpenApiDocument GetSwagger(string documentName, string? host = null, string? basePath = null)
+ {
+ if (_memoryCache.TryGetValue(CacheKey, out OpenApiDocument? openApiDocument) && openApiDocument is not null)
+ {
+ return AdjustDocument(openApiDocument, host, basePath);
+ }
+
+ var acquired = _lock.Wait(_lockTimeout);
+ try
+ {
+ if (_memoryCache.TryGetValue(CacheKey, out openApiDocument) && openApiDocument is not null)
+ {
+ return AdjustDocument(openApiDocument, host, basePath);
+ }
+
+ if (!acquired)
+ {
+ throw new InvalidOperationException("OpenApi document is generating");
+ }
+
+ openApiDocument = _swaggerGenerator.GetSwagger(documentName);
+ _memoryCache.Set(CacheKey, openApiDocument, _cacheOptions);
+ return AdjustDocument(openApiDocument, host, basePath);
+ }
+ finally
+ {
+ if (acquired)
+ {
+ _lock.Release();
+ }
+ }
+ }
+
+ private OpenApiDocument AdjustDocument(OpenApiDocument document, string? host, string? basePath)
+ {
+ document.Servers = _swaggerGeneratorOptions.Servers.Count != 0
+ ? _swaggerGeneratorOptions.Servers
+ : string.IsNullOrEmpty(host) && string.IsNullOrEmpty(basePath)
+ ? []
+ : [new OpenApiServer { Url = $"{host}{basePath}" }];
+
+ return document;
+ }
+}