Implement mapping of HttpResponseMessage to CachedResponse to fix #152 (#153)

* changed name to cache options to fix issue #146

* Add acceptance test that exposes JSON deserialization bug from issue #146

- Create InMemoryJsonHandle for CacheManager that mimics DictionaryHandle but uses ICacheSerializer to serialize/deserialize values instead of saving references
- Add CacheManager.Serialization.Json package
- Add StartupWithCustomCacheHandle class that extends Startup and overrides ConfigureServices to register InMemoryJsonHandle
- Add GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache method to initiate Ocelot with StartupWithCustomCacheHandle
- Add test should_return_response_200_with_simple_url_when_using_jsonserialized_cache

* Create Acceptance test that exposes issue #152

- Add GivenOcelotIsRunningUsingJsonSerializedCache() that initializes Ocelot with InMemoryJsonHandle
- Add should_return_cached_response_when_using_jsonserialized_cache test

* Change Consul port to 9502 on should_return_response_200_with_simple_url_when_using_jsonserialized_cache() test

* Implement mapping of HttpResponseMessage to CachedResponse to enable distributed caching

- Add CachedResponse class that holds HttpResponse data
- Add mapping methods in OutputCacheMiddleware to create HttpResponseMessage from CachedResponse and vice versa
- Replace HttpResponseMessage with CachedResponse in services registrations
- Replace HttpResponseMessage with CachedResponse in OutputCacheController's IOcelotCache

* Fix unit tests for OutputCacheMiddleware and OutputCacheController by replacing HttpResponseMessage with CachedResponse

* Add .editorconfig with default identation settings (spaces with size 4)

* Re-format broken files with new identation settings

* Add Startup_WithConsul_And_CustomCacheHandle class

- Use Startup_WithConsul_And_CustomCacheHandle in GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache step

* Do minor cleanups

- Rename StartupWithCustomCacheHandle to Startup_WithCustomCacheHandle for better readability
- Remove cachemanager settings Action in Startup since it is not used anymore

* Drop Task in CreateHttpResponseMessage - unnecessary overhead

* Make setters private in CachedResponse

- Rework CreateCachedResponse to use new CachedResponse constructor
This commit is contained in:
Charalampos Chomenidis 2017-11-25 16:47:17 +02:00 committed by Tom Pallister
parent 3b27bb376e
commit 48b5a32676
15 changed files with 401 additions and 46 deletions

9
.editorconfig Normal file
View File

@ -0,0 +1,9 @@
root = true
[*]
end_of_line = crlf
insert_final_newline = true
[*.cs]
indent_style = space
indent_size = 4

View File

@ -0,0 +1,25 @@
using System.Collections.Generic;
using System.Net;
namespace Ocelot.Cache
{
public class CachedResponse
{
public CachedResponse(
HttpStatusCode statusCode = HttpStatusCode.OK,
Dictionary<string, IEnumerable<string>> headers = null,
string body = null
)
{
StatusCode = statusCode;
Headers = headers ?? new Dictionary<string, IEnumerable<string>>();
Body = body ?? "";
}
public HttpStatusCode StatusCode { get; private set; }
public Dictionary<string, IEnumerable<string>> Headers { get; private set; }
public string Body { get; private set; }
}
}

View File

