mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-06-19 08:18:16 +08:00
Added some basic cache stuff
This commit is contained in:
10
src/Ocelot/Cache/IOcelotCache.cs
Normal file
10
src/Ocelot/Cache/IOcelotCache.cs
Normal 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);
|
||||
}
|
||||
}
|
69
src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs
Normal file
69
src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
25
src/Ocelot/Cache/OcelotCacheManagerCache.cs
Normal file
25
src/Ocelot/Cache/OcelotCacheManagerCache.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
12
src/Ocelot/Configuration/CacheOptions.cs
Normal file
12
src/Ocelot/Configuration/CacheOptions.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Ocelot.Configuration
|
||||
{
|
||||
public class CacheOptions
|
||||
{
|
||||
public CacheOptions(int ttlSeconds)
|
||||
{
|
||||
TtlSeconds = ttlSeconds;
|
||||
}
|
||||
|
||||
public int TtlSeconds { get; private set; }
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
7
src/Ocelot/Configuration/File/FileCacheOptions.cs
Normal file
7
src/Ocelot/Configuration/File/FileCacheOptions.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Ocelot.Configuration.File
|
||||
{
|
||||
public class FileCacheOptions
|
||||
{
|
||||
public int TtlSeconds { get; set; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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>();
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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": {
|
||||
|
Reference in New Issue
Block a user