aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Server/Filters/CachingOpenApiProvider.cs
diff options
context:
space:
mode:
authorcrobibero <cody@robibe.ro>2025-12-03 14:04:28 -0500
committerBond_009 <bond.009@outlook.com>2025-12-03 14:04:28 -0500
commit6e74be0d46f409b7b63f02a29cbbbd572f40bd32 (patch)
tree2a494c7b7bee70231f33d0cfee6bc808d7159b87 /Jellyfin.Server/Filters/CachingOpenApiProvider.cs
parentdeb81eae1081edc0797fe17283f838da9c7e8a21 (diff)
Backport pull request #15672 from jellyfin/release-10.11.z
Cache OpenApi document generation Original-merge: 8cd56521570992d8587db5e1f80d4cb826537f31 Merged-by: anthonylavado <anthony@lavado.ca> Backported-by: Bond_009 <bond.009@outlook.com>
Diffstat (limited to 'Jellyfin.Server/Filters/CachingOpenApiProvider.cs')
-rw-r--r--Jellyfin.Server/Filters/CachingOpenApiProvider.cs89
1 files changed, 89 insertions, 0 deletions
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;
+ }
+}