@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Http;
using Ocelot.Infrastructure.RequestData; using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Middleware; using Ocelot.Middleware;
using System.IO;
namespace Ocelot.Cache.Middleware namespace Ocelot.Cache.Middleware
{ {
@ -13,15 +14,15 @@ namespace Ocelot.Cache.Middleware
{ {
private readonly RequestDelegate _next; private readonly RequestDelegate _next;
private readonly IOcelotLogger _logger; private readonly IOcelotLogger _logger;
private readonly IOcelotCache<HttpResponseMessage> _outputCache; private readonly IOcelotCache<CachedResponse> _outputCache;
private readonly IRegionCreator _regionCreator; private readonly IRegionCreator _regionCreator;
public OutputCacheMiddleware(RequestDelegate next, public OutputCacheMiddleware(RequestDelegate next,
IOcelotLoggerFactory loggerFactory, IOcelotLoggerFactory loggerFactory,
IRequestScopedDataRepository scopedDataRepository, IRequestScopedDataRepository scopedDataRepository,
IOcelotCache<HttpResponseMessage> outputCache, IOcelotCache<CachedResponse> outputCache,
IRegionCreator regionCreator) IRegionCreator regionCreator)
:base(scopedDataRepository) : base(scopedDataRepository)
{ {
_next = next; _next = next;
_outputCache = outputCache; _outputCache = outputCache;
@ -40,14 +41,15 @@ namespace Ocelot.Cache.Middleware
var downstreamUrlKey = $"{DownstreamRequest.Method.Method}-{DownstreamRequest.RequestUri.OriginalString}"; var downstreamUrlKey = $"{DownstreamRequest.Method.Method}-{DownstreamRequest.RequestUri.OriginalString}";
_logger.LogDebug("started checking cache for {downstreamUrlKey}", downstreamUrlKey); _logger.LogDebug("started checking cache for {downstreamUrlKey}", downstreamUrlKey);
var cached = _outputCache.Get(downstreamUrlKey, DownstreamRoute.ReRoute.CacheOptions.Region); var cached = _outputCache.Get(downstreamUrlKey, DownstreamRoute.ReRoute.CacheOptions.Region);
if (cached != null) if (cached != null)
{ {
_logger.LogDebug("cache entry exists for {downstreamUrlKey}", downstreamUrlKey); _logger.LogDebug("cache entry exists for {downstreamUrlKey}", downstreamUrlKey);
SetHttpResponseMessageThisRequest(cached); var response = CreateHttpResponseMessage(cached);
SetHttpResponseMessageThisRequest(response);
_logger.LogDebug("finished returned cached response for {downstreamUrlKey}", downstreamUrlKey); _logger.LogDebug("finished returned cached response for {downstreamUrlKey}", downstreamUrlKey);
@ -65,11 +67,50 @@ namespace Ocelot.Cache.Middleware
return; return;
} }
var response = HttpResponseMessage; cached = await CreateCachedResponse(HttpResponseMessage);
_outputCache.Add(downstreamUrlKey, response, TimeSpan.FromSeconds(DownstreamRoute.ReRoute.CacheOptions.TtlSeconds), DownstreamRoute.ReRoute.CacheOptions.Region); _outputCache.Add(downstreamUrlKey, cached, TimeSpan.FromSeconds(DownstreamRoute.ReRoute.CacheOptions.TtlSeconds), DownstreamRoute.ReRoute.CacheOptions.Region);
_logger.LogDebug("finished response added to cache for {downstreamUrlKey}", downstreamUrlKey); _logger.LogDebug("finished response added to cache for {downstreamUrlKey}", downstreamUrlKey);
} }
internal HttpResponseMessage CreateHttpResponseMessage(CachedResponse cached)
{
if (cached == null)
{
return null;
}
var response = new HttpResponseMessage(cached.StatusCode);
foreach (var header in cached.Headers)
{
response.Headers.Add(header.Key, header.Value);
}
var content = new MemoryStream(Convert.FromBase64String(cached.Body));
response.Content = new StreamContent(content);
return response;
}
internal async Task<CachedResponse> CreateCachedResponse(HttpResponseMessage response)
{
if (response == null)
{
return null;
}
var statusCode = response.StatusCode;
var headers = response.Headers.ToDictionary(v => v.Key, v => v.Value);
string body = null;
if (response.Content != null)
{
var content = await response.Content.ReadAsByteArrayAsync();
body = Convert.ToBase64String(content);
}
var cached = new CachedResponse(statusCode, headers, body);
return cached;
}
} }
} }

View File

@ -19,7 +19,7 @@ namespace Ocelot.Configuration
List<ClaimToThing> claimsToQueries, List<ClaimToThing> claimsToQueries,
string requestIdKey, string requestIdKey,
bool isCached, bool isCached,
CacheOptions fileCacheOptions, CacheOptions cacheOptions,
string downstreamScheme, string downstreamScheme,
string loadBalancer, string loadBalancer,
string downstreamHost, string downstreamHost,
@ -49,7 +49,7 @@ namespace Ocelot.Configuration
IsAuthorised = isAuthorised; IsAuthorised = isAuthorised;
RequestIdKey = requestIdKey; RequestIdKey = requestIdKey;
IsCached = isCached; IsCached = isCached;
CacheOptions = fileCacheOptions; CacheOptions = cacheOptions;
ClaimsToQueries = claimsToQueries ClaimsToQueries = claimsToQueries
?? new List<ClaimToThing>(); ?? new List<ClaimToThing>();
ClaimsToClaims = claimsToClaims ClaimsToClaims = claimsToClaims

