| | | 1 | | using System; |
| | | 2 | | using AsyncKeyedLock; |
| | | 3 | | using Microsoft.AspNetCore.Mvc.ApiExplorer; |
| | | 4 | | using Microsoft.Extensions.Caching.Memory; |
| | | 5 | | using Microsoft.Extensions.Options; |
| | | 6 | | using Microsoft.OpenApi.Models; |
| | | 7 | | using Swashbuckle.AspNetCore.Swagger; |
| | | 8 | | using Swashbuckle.AspNetCore.SwaggerGen; |
| | | 9 | | |
| | | 10 | | namespace Jellyfin.Server.Filters; |
| | | 11 | | |
| | | 12 | | /// <summary> |
| | | 13 | | /// OpenApi provider with caching. |
| | | 14 | | /// </summary> |
| | | 15 | | internal sealed class CachingOpenApiProvider : ISwaggerProvider |
| | | 16 | | { |
| | | 17 | | private const string CacheKey = "openapi.json"; |
| | | 18 | | |
| | 1 | 19 | | private static readonly MemoryCacheEntryOptions _cacheOptions = new() { SlidingExpiration = TimeSpan.FromMinutes(5) |
| | 1 | 20 | | private static readonly AsyncNonKeyedLocker _lock = new(1); |
| | 1 | 21 | | private static readonly TimeSpan _lockTimeout = TimeSpan.FromSeconds(1); |
| | | 22 | | |
| | | 23 | | private readonly IMemoryCache _memoryCache; |
| | | 24 | | private readonly SwaggerGenerator _swaggerGenerator; |
| | | 25 | | private readonly SwaggerGeneratorOptions _swaggerGeneratorOptions; |
| | | 26 | | |
| | | 27 | | /// <summary> |
| | | 28 | | /// Initializes a new instance of the <see cref="CachingOpenApiProvider"/> class. |
| | | 29 | | /// </summary> |
| | | 30 | | /// <param name="optionsAccessor">The options accessor.</param> |
| | | 31 | | /// <param name="apiDescriptionsProvider">The api descriptions provider.</param> |
| | | 32 | | /// <param name="schemaGenerator">The schema generator.</param> |
| | | 33 | | /// <param name="memoryCache">The memory cache.</param> |
| | | 34 | | public CachingOpenApiProvider( |
| | | 35 | | IOptions<SwaggerGeneratorOptions> optionsAccessor, |
| | | 36 | | IApiDescriptionGroupCollectionProvider apiDescriptionsProvider, |
| | | 37 | | ISchemaGenerator schemaGenerator, |
| | | 38 | | IMemoryCache memoryCache) |
| | | 39 | | { |
| | 169 | 40 | | _swaggerGeneratorOptions = optionsAccessor.Value; |
| | 169 | 41 | | _swaggerGenerator = new SwaggerGenerator(_swaggerGeneratorOptions, apiDescriptionsProvider, schemaGenerator); |
| | 169 | 42 | | _memoryCache = memoryCache; |
| | 169 | 43 | | } |
| | | 44 | | |
| | | 45 | | /// <inheritdoc /> |
| | | 46 | | public OpenApiDocument GetSwagger(string documentName, string? host = null, string? basePath = null) |
| | | 47 | | { |
| | 1 | 48 | | if (_memoryCache.TryGetValue(CacheKey, out OpenApiDocument? openApiDocument) && openApiDocument is not null) |
| | | 49 | | { |
| | 0 | 50 | | return AdjustDocument(openApiDocument, host, basePath); |
| | | 51 | | } |
| | | 52 | | |
| | 1 | 53 | | using var acquired = _lock.LockOrNull(_lockTimeout); |
| | 1 | 54 | | if (_memoryCache.TryGetValue(CacheKey, out openApiDocument) && openApiDocument is not null) |
| | | 55 | | { |
| | 0 | 56 | | return AdjustDocument(openApiDocument, host, basePath); |
| | | 57 | | } |
| | | 58 | | |
| | 1 | 59 | | if (acquired is null) |
| | | 60 | | { |
| | 0 | 61 | | throw new InvalidOperationException("OpenApi document is generating"); |
| | | 62 | | } |
| | | 63 | | |
| | 1 | 64 | | openApiDocument = _swaggerGenerator.GetSwagger(documentName); |
| | 1 | 65 | | _memoryCache.Set(CacheKey, openApiDocument, _cacheOptions); |
| | 1 | 66 | | return AdjustDocument(openApiDocument, host, basePath); |
| | 1 | 67 | | } |
| | | 68 | | |
| | | 69 | | private OpenApiDocument AdjustDocument(OpenApiDocument document, string? host, string? basePath) |
| | | 70 | | { |
| | 1 | 71 | | document.Servers = _swaggerGeneratorOptions.Servers.Count != 0 |
| | 1 | 72 | | ? _swaggerGeneratorOptions.Servers |
| | 1 | 73 | | : string.IsNullOrEmpty(host) && string.IsNullOrEmpty(basePath) |
| | 1 | 74 | | ? [] |
| | 1 | 75 | | : [new OpenApiServer { Url = $"{host}{basePath}" }]; |
| | | 76 | | |
| | 1 | 77 | | return document; |
| | | 78 | | } |
| | | 79 | | } |