From e9bc683f8fddace572fb6af61fa9e8d7b9252b31 Mon Sep 17 00:00:00 2001 From: EMRE TEOMAN Date: Wed, 10 May 2023 12:37:29 +0300 Subject: [PATCH] Cache downstream swagger docs (#265) * cache downstream transformed docs * remove multiple blank lines * cache expire from seconds to timespan * add section for downstream doc cache --------- Co-authored-by: Emre Teoman --- README.md | 14 +++++++ .../Configuration/OcelotSwaggerGenOptions.cs | 10 ++++- .../Transformation/SwaggerJsonTransformer.cs | 41 +++++++++++++++++-- .../SwaggerJsonTransfromerBenchmark.cs | 5 ++- .../SwaggerForOcelotMiddlewareShould.cs | 8 +++- .../SwaggerForOcelotShould.cs | 7 ++-- 6 files changed, 75 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 58cc768..48f469d 100644 --- a/README.md +++ b/README.md @@ -570,6 +570,20 @@ services.AddSwaggerForOcelot(Configuration, Note, this does not affect nor checks the swagger document's `securityDefinitions` property. +## Downstream Documentation Caching +If your downstream documentation is too large, the response time may be slow. +To address this issue, you can enable caching of transformed documentation by setting the +`DownstreamDocsCacheExpire` parameter. If this parameter is not provided, the documentation won't be cached. +If there is any change in the downstream documentation, the cache will be refreshed. + +```csharp +services.AddSwaggerForOcelot(Configuration, + setup => + { + setup.DownstreamDocsCacheExpire = TimeSpan.FromMinutes(10); + }); +``` + ## Limitation - Now, this library support only `{everything}` as a wildcard in routing definition. #68 diff --git a/src/MMLib.SwaggerForOcelot/Configuration/OcelotSwaggerGenOptions.cs b/src/MMLib.SwaggerForOcelot/Configuration/OcelotSwaggerGenOptions.cs index abdd0ef..9625aab 100644 --- a/src/MMLib.SwaggerForOcelot/Configuration/OcelotSwaggerGenOptions.cs +++ b/src/MMLib.SwaggerForOcelot/Configuration/OcelotSwaggerGenOptions.cs @@ -26,6 +26,14 @@ public class OcelotSwaggerGenOptions /// public bool GenerateDocsForGatewayItSelf { get; set; } = false; + /// + /// Gets or sets a value indicating downstream docs cache expire duration in seconds. + /// + /// + /// 0 if it won't be cached; otherwise, cache expire duration in seconds. + /// + public TimeSpan DownstreamDocsCacheExpire { get; set; } = TimeSpan.Zero; + /// /// Generates docs for gateway it self with options. /// @@ -33,7 +41,7 @@ public class OcelotSwaggerGenOptions public void GenerateDocsDocsForGatewayItSelf(Action options = null) { GenerateDocsForGatewayItSelf = true; - + OcelotGatewayItSelfSwaggerGenOptions = new OcelotGatewayItSelfSwaggerGenOptions(); options?.Invoke(OcelotGatewayItSelfSwaggerGenOptions); diff --git a/src/MMLib.SwaggerForOcelot/Transformation/SwaggerJsonTransformer.cs b/src/MMLib.SwaggerForOcelot/Transformation/SwaggerJsonTransformer.cs index 07750d4..7a5b8b1 100644 --- a/src/MMLib.SwaggerForOcelot/Transformation/SwaggerJsonTransformer.cs +++ b/src/MMLib.SwaggerForOcelot/Transformation/SwaggerJsonTransformer.cs @@ -1,10 +1,14 @@ -using Kros.IO; +using Kros.IO; +using Microsoft.Extensions.Caching.Memory; using MMLib.SwaggerForOcelot.Configuration; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using System.Security.Cryptography; +using System.Text; namespace MMLib.SwaggerForOcelot.Transformation { @@ -15,14 +19,37 @@ namespace MMLib.SwaggerForOcelot.Transformation public class SwaggerJsonTransformer : ISwaggerJsonTransformer { private readonly OcelotSwaggerGenOptions _ocelotSwaggerGenOptions; + private readonly IMemoryCache _memoryCache; - public SwaggerJsonTransformer(OcelotSwaggerGenOptions ocelotSwaggerGenOptions) + public SwaggerJsonTransformer(OcelotSwaggerGenOptions ocelotSwaggerGenOptions, IMemoryCache memoryCache) { _ocelotSwaggerGenOptions = ocelotSwaggerGenOptions; + _memoryCache = memoryCache; } /// - public string Transform(string swaggerJson, + public string Transform( + string swaggerJson, + IEnumerable routes, + string serverOverride, + SwaggerEndPointOptions endPointOptions) + { + if (_ocelotSwaggerGenOptions.DownstreamDocsCacheExpire == TimeSpan.Zero) + { + return TransformSwaggerOrOpenApi(swaggerJson, routes, serverOverride, endPointOptions); + } + + return _memoryCache.GetOrCreate( + ComputeHash(swaggerJson), + entry => + { + entry.AbsoluteExpirationRelativeToNow = _ocelotSwaggerGenOptions.DownstreamDocsCacheExpire; + return TransformSwaggerOrOpenApi(swaggerJson, routes, serverOverride, endPointOptions); + }); + } + + private string TransformSwaggerOrOpenApi( + string swaggerJson, IEnumerable routes, string serverOverride, SwaggerEndPointOptions endPointOptions) @@ -327,5 +354,13 @@ private static void TransformServerPaths(JObject openApi, string serverOverride, } } } + + private static string ComputeHash(string input) + { + using var sha256 = SHA256.Create(); + byte[] bytes = Encoding.UTF8.GetBytes(input); + byte[] hash = sha256.ComputeHash(bytes); + return Convert.ToBase64String(hash); + } } } diff --git a/tests/MMLib.SwaggerForOcelot.BenchmarkTests/SwaggerJsonTransfromerBenchmark.cs b/tests/MMLib.SwaggerForOcelot.BenchmarkTests/SwaggerJsonTransfromerBenchmark.cs index 56cfe9f..1ce5bd5 100644 --- a/tests/MMLib.SwaggerForOcelot.BenchmarkTests/SwaggerJsonTransfromerBenchmark.cs +++ b/tests/MMLib.SwaggerForOcelot.BenchmarkTests/SwaggerJsonTransfromerBenchmark.cs @@ -1,4 +1,6 @@ using BenchmarkDotNet.Attributes; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; using MMLib.SwaggerForOcelot.Configuration; using MMLib.SwaggerForOcelot.Transformation; using System.Collections.Generic; @@ -14,6 +16,7 @@ public class SwaggerJsonTransfromerBenchmark private readonly string _swagger; private readonly SwaggerJsonTransformer _transformer; private readonly List _routeOptions; + private readonly IMemoryCache memoryCache = new MemoryCache(Options.Create(new MemoryCacheOptions())); public SwaggerJsonTransfromerBenchmark() { @@ -40,7 +43,7 @@ public SwaggerJsonTransfromerBenchmark() } }; - _transformer = new SwaggerJsonTransformer(new OcelotSwaggerGenOptions()); + _transformer = new SwaggerJsonTransformer(new OcelotSwaggerGenOptions(), memoryCache); } [Benchmark] diff --git a/tests/MMLib.SwaggerForOcelot.Tests/SwaggerForOcelotMiddlewareShould.cs b/tests/MMLib.SwaggerForOcelot.Tests/SwaggerForOcelotMiddlewareShould.cs index 4f6f161..bd112a6 100644 --- a/tests/MMLib.SwaggerForOcelot.Tests/SwaggerForOcelotMiddlewareShould.cs +++ b/tests/MMLib.SwaggerForOcelot.Tests/SwaggerForOcelotMiddlewareShould.cs @@ -10,6 +10,7 @@ using JsonDiffPatchDotNet; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using MMLib.SwaggerForOcelot.Configuration; using MMLib.SwaggerForOcelot.Middleware; @@ -23,6 +24,7 @@ using Swashbuckle.AspNetCore.Swagger; using Xunit; using Xunit.Sdk; +using Options = Microsoft.Extensions.Options.Options; namespace MMLib.SwaggerForOcelot.Tests { @@ -35,6 +37,7 @@ public async Task AllowVersionPlaceholder() const string version = "v1"; const string key = "projects"; HttpContext httpContext = GetHttpContext(requestPath: $"/{version}/{key}"); + IMemoryCache memoryCache = new MemoryCache(Options.Create(new MemoryCacheOptions())); var next = new TestRequestDelegate(); @@ -82,7 +85,7 @@ public async Task AllowVersionPlaceholder() string swaggerJson, IEnumerable routeOptions, string serverOverride, - SwaggerEndPointOptions options) => new SwaggerJsonTransformer(OcelotSwaggerGenOptions.Default) + SwaggerEndPointOptions options) => new SwaggerJsonTransformer(OcelotSwaggerGenOptions.Default, memoryCache) .Transform(swaggerJson, routeOptions, serverOverride, options)); var swaggerForOcelotMiddleware = new SwaggerForOcelotMiddleware( next.Invoke, @@ -167,6 +170,7 @@ public async Task RespectDownstreamHttpVersionRouteSetting() const string version = "v1"; const string key = "projects"; HttpContext httpContext = GetHttpContext(requestPath: $"/{version}/{key}"); + IMemoryCache memoryCache = new MemoryCache(Options.Create(new MemoryCacheOptions())); var next = new TestRequestDelegate(); @@ -200,7 +204,7 @@ public async Task RespectDownstreamHttpVersionRouteSetting() string swaggerJson, IEnumerable routeOptions, string serverOverride, - SwaggerEndPointOptions options) => new SwaggerJsonTransformer(OcelotSwaggerGenOptions.Default) + SwaggerEndPointOptions options) => new SwaggerJsonTransformer(OcelotSwaggerGenOptions.Default, memoryCache) .Transform(swaggerJson, routeOptions, serverOverride, options)); var swaggerForOcelotMiddleware = new SwaggerForOcelotMiddleware( next.Invoke, diff --git a/tests/MMLib.SwaggerForOcelot.Tests/SwaggerForOcelotShould.cs b/tests/MMLib.SwaggerForOcelot.Tests/SwaggerForOcelotShould.cs index 1ee7b80..27b898a 100644 --- a/tests/MMLib.SwaggerForOcelot.Tests/SwaggerForOcelotShould.cs +++ b/tests/MMLib.SwaggerForOcelot.Tests/SwaggerForOcelotShould.cs @@ -1,4 +1,5 @@ using JsonDiffPatchDotNet; +using Microsoft.Extensions.Caching.Memory; using MMLib.SwaggerForOcelot.Configuration; using MMLib.SwaggerForOcelot.Transformation; using Newtonsoft.Json.Linq; @@ -19,13 +20,14 @@ public class SwaggerForOcelotShould public void TransferDownstreamSwaggerToUpstreamFormat(TestCase testData) { var options = new OcelotSwaggerGenOptions(); + IMemoryCache memoryCache = new MemoryCache(Microsoft.Extensions.Options.Options.Create(new MemoryCacheOptions())); foreach ((string key, string value) in testData.AuthenticationProviderKeyMap) { options.AuthenticationProviderKeyMap.Add(key, value); } - var transformer = new SwaggerJsonTransformer(options); + var transformer = new SwaggerJsonTransformer(options, memoryCache); string transformed = transformer.Transform( testData.DownstreamSwagger.ToString(), @@ -33,8 +35,7 @@ public void TransferDownstreamSwaggerToUpstreamFormat(TestCase testData) testData.HostOverride, new SwaggerEndPointOptions() { - TakeServersFromDownstreamService = testData.TakeServersFromDownstreamService, - RemoveUnusedComponentsFromScheme = testData.RemoveUnusedComponentsFromScheme + TakeServersFromDownstreamService = testData.TakeServersFromDownstreamService, RemoveUnusedComponentsFromScheme = testData.RemoveUnusedComponentsFromScheme }); AreEqual(transformed, testData.ExpectedTransformedSwagger, testData.FileName);