View File

@ -11,9 +11,9 @@ namespace Ocelot.Controllers
[Route("outputcache")] [Route("outputcache")]
public class OutputCacheController : Controller public class OutputCacheController : Controller
{ {
private IOcelotCache<HttpResponseMessage> _cache; private IOcelotCache<CachedResponse> _cache;
public OutputCacheController(IOcelotCache<HttpResponseMessage> cache) public OutputCacheController(IOcelotCache<CachedResponse> cache)
{ {
_cache = cache; _cache = cache;
} }

View File

@ -161,13 +161,13 @@ namespace Ocelot.DependencyInjection
public IOcelotBuilder AddCacheManager(Action<ConfigurationBuilderCachePart> settings) public IOcelotBuilder AddCacheManager(Action<ConfigurationBuilderCachePart> settings)
{ {
var cacheManagerOutputCache = CacheFactory.Build<HttpResponseMessage>("OcelotOutputCache", settings); var cacheManagerOutputCache = CacheFactory.Build<CachedResponse>("OcelotOutputCache", settings);
var ocelotOutputCacheManager = new OcelotCacheManagerCache<HttpResponseMessage>(cacheManagerOutputCache); var ocelotOutputCacheManager = new OcelotCacheManagerCache<CachedResponse>(cacheManagerOutputCache);
_services.RemoveAll(typeof(ICacheManager<HttpResponseMessage>)); _services.RemoveAll(typeof(ICacheManager<CachedResponse>));
_services.RemoveAll(typeof(IOcelotCache<HttpResponseMessage>)); _services.RemoveAll(typeof(IOcelotCache<CachedResponse>));
_services.AddSingleton<ICacheManager<HttpResponseMessage>>(cacheManagerOutputCache); _services.AddSingleton<ICacheManager<CachedResponse>>(cacheManagerOutputCache);
_services.AddSingleton<IOcelotCache<HttpResponseMessage>>(ocelotOutputCacheManager); _services.AddSingleton<IOcelotCache<CachedResponse>>(ocelotOutputCacheManager);
var ocelotConfigCacheManagerOutputCache = CacheFactory.Build<IOcelotConfiguration>("OcelotConfigurationCache", settings); var ocelotConfigCacheManagerOutputCache = CacheFactory.Build<IOcelotConfiguration>("OcelotConfigurationCache", settings);
var ocelotConfigCacheManager = new OcelotCacheManagerCache<IOcelotConfiguration>(ocelotConfigCacheManagerOutputCache); var ocelotConfigCacheManager = new OcelotCacheManagerCache<IOcelotConfiguration>(ocelotConfigCacheManagerOutputCache);

View File

@ -0,0 +1,137 @@
using CacheManager.Core;
using CacheManager.Core.Internal;
using CacheManager.Core.Logging;
using System;
using System.Collections.Concurrent;
using System.Linq;
using static CacheManager.Core.Utility.Guard;
namespace Ocelot.AcceptanceTests.Caching
{
public class InMemoryJsonHandle<TCacheValue> : BaseCacheHandle<TCacheValue>
{
private readonly ICacheSerializer _serializer;
private readonly ConcurrentDictionary<string, Tuple<Type, byte[]>> _cache;
public InMemoryJsonHandle(
ICacheManagerConfiguration managerConfiguration,
CacheHandleConfiguration configuration,
ICacheSerializer serializer,
ILoggerFactory loggerFactory) : base(managerConfiguration, configuration)
{
_cache = new ConcurrentDictionary<string, Tuple<Type, byte[]>>();
_serializer = serializer;
Logger = loggerFactory.CreateLogger(this);
}
public override int Count => _cache.Count;
protected override ILogger Logger { get; }
public override void Clear() => _cache.Clear();
public override void ClearRegion(string region)
{
NotNullOrWhiteSpace(region, nameof(region));
var key = string.Concat(region, ":");
foreach (var item in _cache.Where(p => p.Key.StartsWith(key, StringComparison.OrdinalIgnoreCase)))
{
_cache.TryRemove(item.Key, out Tuple<Type, byte[]> val);
}
}
public override bool Exists(string key)
{
NotNullOrWhiteSpace(key, nameof(key));
return _cache.ContainsKey(key);
}
public override bool Exists(string key, string region)
{
NotNullOrWhiteSpace(region, nameof(region));
var fullKey = GetKey(key, region);
return _cache.ContainsKey(fullKey);
}
protected override bool AddInternalPrepared(CacheItem<TCacheValue> item)
{
NotNull(item, nameof(item));
var key = GetKey(item.Key, item.Region);
var serializedItem = _serializer.SerializeCacheItem(item);
return _cache.TryAdd(key, new Tuple<Type, byte[]>(item.Value.GetType(), serializedItem));
}
protected override CacheItem<TCacheValue> GetCacheItemInternal(string key) => GetCacheItemInternal(key, null);
protected override CacheItem<TCacheValue> GetCacheItemInternal(string key, string region)
{
var fullKey = GetKey(key, region);
CacheItem<TCacheValue> deserializedResult = null;
if (_cache.TryGetValue(fullKey, out Tuple<Type, byte[]> result))
{
deserializedResult = _serializer.DeserializeCacheItem<TCacheValue>(result.Item2, result.Item1);
if (deserializedResult.ExpirationMode != ExpirationMode.None && IsExpired(deserializedResult, DateTime.UtcNow))
{
_cache.TryRemove(fullKey, out Tuple<Type, byte[]> removeResult);
TriggerCacheSpecificRemove(key, region, CacheItemRemovedReason.Expired, deserializedResult.Value);
return null;
}
}
return deserializedResult;
}
protected override void PutInternalPrepared(CacheItem<TCacheValue> item)
{
NotNull(item, nameof(item));
var serializedItem = _serializer.SerializeCacheItem<TCacheValue>(item);
_cache[GetKey(item.Key, item.Region)] = new Tuple<Type, byte[]>(item.Value.GetType(), serializedItem);
}
protected override bool RemoveInternal(string key) => RemoveInternal(key, null);
protected override bool RemoveInternal(string key, string region)
{
var fullKey = GetKey(key, region);
return _cache.TryRemove(fullKey, out Tuple<Type, byte[]> val);
}
private static string GetKey(string key, string region)
{
NotNullOrWhiteSpace(key, nameof(key));
if (string.IsNullOrWhiteSpace(region))
{
return key;
}
return string.Concat(region, ":", key);
}
private static bool IsExpired(CacheItem<TCacheValue> item, DateTime now)
{
if (item.ExpirationMode == ExpirationMode.Absolute
&& item.CreatedUtc.Add(item.ExpirationTimeout) < now)
{
return true;
}
else if (item.ExpirationMode == ExpirationMode.Sliding
&& item.LastAccessedUtc.Add(item.ExpirationTimeout) < now)
{
return true;
}
return false;
}
}
}

View File

@ -58,6 +58,42 @@ namespace Ocelot.AcceptanceTests
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_return_cached_response_when_using_jsonserialized_cache()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamPort = 51879,
DownstreamScheme = "http",
DownstreamHost = "localhost",
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
FileCacheOptions = new FileCacheOptions
{
TtlSeconds = 100
}
}
}
};
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 200, "Hello from Laura"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningUsingJsonSerializedCache())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.Given(x => x.GivenTheServiceNowReturns("http://localhost:51879", 200, "Hello from Tom"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.BDDfy();
}
[Fact] [Fact]
public void should_not_return_cached_response_as_ttl_expires() public void should_not_return_cached_response_as_ttl_expires()
{ {

View File

@ -68,6 +68,45 @@ namespace Ocelot.AcceptanceTests
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_return_response_200_with_simple_url_when_using_jsonserialized_cache()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHost = "localhost",
DownstreamPort = 51779,
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
}
},
GlobalConfiguration = new FileGlobalConfiguration()
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
{
Host = "localhost",
Port = 9502
}
}
};
var fakeConsulServiceDiscoveryUrl = "http://localhost:9502";
this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl))
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.BDDfy();
}
[Fact] [Fact]
public void should_load_configuration_out_of_consul() public void should_load_configuration_out_of_consul()
{ {
@ -236,7 +275,7 @@ namespace Ocelot.AcceptanceTests
var kvp = new FakeConsulGetResponse(base64); var kvp = new FakeConsulGetResponse(base64);
await context.Response.WriteJsonAsync(new FakeConsulGetResponse[]{kvp}); await context.Response.WriteJsonAsync(new FakeConsulGetResponse[] { kvp });
} }
else if (context.Request.Method.ToLower() == "put" && context.Request.Path.Value == "/v1/kv/OcelotConfiguration") else if (context.Request.Method.ToLower() == "put" && context.Request.Path.Value == "/v1/kv/OcelotConfiguration")
@ -311,4 +350,4 @@ namespace Ocelot.AcceptanceTests
_steps.Dispose(); _steps.Dispose();
} }
} }
} }

