Added some basic cache stuff

This commit is contained in:
TomPallister
2016-11-04 15:05:59 +00:00
parent 5afcebe7cb
commit 47afc850ff
31 changed files with 800 additions and 148 deletions

View File

@ -0,0 +1,10 @@
using System;
namespace Ocelot.Cache
{
public interface IOcelotCache<T>
{
void Add(string key, T value, TimeSpan ttl);
T Get(string key);
}
}

View File

@ -0,0 +1,69 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Middleware;
namespace Ocelot.Cache.Middleware
{
public class OutputCacheMiddleware : OcelotMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
private readonly IOcelotCache<HttpResponseMessage> _outputCache;
public OutputCacheMiddleware(RequestDelegate next,
ILoggerFactory loggerFactory,
IRequestScopedDataRepository scopedDataRepository,
IOcelotCache<HttpResponseMessage> outputCache)
:base(scopedDataRepository)
{
_next = next;
_outputCache = outputCache;
_logger = loggerFactory.CreateLogger<OutputCacheMiddleware>();
}
public async Task Invoke(HttpContext context)
{
var downstreamUrlKey = DownstreamUrl;
if (!DownstreamRoute.ReRoute.IsCached)
{
await _next.Invoke(context);
return;
}
_logger.LogDebug("OutputCacheMiddleware.Invoke stared checking cache for downstreamUrlKey", downstreamUrlKey);
var cached = _outputCache.Get(downstreamUrlKey);
if (cached != null)
{
SetHttpResponseMessageThisRequest(cached);
_logger.LogDebug("OutputCacheMiddleware.Invoke finished returned cached response for downstreamUrlKey", downstreamUrlKey);
return;
}
_logger.LogDebug("OutputCacheMiddleware.Invoke no resonse cached for downstreamUrlKey", downstreamUrlKey);
await _next.Invoke(context);
if (PipelineError())
{
_logger.LogDebug("OutputCacheMiddleware.Invoke there was a pipeline error for downstreamUrlKey", downstreamUrlKey);
return;
}
var response = HttpResponseMessage;
_outputCache.Add(downstreamUrlKey, response, TimeSpan.FromSeconds(DownstreamRoute.ReRoute.FileCacheOptions.TtlSeconds));
_logger.LogDebug("OutputCacheMiddleware.Invoke finished response added to cache for downstreamUrlKey", downstreamUrlKey);
}
}
}

View File

@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Builder;
namespace Ocelot.Cache.Middleware
{
public static class OutputCacheMiddlewareExtensions
{
public static IApplicationBuilder UseOutputCacheMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<OutputCacheMiddleware>();
}
}
}

View File

@ -0,0 +1,25 @@
using System;
using CacheManager.Core;
namespace Ocelot.Cache
{
public class OcelotCacheManagerCache<T> : IOcelotCache<T>
{
private readonly ICacheManager<T> _cacheManager;
public OcelotCacheManagerCache(ICacheManager<T> cacheManager)
{
_cacheManager = cacheManager;
}
public void Add(string key, T value, TimeSpan ttl)
{
_cacheManager.Add(new CacheItem<T>(key, value, ExpirationMode.Absolute, ttl));
}
public T Get(string key)
{
return _cacheManager.Get<T>(key);
}
}
}

View File

@ -21,6 +21,8 @@ namespace Ocelot.Configuration.Builder
private bool _isAuthorised;
private List<ClaimToThing> _claimToQueries;
private string _requestIdHeaderKey;
private bool _isCached;
private CacheOptions _fileCacheOptions;
public ReRouteBuilder()
{
@ -127,9 +129,24 @@ namespace Ocelot.Configuration.Builder
return this;
}
public ReRouteBuilder WithIsCached(bool input)
{
_isCached = input;
return this;
}
public ReRouteBuilder WithCacheOptions(CacheOptions input)
{
_fileCacheOptions = input;
return this;
}
public ReRoute Build()
{
return new ReRoute(_downstreamTemplate, _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement, _isAuthorised, _claimToQueries, _requestIdHeaderKey);
return new ReRoute(_downstreamTemplate, _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern,
_isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName,
_requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement,
_isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions);
}
}
}

View File

@ -0,0 +1,12 @@
namespace Ocelot.Configuration
{
public class CacheOptions
{
public CacheOptions(int ttlSeconds)
{
TtlSeconds = ttlSeconds;
}
public int TtlSeconds { get; private set; }
}
}

View File

@ -100,6 +100,8 @@ namespace Ocelot.Configuration.Creator
var isAuthorised = reRoute.RouteClaimsRequirement?.Count > 0;
var isCached = reRoute.FileCacheOptions.TtlSeconds > 0;
if (isAuthenticated)
{
var authOptionsForRoute = new AuthenticationOptions(reRoute.AuthenticationOptions.Provider,
@ -115,7 +117,7 @@ namespace Ocelot.Configuration.Creator
reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated,
authOptionsForRoute, claimsToHeaders, claimsToClaims,
reRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries,
reRoute.RequestIdKey
reRoute.RequestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds)
);
}
@ -123,7 +125,7 @@ namespace Ocelot.Configuration.Creator
reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated,
null, new List<ClaimToThing>(), new List<ClaimToThing>(),
reRoute.RouteClaimsRequirement, isAuthorised, new List<ClaimToThing>(),
reRoute.RequestIdKey);
reRoute.RequestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds));
}
private List<ClaimToThing> GetAddThingsToRequest(Dictionary<string,string> thingBeingAdded)

