mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 02:42:52 +08:00
Feature/dont validate cached content headers (#406)
* #372 use period timespan to decide when client can make requests again * #400 dont validate cached body headers
This commit is contained in:
parent
9979f8a4b8
commit
8e1a5ce827
@ -1,120 +1,120 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
using Ocelot.Middleware;
|
using Ocelot.Middleware;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Ocelot.Middleware.Multiplexer;
|
using Ocelot.Middleware.Multiplexer;
|
||||||
|
|
||||||
namespace Ocelot.Cache.Middleware
|
namespace Ocelot.Cache.Middleware
|
||||||
{
|
{
|
||||||
public class OutputCacheMiddleware : OcelotMiddleware
|
public class OutputCacheMiddleware : OcelotMiddleware
|
||||||
{
|
{
|
||||||
private readonly OcelotRequestDelegate _next;
|
private readonly OcelotRequestDelegate _next;
|
||||||
private readonly IOcelotCache<CachedResponse> _outputCache;
|
private readonly IOcelotCache<CachedResponse> _outputCache;
|
||||||
private readonly IRegionCreator _regionCreator;
|
private readonly IRegionCreator _regionCreator;
|
||||||
|
|
||||||
public OutputCacheMiddleware(OcelotRequestDelegate next,
|
public OutputCacheMiddleware(OcelotRequestDelegate next,
|
||||||
IOcelotLoggerFactory loggerFactory,
|
IOcelotLoggerFactory loggerFactory,
|
||||||
IOcelotCache<CachedResponse> outputCache,
|
IOcelotCache<CachedResponse> outputCache,
|
||||||
IRegionCreator regionCreator)
|
IRegionCreator regionCreator)
|
||||||
:base(loggerFactory.CreateLogger<OutputCacheMiddleware>())
|
:base(loggerFactory.CreateLogger<OutputCacheMiddleware>())
|
||||||
{
|
{
|
||||||
_next = next;
|
_next = next;
|
||||||
_outputCache = outputCache;
|
_outputCache = outputCache;
|
||||||
_regionCreator = regionCreator;
|
_regionCreator = regionCreator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Invoke(DownstreamContext context)
|
public async Task Invoke(DownstreamContext context)
|
||||||
{
|
{
|
||||||
if (!context.DownstreamReRoute.IsCached)
|
if (!context.DownstreamReRoute.IsCached)
|
||||||
{
|
{
|
||||||
await _next.Invoke(context);
|
await _next.Invoke(context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var downstreamUrlKey = $"{context.DownstreamRequest.Method}-{context.DownstreamRequest.OriginalString}";
|
var downstreamUrlKey = $"{context.DownstreamRequest.Method}-{context.DownstreamRequest.OriginalString}";
|
||||||
|
|
||||||
Logger.LogDebug($"Started checking cache for {downstreamUrlKey}");
|
Logger.LogDebug($"Started checking cache for {downstreamUrlKey}");
|
||||||
|
|
||||||
var cached = _outputCache.Get(downstreamUrlKey, context.DownstreamReRoute.CacheOptions.Region);
|
var cached = _outputCache.Get(downstreamUrlKey, context.DownstreamReRoute.CacheOptions.Region);
|
||||||
|
|
||||||
if (cached != null)
|
if (cached != null)
|
||||||
{
|
{
|
||||||
Logger.LogDebug($"cache entry exists for {downstreamUrlKey}");
|
Logger.LogDebug($"cache entry exists for {downstreamUrlKey}");
|
||||||
|
|
||||||
var response = CreateHttpResponseMessage(cached);
|
var response = CreateHttpResponseMessage(cached);
|
||||||
SetHttpResponseMessageThisRequest(context, response);
|
SetHttpResponseMessageThisRequest(context, response);
|
||||||
|
|
||||||
Logger.LogDebug($"finished returned cached response for {downstreamUrlKey}");
|
Logger.LogDebug($"finished returned cached response for {downstreamUrlKey}");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogDebug($"no resonse cached for {downstreamUrlKey}");
|
Logger.LogDebug($"no resonse cached for {downstreamUrlKey}");
|
||||||
|
|
||||||
await _next.Invoke(context);
|
await _next.Invoke(context);
|
||||||
|
|
||||||
if (context.IsError)
|
if (context.IsError)
|
||||||
{
|
{
|
||||||
Logger.LogDebug($"there was a pipeline error for {downstreamUrlKey}");
|
Logger.LogDebug($"there was a pipeline error for {downstreamUrlKey}");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cached = await CreateCachedResponse(context.DownstreamResponse);
|
cached = await CreateCachedResponse(context.DownstreamResponse);
|
||||||
|
|
||||||
_outputCache.Add(downstreamUrlKey, cached, TimeSpan.FromSeconds(context.DownstreamReRoute.CacheOptions.TtlSeconds), context.DownstreamReRoute.CacheOptions.Region);
|
_outputCache.Add(downstreamUrlKey, cached, TimeSpan.FromSeconds(context.DownstreamReRoute.CacheOptions.TtlSeconds), context.DownstreamReRoute.CacheOptions.Region);
|
||||||
|
|
||||||
Logger.LogDebug($"finished response added to cache for {downstreamUrlKey}");
|
Logger.LogDebug($"finished response added to cache for {downstreamUrlKey}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetHttpResponseMessageThisRequest(DownstreamContext context, DownstreamResponse response)
|
private void SetHttpResponseMessageThisRequest(DownstreamContext context, DownstreamResponse response)
|
||||||
{
|
{
|
||||||
context.DownstreamResponse = response;
|
context.DownstreamResponse = response;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal DownstreamResponse CreateHttpResponseMessage(CachedResponse cached)
|
internal DownstreamResponse CreateHttpResponseMessage(CachedResponse cached)
|
||||||
{
|
{
|
||||||
if (cached == null)
|
if (cached == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var content = new MemoryStream(Convert.FromBase64String(cached.Body));
|
var content = new MemoryStream(Convert.FromBase64String(cached.Body));
|
||||||
|
|
||||||
var streamContent = new StreamContent(content);
|
var streamContent = new StreamContent(content);
|
||||||
|
|
||||||
foreach (var header in cached.ContentHeaders)
|
foreach (var header in cached.ContentHeaders)
|
||||||
{
|
{
|
||||||
streamContent.Headers.Add(header.Key, header.Value);
|
streamContent.Headers.TryAddWithoutValidation(header.Key, header.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new DownstreamResponse(streamContent, cached.StatusCode, cached.Headers.ToList());
|
return new DownstreamResponse(streamContent, cached.StatusCode, cached.Headers.ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<CachedResponse> CreateCachedResponse(DownstreamResponse response)
|
internal async Task<CachedResponse> CreateCachedResponse(DownstreamResponse response)
|
||||||
{
|
{
|
||||||
if (response == null)
|
if (response == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var statusCode = response.StatusCode;
|
var statusCode = response.StatusCode;
|
||||||
var headers = response.Headers.ToDictionary(v => v.Key, v => v.Values);
|
var headers = response.Headers.ToDictionary(v => v.Key, v => v.Values);
|
||||||
string body = null;
|
string body = null;
|
||||||
|
|
||||||
if (response.Content != null)
|
if (response.Content != null)
|
||||||
{
|
{
|
||||||
var content = await response.Content.ReadAsByteArrayAsync();
|
var content = await response.Content.ReadAsByteArrayAsync();
|
||||||
body = Convert.ToBase64String(content);
|
body = Convert.ToBase64String(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
var contentHeaders = response?.Content?.Headers.ToDictionary(v => v.Key, v => v.Value);
|
var contentHeaders = response?.Content?.Headers.ToDictionary(v => v.Key, v => v.Value);
|
||||||
|
|
||||||
var cached = new CachedResponse(statusCode, headers, body, contentHeaders);
|
var cached = new CachedResponse(statusCode, headers, body, contentHeaders);
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,191 +1,239 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Ocelot.AcceptanceTests
|
namespace Ocelot.AcceptanceTests
|
||||||
{
|
{
|
||||||
public class CachingTests : IDisposable
|
public class CachingTests : IDisposable
|
||||||
{
|
{
|
||||||
private IWebHost _builder;
|
private IWebHost _builder;
|
||||||
private readonly Steps _steps;
|
private readonly Steps _steps;
|
||||||
|
|
||||||
public CachingTests()
|
public CachingTests()
|
||||||
{
|
{
|
||||||
_steps = new Steps();
|
_steps = new Steps();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_return_cached_response()
|
public void should_return_cached_response()
|
||||||
{
|
{
|
||||||
var configuration = new FileConfiguration
|
var configuration = new FileConfiguration
|
||||||
{
|
{
|
||||||
ReRoutes = new List<FileReRoute>
|
ReRoutes = new List<FileReRoute>
|
||||||
{
|
{
|
||||||
new FileReRoute
|
new FileReRoute
|
||||||
{
|
{
|
||||||
DownstreamPathTemplate = "/",
|
DownstreamPathTemplate = "/",
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
{
|
{
|
||||||
new FileHostAndPort
|
new FileHostAndPort
|
||||||
{
|
{
|
||||||
Host = "localhost",
|
Host = "localhost",
|
||||||
Port = 51899,
|
Port = 51899,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
DownstreamScheme = "http",
|
DownstreamScheme = "http",
|
||||||
UpstreamPathTemplate = "/",
|
UpstreamPathTemplate = "/",
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
FileCacheOptions = new FileCacheOptions
|
FileCacheOptions = new FileCacheOptions
|
||||||
{
|
{
|
||||||
TtlSeconds = 100
|
TtlSeconds = 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", 200, "Hello from Laura"))
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", 200, "Hello from Laura", null, null))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
.Given(x => x.GivenTheServiceNowReturns("http://localhost:51899", 200, "Hello from Tom"))
|
.Given(x => x.GivenTheServiceNowReturns("http://localhost:51899", 200, "Hello from Tom"))
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
.And(x => _steps.ThenTheContentLengthIs(16))
|
.And(x => _steps.ThenTheContentLengthIs(16))
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_return_cached_response_when_using_jsonserialized_cache()
|
public void should_return_cached_response_with_expires_header()
|
||||||
{
|
{
|
||||||
var configuration = new FileConfiguration
|
var configuration = new FileConfiguration
|
||||||
{
|
{
|
||||||
ReRoutes = new List<FileReRoute>
|
ReRoutes = new List<FileReRoute>
|
||||||
{
|
{
|
||||||
new FileReRoute
|
new FileReRoute
|
||||||
{
|
{
|
||||||
DownstreamPathTemplate = "/",
|
DownstreamPathTemplate = "/",
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
{
|
{
|
||||||
new FileHostAndPort
|
new FileHostAndPort
|
||||||
{
|
{
|
||||||
Host = "localhost",
|
Host = "localhost",
|
||||||
Port = 51899,
|
Port = 52839,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
DownstreamScheme = "http",
|
DownstreamScheme = "http",
|
||||||
UpstreamPathTemplate = "/",
|
UpstreamPathTemplate = "/",
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
FileCacheOptions = new FileCacheOptions
|
FileCacheOptions = new FileCacheOptions
|
||||||
{
|
{
|
||||||
TtlSeconds = 100
|
TtlSeconds = 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", 200, "Hello from Laura"))
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:52839", 200, "Hello from Laura", "Expires", "-1"))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => _steps.GivenOcelotIsRunningUsingJsonSerializedCache())
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
.Given(x => x.GivenTheServiceNowReturns("http://localhost:51899", 200, "Hello from Tom"))
|
.Given(x => x.GivenTheServiceNowReturns("http://localhost:52839", 200, "Hello from Tom"))
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
.BDDfy();
|
.And(x => _steps.ThenTheContentLengthIs(16))
|
||||||
}
|
.And(x => _steps.ThenTheResponseBodyHeaderIs("Expires", "-1"))
|
||||||
|
.BDDfy();
|
||||||
[Fact]
|
}
|
||||||
public void should_not_return_cached_response_as_ttl_expires()
|
|
||||||
{
|
[Fact]
|
||||||
var configuration = new FileConfiguration
|
public void should_return_cached_response_when_using_jsonserialized_cache()
|
||||||
{
|
{
|
||||||
ReRoutes = new List<FileReRoute>
|
var configuration = new FileConfiguration
|
||||||
{
|
{
|
||||||
new FileReRoute
|
ReRoutes = new List<FileReRoute>
|
||||||
{
|
{
|
||||||
DownstreamPathTemplate = "/",
|
new FileReRoute
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
{
|
||||||
{
|
DownstreamPathTemplate = "/",
|
||||||
new FileHostAndPort
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
{
|
{
|
||||||
Host = "localhost",
|
new FileHostAndPort
|
||||||
Port = 51899,
|
{
|
||||||
}
|
Host = "localhost",
|
||||||
},
|
Port = 51899,
|
||||||
DownstreamScheme = "http",
|
}
|
||||||
UpstreamPathTemplate = "/",
|
},
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
DownstreamScheme = "http",
|
||||||
FileCacheOptions = new FileCacheOptions
|
UpstreamPathTemplate = "/",
|
||||||
{
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
TtlSeconds = 1
|
FileCacheOptions = new FileCacheOptions
|
||||||
}
|
{
|
||||||
}
|
TtlSeconds = 100
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
}
|
||||||
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", 200, "Hello from Laura"))
|
};
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", 200, "Hello from Laura", null, null))
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
.And(x => _steps.GivenOcelotIsRunningUsingJsonSerializedCache())
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
.Given(x => x.GivenTheServiceNowReturns("http://localhost:51899", 200, "Hello from Tom"))
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
.And(x => x.GivenTheCacheExpires())
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
.Given(x => x.GivenTheServiceNowReturns("http://localhost:51899", 200, "Hello from Tom"))
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom"))
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
.BDDfy();
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
}
|
.BDDfy();
|
||||||
|
}
|
||||||
private void GivenTheCacheExpires()
|
|
||||||
{
|
[Fact]
|
||||||
Thread.Sleep(1000);
|
public void should_not_return_cached_response_as_ttl_expires()
|
||||||
}
|
{
|
||||||
|
var configuration = new FileConfiguration
|
||||||
private void GivenTheServiceNowReturns(string url, int statusCode, string responseBody)
|
{
|
||||||
{
|
ReRoutes = new List<FileReRoute>
|
||||||
_builder.Dispose();
|
{
|
||||||
GivenThereIsAServiceRunningOn(url, statusCode, responseBody);
|
new FileReRoute
|
||||||
}
|
{
|
||||||
|
DownstreamPathTemplate = "/",
|
||||||
private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody)
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
{
|
{
|
||||||
_builder = new WebHostBuilder()
|
new FileHostAndPort
|
||||||
.UseUrls(url)
|
{
|
||||||
.UseKestrel()
|
Host = "localhost",
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
Port = 51899,
|
||||||
.UseIISIntegration()
|
}
|
||||||
.UseUrls(url)
|
},
|
||||||
.Configure(app =>
|
DownstreamScheme = "http",
|
||||||
{
|
UpstreamPathTemplate = "/",
|
||||||
app.Run(async context =>
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
{
|
FileCacheOptions = new FileCacheOptions
|
||||||
context.Response.StatusCode = statusCode;
|
{
|
||||||
await context.Response.WriteAsync(responseBody);
|
TtlSeconds = 1
|
||||||
});
|
}
|
||||||
})
|
}
|
||||||
.Build();
|
}
|
||||||
|
};
|
||||||
_builder.Start();
|
|
||||||
}
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", 200, "Hello from Laura", null, null))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
public void Dispose()
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
{
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
_builder?.Dispose();
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
_steps.Dispose();
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
}
|
.Given(x => x.GivenTheServiceNowReturns("http://localhost:51899", 200, "Hello from Tom"))
|
||||||
}
|
.And(x => x.GivenTheCacheExpires())
|
||||||
}
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom"))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheCacheExpires()
|
||||||
|
{
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheServiceNowReturns(string url, int statusCode, string responseBody)
|
||||||
|
{
|
||||||
|
_builder.Dispose();
|
||||||
|
GivenThereIsAServiceRunningOn(url, statusCode, responseBody, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody, string key, string value)
|
||||||
|
{
|
||||||
|
_builder = new WebHostBuilder()
|
||||||
|
.UseUrls(url)
|
||||||
|
.UseKestrel()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseIISIntegration()
|
||||||
|
.UseUrls(url)
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.Run(async context =>
|
||||||
|
{
|
||||||
|
if(!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(key))
|
||||||
|
{
|
||||||
|
context.Response.Headers.Add(key, value);
|
||||||
|
}
|
||||||
|
context.Response.StatusCode = statusCode;
|
||||||
|
await context.Response.WriteAsync(responseBody);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_builder.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_builder?.Dispose();
|
||||||
|
_steps.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -392,6 +392,12 @@ namespace Ocelot.AcceptanceTests
|
|||||||
header.First().ShouldBe(value);
|
header.First().ShouldBe(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ThenTheResponseBodyHeaderIs(string key, string value)
|
||||||
|
{
|
||||||
|
var header = _response.Content.Headers.GetValues(key);
|
||||||
|
header.First().ShouldBe(value);
|
||||||
|
}
|
||||||
|
|
||||||
public void ThenTheTraceHeaderIsSet(string key)
|
public void ThenTheTraceHeaderIsSet(string key)
|
||||||
{
|
{
|
||||||
var header = _response.Headers.GetValues(key);
|
var header = _response.Headers.GetValues(key);
|
||||||
|
@ -1,124 +1,140 @@
|
|||||||
namespace Ocelot.UnitTests.Cache
|
namespace Ocelot.UnitTests.Cache
|
||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Ocelot.Cache;
|
using Ocelot.Cache;
|
||||||
using Ocelot.Cache.Middleware;
|
using Ocelot.Cache.Middleware;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using Ocelot.Configuration.Builder;
|
using Ocelot.Configuration.Builder;
|
||||||
using Ocelot.DownstreamRouteFinder;
|
using Ocelot.DownstreamRouteFinder;
|
||||||
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using Ocelot.Middleware;
|
using Ocelot.Middleware;
|
||||||
using Ocelot.Middleware.Multiplexer;
|
using Ocelot.Middleware.Multiplexer;
|
||||||
|
|
||||||
public class OutputCacheMiddlewareTests
|
public class OutputCacheMiddlewareTests
|
||||||
{
|
{
|
||||||
private readonly Mock<IOcelotCache<CachedResponse>> _cacheManager;
|
private readonly Mock<IOcelotCache<CachedResponse>> _cacheManager;
|
||||||
private readonly Mock<IOcelotLoggerFactory> _loggerFactory;
|
private readonly Mock<IOcelotLoggerFactory> _loggerFactory;
|
||||||
private Mock<IOcelotLogger> _logger;
|
private Mock<IOcelotLogger> _logger;
|
||||||
private OutputCacheMiddleware _middleware;
|
private OutputCacheMiddleware _middleware;
|
||||||
private readonly DownstreamContext _downstreamContext;
|
private readonly DownstreamContext _downstreamContext;
|
||||||
private readonly OcelotRequestDelegate _next;
|
private readonly OcelotRequestDelegate _next;
|
||||||
private CachedResponse _response;
|
private CachedResponse _response;
|
||||||
private readonly IRegionCreator _regionCreator;
|
private readonly IRegionCreator _regionCreator;
|
||||||
|
|
||||||
public OutputCacheMiddlewareTests()
|
public OutputCacheMiddlewareTests()
|
||||||
{
|
{
|
||||||
_cacheManager = new Mock<IOcelotCache<CachedResponse>>();
|
_cacheManager = new Mock<IOcelotCache<CachedResponse>>();
|
||||||
_regionCreator = new RegionCreator();
|
_regionCreator = new RegionCreator();
|
||||||
_downstreamContext = new DownstreamContext(new DefaultHttpContext());
|
_downstreamContext = new DownstreamContext(new DefaultHttpContext());
|
||||||
_loggerFactory = new Mock<IOcelotLoggerFactory>();
|
_loggerFactory = new Mock<IOcelotLoggerFactory>();
|
||||||
_logger = new Mock<IOcelotLogger>();
|
_logger = new Mock<IOcelotLogger>();
|
||||||
_loggerFactory.Setup(x => x.CreateLogger<OutputCacheMiddleware>()).Returns(_logger.Object);
|
_loggerFactory.Setup(x => x.CreateLogger<OutputCacheMiddleware>()).Returns(_logger.Object);
|
||||||
_next = context => Task.CompletedTask;
|
_next = context => Task.CompletedTask;
|
||||||
_downstreamContext.DownstreamRequest = new Ocelot.Request.Middleware.DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123"));
|
_downstreamContext.DownstreamRequest = new Ocelot.Request.Middleware.DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_returned_cached_item_when_it_is_in_cache()
|
public void should_returned_cached_item_when_it_is_in_cache()
|
||||||
{
|
{
|
||||||
var headers = new Dictionary<string, IEnumerable<string>>
|
var headers = new Dictionary<string, IEnumerable<string>>
|
||||||
{
|
{
|
||||||
{ "test", new List<string> { "test" } }
|
{ "test", new List<string> { "test" } }
|
||||||
};
|
};
|
||||||
|
|
||||||
var contentHeaders = new Dictionary<string, IEnumerable<string>>
|
var contentHeaders = new Dictionary<string, IEnumerable<string>>
|
||||||
{
|
{
|
||||||
{ "content-type", new List<string> { "application/json" } }
|
{ "content-type", new List<string> { "application/json" } }
|
||||||
};
|
};
|
||||||
|
|
||||||
var cachedResponse = new CachedResponse(HttpStatusCode.OK, headers, "", contentHeaders);
|
var cachedResponse = new CachedResponse(HttpStatusCode.OK, headers, "", contentHeaders);
|
||||||
this.Given(x => x.GivenThereIsACachedResponse(cachedResponse))
|
this.Given(x => x.GivenThereIsACachedResponse(cachedResponse))
|
||||||
.And(x => x.GivenTheDownstreamRouteIs())
|
.And(x => x.GivenTheDownstreamRouteIs())
|
||||||
.When(x => x.WhenICallTheMiddleware())
|
.When(x => x.WhenICallTheMiddleware())
|
||||||
.Then(x => x.ThenTheCacheGetIsCalledCorrectly())
|
.Then(x => x.ThenTheCacheGetIsCalledCorrectly())
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_continue_with_pipeline_and_cache_response()
|
public void should_returned_cached_item_when_it_is_in_cache_expires_header()
|
||||||
{
|
{
|
||||||
this.Given(x => x.GivenResponseIsNotCached())
|
var contentHeaders = new Dictionary<string, IEnumerable<string>>
|
||||||
.And(x => x.GivenTheDownstreamRouteIs())
|
{
|
||||||
.When(x => x.WhenICallTheMiddleware())
|
{ "Expires", new List<string> { "-1" } }
|
||||||
.Then(x => x.ThenTheCacheAddIsCalledCorrectly())
|
};
|
||||||
.BDDfy();
|
|
||||||
}
|
var cachedResponse = new CachedResponse(HttpStatusCode.OK, new Dictionary<string, IEnumerable<string>>(), "", contentHeaders);
|
||||||
|
this.Given(x => x.GivenThereIsACachedResponse(cachedResponse))
|
||||||
private void WhenICallTheMiddleware()
|
.And(x => x.GivenTheDownstreamRouteIs())
|
||||||
{
|
.When(x => x.WhenICallTheMiddleware())
|
||||||
_middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cacheManager.Object, _regionCreator);
|
.Then(x => x.ThenTheCacheGetIsCalledCorrectly())
|
||||||
_middleware.Invoke(_downstreamContext).GetAwaiter().GetResult();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenThereIsACachedResponse(CachedResponse response)
|
[Fact]
|
||||||
{
|
public void should_continue_with_pipeline_and_cache_response()
|
||||||
_response = response;
|
{
|
||||||
_cacheManager
|
this.Given(x => x.GivenResponseIsNotCached(new HttpResponseMessage()))
|
||||||
.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<string>()))
|
.And(x => x.GivenTheDownstreamRouteIs())
|
||||||
.Returns(_response);
|
.When(x => x.WhenICallTheMiddleware())
|
||||||
}
|
.Then(x => x.ThenTheCacheAddIsCalledCorrectly())
|
||||||
|
.BDDfy();
|
||||||
private void GivenResponseIsNotCached()
|
}
|
||||||
{
|
|
||||||
_downstreamContext.DownstreamResponse = new DownstreamResponse(new HttpResponseMessage());
|
private void WhenICallTheMiddleware()
|
||||||
}
|
{
|
||||||
|
_middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cacheManager.Object, _regionCreator);
|
||||||
private void GivenTheDownstreamRouteIs()
|
_middleware.Invoke(_downstreamContext).GetAwaiter().GetResult();
|
||||||
{
|
}
|
||||||
var reRoute = new ReRouteBuilder()
|
|
||||||
.WithDownstreamReRoute(new DownstreamReRouteBuilder()
|
private void GivenThereIsACachedResponse(CachedResponse response)
|
||||||
.WithIsCached(true)
|
{
|
||||||
.WithCacheOptions(new CacheOptions(100, "kanken"))
|
_response = response;
|
||||||
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
_cacheManager
|
||||||
.Build())
|
.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<string>()))
|
||||||
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
.Returns(_response);
|
||||||
.Build();
|
}
|
||||||
|
|
||||||
var downstreamRoute = new DownstreamRoute(new List<PlaceholderNameAndValue>(), reRoute);
|
private void GivenResponseIsNotCached(HttpResponseMessage responseMessage)
|
||||||
|
{
|
||||||
_downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues;
|
_downstreamContext.DownstreamResponse = new DownstreamResponse(responseMessage);
|
||||||
_downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0];
|
}
|
||||||
}
|
|
||||||
|
private void GivenTheDownstreamRouteIs()
|
||||||
private void ThenTheCacheGetIsCalledCorrectly()
|
{
|
||||||
{
|
var reRoute = new ReRouteBuilder()
|
||||||
_cacheManager
|
.WithDownstreamReRoute(new DownstreamReRouteBuilder()
|
||||||
.Verify(x => x.Get(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
|
.WithIsCached(true)
|
||||||
}
|
.WithCacheOptions(new CacheOptions(100, "kanken"))
|
||||||
|
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
||||||
private void ThenTheCacheAddIsCalledCorrectly()
|
.Build())
|
||||||
{
|
.WithUpstreamHttpMethod(new List<string> { "Get" })
|
||||||
_cacheManager
|
.Build();
|
||||||
.Verify(x => x.Add(It.IsAny<string>(), It.IsAny<CachedResponse>(), It.IsAny<TimeSpan>(), It.IsAny<string>()), Times.Once);
|
|
||||||
}
|
var downstreamRoute = new DownstreamRoute(new List<PlaceholderNameAndValue>(), reRoute);
|
||||||
}
|
|
||||||
}
|
_downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues;
|
||||||
|
_downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheCacheGetIsCalledCorrectly()
|
||||||
|
{
|
||||||
|
_cacheManager
|
||||||
|
.Verify(x => x.Get(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheCacheAddIsCalledCorrectly()
|
||||||
|
{
|
||||||
|
_cacheManager
|
||||||
|
.Verify(x => x.Add(It.IsAny<string>(), It.IsAny<CachedResponse>(), It.IsAny<TimeSpan>(), It.IsAny<string>()), Times.Once);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user