View File

@ -30,6 +30,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CacheManager.Serialization.Json" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" /> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.0" /> <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0-preview-20171031-01" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0-preview-20171031-01" />

View File

@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging;
using Ocelot.DependencyInjection; using Ocelot.DependencyInjection;
using Ocelot.Middleware; using Ocelot.Middleware;
using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder;
using Ocelot.AcceptanceTests.Caching;
namespace Ocelot.AcceptanceTests namespace Ocelot.AcceptanceTests
{ {
@ -27,13 +28,8 @@ namespace Ocelot.AcceptanceTests
public IConfigurationRoot Configuration { get; } public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services) public virtual void ConfigureServices(IServiceCollection services)
{ {
Action<ConfigurationBuilderCachePart> settings = (x) =>
{
x.WithDictionaryHandle();
};
services.AddOcelot(Configuration); services.AddOcelot(Configuration);
} }
@ -44,4 +40,43 @@ namespace Ocelot.AcceptanceTests
app.UseOcelot().Wait(); app.UseOcelot().Wait();
} }
} }
public class Startup_WithCustomCacheHandle : Startup
{
public Startup_WithCustomCacheHandle(IHostingEnvironment env) : base(env) { }
public override void ConfigureServices(IServiceCollection services)
{
services.AddOcelot(Configuration)
.AddCacheManager((x) =>
{
x.WithMicrosoftLogging(log =>
{
log.AddConsole(LogLevel.Debug);
})
.WithJsonSerializer()
.WithHandle(typeof(InMemoryJsonHandle<>));
});
}
}
public class Startup_WithConsul_And_CustomCacheHandle : Startup
{
public Startup_WithConsul_And_CustomCacheHandle(IHostingEnvironment env) : base(env) { }
public override void ConfigureServices(IServiceCollection services)
{
services.AddOcelot(Configuration)
.AddCacheManager((x) =>
{
x.WithMicrosoftLogging(log =>
{
log.AddConsole(LogLevel.Debug);
})
.WithJsonSerializer()
.WithHandle(typeof(InMemoryJsonHandle<>));
})
.AddStoreOcelotConfigurationInConsul();
}
}
} }