View File

@ -0,0 +1,7 @@
namespace Ocelot.Configuration.File
{
public class FileCacheOptions
{
public int TtlSeconds { get; set; }
}
}

View File

@ -11,6 +11,7 @@ namespace Ocelot.Configuration.File
RouteClaimsRequirement = new Dictionary<string, string>();
AddQueriesToRequest = new Dictionary<string, string>();
AuthenticationOptions = new FileAuthenticationOptions();
FileCacheOptions = new FileCacheOptions();
}
public string DownstreamTemplate { get; set; }
@ -22,5 +23,6 @@ namespace Ocelot.Configuration.File
public Dictionary<string, string> RouteClaimsRequirement { get; set; }
public Dictionary<string, string> AddQueriesToRequest { get; set; }
public string RequestIdKey { get; set; }
public FileCacheOptions FileCacheOptions { get; set; }
}
}

View File

@ -4,7 +4,10 @@ namespace Ocelot.Configuration
{
public class ReRoute
{
public ReRoute(string downstreamTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern, bool isAuthenticated, AuthenticationOptions authenticationOptions, List<ClaimToThing> configurationHeaderExtractorProperties, List<ClaimToThing> claimsToClaims, Dictionary<string, string> routeClaimsRequirement, bool isAuthorised, List<ClaimToThing> claimsToQueries, string requestIdKey)
public ReRoute(string downstreamTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern,
bool isAuthenticated, AuthenticationOptions authenticationOptions, List<ClaimToThing> configurationHeaderExtractorProperties,
List<ClaimToThing> claimsToClaims, Dictionary<string, string> routeClaimsRequirement, bool isAuthorised, List<ClaimToThing> claimsToQueries,
string requestIdKey, bool isCached, CacheOptions fileCacheOptions)
{
DownstreamTemplate = downstreamTemplate;
UpstreamTemplate = upstreamTemplate;
@ -15,6 +18,8 @@ namespace Ocelot.Configuration
RouteClaimsRequirement = routeClaimsRequirement;
IsAuthorised = isAuthorised;
RequestIdKey = requestIdKey;
IsCached = isCached;
FileCacheOptions = fileCacheOptions;
ClaimsToQueries = claimsToQueries
?? new List<ClaimToThing>();
ClaimsToClaims = claimsToClaims
@ -35,5 +40,7 @@ namespace Ocelot.Configuration
public List<ClaimToThing> ClaimsToClaims { get; private set; }
public Dictionary<string, string> RouteClaimsRequirement { get; private set; }
public string RequestIdKey { get; private set; }
public bool IsCached { get; private set; }
public CacheOptions FileCacheOptions { get; private set; }
}
}

View File

@ -1,9 +1,14 @@
using Microsoft.AspNetCore.Http;
using System;
using System.Net.Http;
using CacheManager.Core;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Ocelot.Authentication.Handler.Creator;
using Ocelot.Authentication.Handler.Factory;
using Ocelot.Authorisation;
using Ocelot.Cache;
using Ocelot.Claims;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
@ -26,11 +31,21 @@ namespace Ocelot.DependencyInjection
{
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddOcelotOutputCaching(this IServiceCollection services, Action<ConfigurationBuilderCachePart> settings)
{
var cacheManagerOutputCache = CacheFactory.Build<HttpResponseMessage>("OcelotOutputCache", settings);
var ocelotCacheManager = new OcelotCacheManagerCache<HttpResponseMessage>(cacheManagerOutputCache);
services.AddSingleton<ICacheManager<HttpResponseMessage>>(cacheManagerOutputCache);
services.AddSingleton<IOcelotCache<HttpResponseMessage>>(ocelotCacheManager);
return services;
}
public static IServiceCollection AddOcelotFileConfiguration(this IServiceCollection services, IConfigurationRoot configurationRoot)
{
services.Configure<FileConfiguration>(configurationRoot);
// ocelot services.
services.AddSingleton<IOcelotConfigurationCreator, FileOcelotConfigurationCreator>();
services.AddSingleton<IOcelotConfigurationRepository, InMemoryOcelotConfigurationRepository>();
services.AddSingleton<IConfigurationValidator, FileConfigurationValidator>();
@ -40,11 +55,9 @@ namespace Ocelot.DependencyInjection
public static IServiceCollection AddOcelot(this IServiceCollection services)
{
// framework services dependency for the identity server middleware
services.AddMvcCore().AddJsonFormatters();
services.AddLogging();
// ocelot services.
services.AddSingleton<IRemoveOutputHeaders, RemoveOutputHeaders>();
services.AddSingleton<IOcelotConfigurationProvider, OcelotConfigurationProvider>();
services.AddSingleton<IClaimToThingConfigurationParser, ClaimToThingConfigurationParser>();

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Net.Http;
using Ocelot.DownstreamRouteFinder;
using Ocelot.Errors;
using Ocelot.Infrastructure.RequestData;
@ -59,6 +60,15 @@ namespace Ocelot.Middleware
}
}
public HttpResponseMessage HttpResponseMessage
{
get
{
var request = _requestScopedDataRepository.Get<HttpResponseMessage>("HttpResponseMessage");
return request.Data;
}
}
public void SetDownstreamRouteForThisRequest(DownstreamRoute downstreamRoute)
{
_requestScopedDataRepository.Add("DownstreamRoute", downstreamRoute);
@ -73,5 +83,11 @@ namespace Ocelot.Middleware
{
_requestScopedDataRepository.Add("Request", request);
}
public void SetHttpResponseMessageThisRequest(HttpResponseMessage responseMessage)
{
_requestScopedDataRepository.Add("HttpResponseMessage", responseMessage);
}
}
}

View File

@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Builder;
using Ocelot.Authentication.Middleware;
using Ocelot.Cache.Middleware;
using Ocelot.Claims.Middleware;
using Ocelot.DownstreamRouteFinder.Middleware;
using Ocelot.DownstreamUrlCreator.Middleware;
@ -99,6 +100,10 @@ namespace Ocelot.Middleware
// This takes the downstream route we retrieved earlier and replaces any placeholders with the variables that should be used
builder.UseDownstreamUrlCreatorMiddleware();
// Not sure if this is the best place for this but we use the downstream url
// as the basis for our cache key.
builder.UseOutputCacheMiddleware();
// Everything should now be ready to build or HttpRequest
builder.UseHttpRequestBuilderMiddleware();

View File

@ -1,10 +1,7 @@
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Middleware;
using Ocelot.Responder;
namespace Ocelot.Requester.Middleware
{
@ -12,17 +9,14 @@ namespace Ocelot.Requester.Middleware
{
private readonly RequestDelegate _next;
private readonly IHttpRequester _requester;
private readonly IHttpResponder _responder;
public HttpRequesterMiddleware(RequestDelegate next,
IHttpRequester requester,
IRequestScopedDataRepository requestScopedDataRepository,
IHttpResponder responder)
IRequestScopedDataRepository requestScopedDataRepository)
:base(requestScopedDataRepository)
{
_next = next;
_requester = requester;
_responder = responder;
}
public async Task Invoke(HttpContext context)
@ -35,12 +29,7 @@ namespace Ocelot.Requester.Middleware
return;
}
var setResponse = await _responder.SetResponseOnHttpContext(context, response.Data);
if (setResponse.IsError)
{
SetPipelineError(response.Errors);
}
SetHttpResponseMessageThisRequest(response.Data);
}
}
}

