mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-20 08:02:50 +08:00
* 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:
parent
3b27bb376e
commit
48b5a32676
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = crlf
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.cs]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
25
src/Ocelot/Cache/CachedResponse.cs
Normal file
25
src/Ocelot/Cache/CachedResponse.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
137
test/Ocelot.AcceptanceTests/Caching/InMemoryJsonHandle.cs
Normal file
137
test/Ocelot.AcceptanceTests/Caching/InMemoryJsonHandle.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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()
|
||||||
{
|
{
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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" />
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user