View File

@ -23,6 +23,7 @@ using Ocelot.Middleware;
using Ocelot.ServiceDiscovery; using Ocelot.ServiceDiscovery;
using Shouldly; using Shouldly;
using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder;
using Ocelot.AcceptanceTests.Caching;
namespace Ocelot.AcceptanceTests namespace Ocelot.AcceptanceTests
{ {
@ -74,9 +75,9 @@ namespace Ocelot.AcceptanceTests
/// </summary> /// </summary>
public void GivenOcelotIsRunning() public void GivenOcelotIsRunning()
{ {
_webHostBuilder = new WebHostBuilder(); _webHostBuilder = new WebHostBuilder();
_webHostBuilder.ConfigureServices(s => _webHostBuilder.ConfigureServices(s =>
{ {
s.AddSingleton(_webHostBuilder); s.AddSingleton(_webHostBuilder);
}); });
@ -107,6 +108,21 @@ namespace Ocelot.AcceptanceTests
_ocelotClient = _ocelotServer.CreateClient(); _ocelotClient = _ocelotServer.CreateClient();
} }
public void GivenOcelotIsRunningUsingJsonSerializedCache()
{
_webHostBuilder = new WebHostBuilder();
_webHostBuilder.ConfigureServices(s =>
{
s.AddSingleton(_webHostBuilder);
});
_ocelotServer = new TestServer(_webHostBuilder
.UseStartup<Startup_WithCustomCacheHandle>());
_ocelotClient = _ocelotServer.CreateClient();
}
public void GivenOcelotIsRunningUsingConsulToStoreConfig() public void GivenOcelotIsRunningUsingConsulToStoreConfig()
{ {
_webHostBuilder = new WebHostBuilder(); _webHostBuilder = new WebHostBuilder();
@ -122,16 +138,31 @@ namespace Ocelot.AcceptanceTests
_ocelotClient = _ocelotServer.CreateClient(); _ocelotClient = _ocelotServer.CreateClient();
} }
public void GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache()
{
_webHostBuilder = new WebHostBuilder();
_webHostBuilder.ConfigureServices(s =>
{
s.AddSingleton(_webHostBuilder);
});
_ocelotServer = new TestServer(_webHostBuilder
.UseStartup<Startup_WithConsul_And_CustomCacheHandle>());
_ocelotClient = _ocelotServer.CreateClient();
}
internal void ThenTheResponseShouldBe(FileConfiguration expected) internal void ThenTheResponseShouldBe(FileConfiguration expected)
{ {
var response = JsonConvert.DeserializeObject<FileConfiguration>(_response.Content.ReadAsStringAsync().Result); var response = JsonConvert.DeserializeObject<FileConfiguration>(_response.Content.ReadAsStringAsync().Result);
response.GlobalConfiguration.AdministrationPath.ShouldBe(expected.GlobalConfiguration.AdministrationPath); response.GlobalConfiguration.AdministrationPath.ShouldBe(expected.GlobalConfiguration.AdministrationPath);
response.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey); response.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey);
response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host); response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host);
response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port); response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port);
for(var i = 0; i < response.ReRoutes.Count; i++) for (var i = 0; i < response.ReRoutes.Count; i++)
{ {
response.ReRoutes[i].DownstreamHost.ShouldBe(expected.ReRoutes[i].DownstreamHost); response.ReRoutes[i].DownstreamHost.ShouldBe(expected.ReRoutes[i].DownstreamHost);
response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate); response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate);
@ -155,7 +186,7 @@ namespace Ocelot.AcceptanceTests
var configuration = builder.Build(); var configuration = builder.Build();
_webHostBuilder = new WebHostBuilder(); _webHostBuilder = new WebHostBuilder();
_webHostBuilder.ConfigureServices(s => _webHostBuilder.ConfigureServices(s =>
{ {
s.AddSingleton(_webHostBuilder); s.AddSingleton(_webHostBuilder);
}); });
@ -172,7 +203,7 @@ namespace Ocelot.AcceptanceTests
}) })
.WithDictionaryHandle(); .WithDictionaryHandle();
}; };
s.AddOcelot(configuration); s.AddOcelot(configuration);
}) })
.ConfigureLogging(l => .ConfigureLogging(l =>
@ -299,12 +330,12 @@ namespace Ocelot.AcceptanceTests
public void WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times) public void WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times)
{ {
var tasks = new Task[times]; var tasks = new Task[times];
for (int i = 0; i < times; i++) for (int i = 0; i < times; i++)
{ {
var urlCopy = url; var urlCopy = url;
tasks[i] = GetForServiceDiscoveryTest(urlCopy); tasks[i] = GetForServiceDiscoveryTest(urlCopy);
Thread.Sleep(_random.Next(40,60)); Thread.Sleep(_random.Next(40, 60));
} }
Task.WaitAll(tasks); Task.WaitAll(tasks);
@ -327,7 +358,7 @@ namespace Ocelot.AcceptanceTests
request.Headers.Add("ClientId", clientId); request.Headers.Add("ClientId", clientId);
_response = _ocelotClient.SendAsync(request).Result; _response = _ocelotClient.SendAsync(request).Result;
} }
} }
public void WhenIGetUrlOnTheApiGateway(string url, string requestId) public void WhenIGetUrlOnTheApiGateway(string url, string requestId)
{ {

View File

@ -19,12 +19,12 @@
public class OutputCacheMiddlewareTests : ServerHostedMiddlewareTest public class OutputCacheMiddlewareTests : ServerHostedMiddlewareTest
{ {
private readonly Mock<IOcelotCache<HttpResponseMessage>> _cacheManager; private readonly Mock<IOcelotCache<CachedResponse>> _cacheManager;
private HttpResponseMessage _response; private CachedResponse _response;
public OutputCacheMiddlewareTests() public OutputCacheMiddlewareTests()
{ {
_cacheManager = new Mock<IOcelotCache<HttpResponseMessage>>(); _cacheManager = new Mock<IOcelotCache<CachedResponse>>();
ScopedRepository ScopedRepository
.Setup(sr => sr.Get<HttpRequestMessage>("DownstreamRequest")) .Setup(sr => sr.Get<HttpRequestMessage>("DownstreamRequest"))
@ -36,7 +36,8 @@
[Fact] [Fact]
public void should_returned_cached_item_when_it_is_in_cache() public void should_returned_cached_item_when_it_is_in_cache()
{ {
this.Given(x => x.GivenThereIsACachedResponse(new HttpResponseMessage())) var cachedResponse = new CachedResponse();
this.Given(x => x.GivenThereIsACachedResponse(cachedResponse))
.And(x => x.GivenTheDownstreamRouteIs()) .And(x => x.GivenTheDownstreamRouteIs())
.And(x => x.GivenThereIsADownstreamUrl()) .And(x => x.GivenThereIsADownstreamUrl())
.When(x => x.WhenICallTheMiddleware()) .When(x => x.WhenICallTheMiddleware())
@ -70,7 +71,7 @@
app.UseOutputCacheMiddleware(); app.UseOutputCacheMiddleware();
} }
private void GivenThereIsACachedResponse(HttpResponseMessage response) private void GivenThereIsACachedResponse(CachedResponse response)
{ {
_response = response; _response = response;
_cacheManager _cacheManager
@ -123,7 +124,7 @@
private void ThenTheCacheAddIsCalledCorrectly() private void ThenTheCacheAddIsCalledCorrectly()
{ {
_cacheManager _cacheManager
.Verify(x => x.Add(It.IsAny<string>(), It.IsAny<HttpResponseMessage>(), It.IsAny<TimeSpan>(), It.IsAny<string>()), Times.Once); .Verify(x => x.Add(It.IsAny<string>(), It.IsAny<CachedResponse>(), It.IsAny<TimeSpan>(), It.IsAny<string>()), Times.Once);
} }
} }
} }

View File

@ -14,12 +14,12 @@ namespace Ocelot.UnitTests.Controllers
public class OutputCacheControllerTests public class OutputCacheControllerTests
{ {
private OutputCacheController _controller; private OutputCacheController _controller;
private Mock<IOcelotCache<HttpResponseMessage>> _cache; private Mock<IOcelotCache<CachedResponse>> _cache;
private IActionResult _result; private IActionResult _result;
public OutputCacheControllerTests() public OutputCacheControllerTests()
{ {
_cache = new Mock<IOcelotCache<HttpResponseMessage>>(); _cache = new Mock<IOcelotCache<CachedResponse>>();
_controller = new OutputCacheController(_cache.Object); _controller = new OutputCacheController(_cache.Object);
} }

View File

@ -72,9 +72,9 @@ namespace Ocelot.UnitTests.DependencyInjection
private void OnlyOneVersionOfEachCacheIsRegistered() private void OnlyOneVersionOfEachCacheIsRegistered()
{ {
var outputCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache<HttpResponseMessage>)); var outputCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache<CachedResponse>));
var outputCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager<HttpResponseMessage>)); var outputCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager<CachedResponse>));
var thing = (CacheManager.Core.ICacheManager<System.Net.Http.HttpResponseMessage>)outputCacheManager.ImplementationInstance; var thing = (CacheManager.Core.ICacheManager<CachedResponse>)outputCacheManager.ImplementationInstance;
thing.Configuration.MaxRetries.ShouldBe(_maxRetries); thing.Configuration.MaxRetries.ShouldBe(_maxRetries);
var ocelotConfigCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache<IOcelotConfiguration>)); var ocelotConfigCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache<IOcelotConfiguration>));