View File

@ -38,7 +38,7 @@ namespace Ocelot.Responder
AddHeaderIfDoesntExist(context, httpResponseHeader);
}
var content = await response.Content.ReadAsStreamAsync();
var content = await response.Content.ReadAsByteArrayAsync();
AddHeaderIfDoesntExist(context, new KeyValuePair<string, IEnumerable<string>>("Content-Length", new []{ content.Length.ToString() }) );
@ -52,12 +52,10 @@ namespace Ocelot.Responder
}, context);
using (var reader = new StreamReader(content))
using (Stream stream = new MemoryStream(content))
{
var responseContent = reader.ReadToEnd();
await context.Response.WriteAsync(responseContent);
await stream.CopyToAsync(context.Response.Body);
}
return new OkResponse();
}

View File

@ -1,6 +1,7 @@
using System.Net.Http;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Ocelot.Errors;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Middleware;
@ -30,18 +31,32 @@ namespace Ocelot.Responder.Middleware
if (PipelineError())
{
var errors = GetPipelineErrors();
var statusCode = _codeMapper.Map(errors);
if (!statusCode.IsError)
await SetErrorResponse(context, errors);
}
else
{
var setResponse = await _responder.SetResponseOnHttpContext(context, HttpResponseMessage);
if (setResponse.IsError)
{
await _responder.SetErrorResponseOnContext(context, statusCode.Data);
}
else
{
await _responder.SetErrorResponseOnContext(context, 500);
await SetErrorResponse(context, setResponse.Errors);
}
}
}
private async Task SetErrorResponse(HttpContext context, List<Error> errors)
{
var statusCode = _codeMapper.Map(errors);
if (!statusCode.IsError)
{
await _responder.SetErrorResponseOnContext(context, statusCode.Data);
}
else
{
await _responder.SetErrorResponseOnContext(context, 500);
}
}
}
}

View File

@ -27,7 +27,10 @@
"Microsoft.NETCore.App": {
"version": "1.0.1",
"type": "platform"
}
},
"CacheManager.Core": "0.9.1",
"CacheManager.Microsoft.Extensions.Configuration": "0.9.1",
"CacheManager.Microsoft.Extensions.Logging": "0.9.1"
},
"frameworks": {