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

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.Logging;
using Ocelot.Middleware;
using System.IO;
namespace Ocelot.Cache.Middleware
{
@ -13,15 +14,15 @@ namespace Ocelot.Cache.Middleware
{
private readonly RequestDelegate _next;
private readonly IOcelotLogger _logger;
private readonly IOcelotCache<HttpResponseMessage> _outputCache;
private readonly IOcelotCache<CachedResponse> _outputCache;
private readonly IRegionCreator _regionCreator;
public OutputCacheMiddleware(RequestDelegate next,
IOcelotLoggerFactory loggerFactory,
IRequestScopedDataRepository scopedDataRepository,
IOcelotCache<HttpResponseMessage> outputCache,
IOcelotCache<CachedResponse> outputCache,
IRegionCreator regionCreator)
:base(scopedDataRepository)
: base(scopedDataRepository)
{
_next = next;
_outputCache = outputCache;
@ -40,14 +41,15 @@ namespace Ocelot.Cache.Middleware
var downstreamUrlKey = $"{DownstreamRequest.Method.Method}-{DownstreamRequest.RequestUri.OriginalString}";
_logger.LogDebug("started checking cache for {downstreamUrlKey}", downstreamUrlKey);
var cached = _outputCache.Get(downstreamUrlKey, DownstreamRoute.ReRoute.CacheOptions.Region);
if (cached != null)
{
_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);
@ -65,11 +67,50 @@ namespace Ocelot.Cache.Middleware
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);
}
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,
string requestIdKey,
bool isCached,
CacheOptions fileCacheOptions,
CacheOptions cacheOptions,
string downstreamScheme,
string loadBalancer,
string downstreamHost,
@ -49,7 +49,7 @@ namespace Ocelot.Configuration
IsAuthorised = isAuthorised;
RequestIdKey = requestIdKey;
IsCached = isCached;
CacheOptions = fileCacheOptions;
CacheOptions = cacheOptions;
ClaimsToQueries = claimsToQueries
?? new List<ClaimToThing>();
ClaimsToClaims = claimsToClaims

View File

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

View File

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