CachingOpenApiProvider.cs 3.3 KB

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