Skip to content

Commit

Permalink
Cache downstream swagger docs (#265)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
teomane and emreteoman authored May 10, 2023
1 parent 777cc88 commit e9bc683
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 10 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,22 @@ public class OcelotSwaggerGenOptions
/// </value>
public bool GenerateDocsForGatewayItSelf { get; set; } = false;

/// <summary>
/// Gets or sets a value indicating downstream docs cache expire duration in seconds.
/// </summary>
/// <value>
/// <c>0</c> if it won't be cached; otherwise, cache expire duration in seconds.
/// </value>
public TimeSpan DownstreamDocsCacheExpire { get; set; } = TimeSpan.Zero;

/// <summary>
/// Generates docs for gateway it self with options.
/// </summary>
/// <param name="options">Gateway itself docs generation options.</param>
public void GenerateDocsDocsForGatewayItSelf(Action<OcelotGatewayItSelfSwaggerGenOptions> options = null)
{
GenerateDocsForGatewayItSelf = true;

OcelotGatewayItSelfSwaggerGenOptions = new OcelotGatewayItSelfSwaggerGenOptions();
options?.Invoke(OcelotGatewayItSelfSwaggerGenOptions);

Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -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;
}

/// <inheritdoc/>
public string Transform(string swaggerJson,
public string Transform(
string swaggerJson,
IEnumerable<RouteOptions> 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<RouteOptions> routes,
string serverOverride,
SwaggerEndPointOptions endPointOptions)
Expand Down Expand Up @@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -14,6 +16,7 @@ public class SwaggerJsonTransfromerBenchmark
private readonly string _swagger;
private readonly SwaggerJsonTransformer _transformer;
private readonly List<RouteOptions> _routeOptions;
private readonly IMemoryCache memoryCache = new MemoryCache(Options.Create(new MemoryCacheOptions()));

public SwaggerJsonTransfromerBenchmark()
{
Expand All @@ -40,7 +43,7 @@ public SwaggerJsonTransfromerBenchmark()
}
};

_transformer = new SwaggerJsonTransformer(new OcelotSwaggerGenOptions());
_transformer = new SwaggerJsonTransformer(new OcelotSwaggerGenOptions(), memoryCache);
}

[Benchmark]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -23,6 +24,7 @@
using Swashbuckle.AspNetCore.Swagger;
using Xunit;
using Xunit.Sdk;
using Options = Microsoft.Extensions.Options.Options;

namespace MMLib.SwaggerForOcelot.Tests
{
Expand All @@ -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();

Expand Down Expand Up @@ -82,7 +85,7 @@ public async Task AllowVersionPlaceholder()
string swaggerJson,
IEnumerable<RouteOptions> 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,
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -200,7 +204,7 @@ public async Task RespectDownstreamHttpVersionRouteSetting()
string swaggerJson,
IEnumerable<RouteOptions> 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,
Expand Down
7 changes: 4 additions & 3 deletions tests/MMLib.SwaggerForOcelot.Tests/SwaggerForOcelotShould.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using JsonDiffPatchDotNet;
using Microsoft.Extensions.Caching.Memory;
using MMLib.SwaggerForOcelot.Configuration;
using MMLib.SwaggerForOcelot.Transformation;
using Newtonsoft.Json.Linq;
Expand All @@ -19,22 +20,22 @@ 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(),
testData.Routes,
testData.HostOverride,
new SwaggerEndPointOptions()
{
TakeServersFromDownstreamService = testData.TakeServersFromDownstreamService,
RemoveUnusedComponentsFromScheme = testData.RemoveUnusedComponentsFromScheme
TakeServersFromDownstreamService = testData.TakeServersFromDownstreamService, RemoveUnusedComponentsFromScheme = testData.RemoveUnusedComponentsFromScheme
});

AreEqual(transformed, testData.ExpectedTransformedSwagger, testData.FileName);
Expand Down

0 comments on commit e9bc683

Please sign in to comment.