mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-06-19 22:08:17 +08:00
Activate ChangeToken when Ocelot's configuration changes #1037
* Add configuration change token (#1036) * Add IOptionsMonitor<IInternalConfiguration> * Activate change token from *ConfigurationRepository instead of FileAndInternalConfigurationSetter; add acceptance & integration tests * Update documentation * Use IWebHostEnvironment as IHostingEnvironment deprecated Co-authored-by: Chris Swinchatt <chrisswinchatt@gmail.com>
This commit is contained in:
@ -0,0 +1,14 @@
|
||||
namespace Ocelot.Configuration.ChangeTracking
|
||||
{
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IChangeToken" /> source which is activated when Ocelot's configuration is changed.
|
||||
/// </summary>
|
||||
public interface IOcelotConfigurationChangeTokenSource
|
||||
{
|
||||
IChangeToken ChangeToken { get; }
|
||||
|
||||
void Activate();
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
namespace Ocelot.Configuration.ChangeTracking
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
public class OcelotConfigurationChangeToken : IChangeToken
|
||||
{
|
||||
public const double PollingIntervalSeconds = 1;
|
||||
|
||||
private readonly ICollection<CallbackWrapper> _callbacks = new List<CallbackWrapper>();
|
||||
private readonly object _lock = new object();
|
||||
private DateTime? _timeChanged;
|
||||
|
||||
public IDisposable RegisterChangeCallback(Action<object> callback, object state)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var wrapper = new CallbackWrapper(callback, state, _callbacks, _lock);
|
||||
_callbacks.Add(wrapper);
|
||||
return wrapper;
|
||||
}
|
||||
}
|
||||
|
||||
public void Activate()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_timeChanged = DateTime.UtcNow;
|
||||
foreach (var wrapper in _callbacks)
|
||||
{
|
||||
wrapper.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Token stays active for PollingIntervalSeconds after a change (could be parameterised) - otherwise HasChanged would be true forever.
|
||||
// Taking suggestions for better ways to reset HasChanged back to false.
|
||||
public bool HasChanged => _timeChanged.HasValue && (DateTime.UtcNow - _timeChanged.Value).TotalSeconds < PollingIntervalSeconds;
|
||||
|
||||
public bool ActiveChangeCallbacks => true;
|
||||
|
||||
private class CallbackWrapper : IDisposable
|
||||
{
|
||||
private readonly ICollection<CallbackWrapper> _callbacks;
|
||||
private readonly object _lock;
|
||||
|
||||
public CallbackWrapper(Action<object> callback, object state, ICollection<CallbackWrapper> callbacks, object @lock)
|
||||
{
|
||||
_callbacks = callbacks;
|
||||
_lock = @lock;
|
||||
Callback = callback;
|
||||
State = state;
|
||||
}
|
||||
|
||||
public void Invoke()
|
||||
{
|
||||
Callback.Invoke(State);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_callbacks.Remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
public Action<object> Callback { get; }
|
||||
|
||||
public object State { get; }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
namespace Ocelot.Configuration.ChangeTracking
|
||||
{
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
public class OcelotConfigurationChangeTokenSource : IOcelotConfigurationChangeTokenSource
|
||||
{
|
||||
private readonly OcelotConfigurationChangeToken _changeToken = new OcelotConfigurationChangeToken();
|
||||
|
||||
public IChangeToken ChangeToken => _changeToken;
|
||||
|
||||
public void Activate()
|
||||
{
|
||||
_changeToken.Activate();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
namespace Ocelot.Configuration.ChangeTracking
|
||||
{
|
||||
using System;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Ocelot.Configuration.Repository;
|
||||
|
||||
public class OcelotConfigurationMonitor : IOptionsMonitor<IInternalConfiguration>
|
||||
{
|
||||
private readonly IOcelotConfigurationChangeTokenSource _changeTokenSource;
|
||||
private readonly IInternalConfigurationRepository _repo;
|
||||
|
||||
public OcelotConfigurationMonitor(IInternalConfigurationRepository repo, IOcelotConfigurationChangeTokenSource changeTokenSource)
|
||||
{
|
||||
_changeTokenSource = changeTokenSource;
|
||||
_repo = repo;
|
||||
}
|
||||
|
||||
public IInternalConfiguration Get(string name)
|
||||
{
|
||||
return _repo.Get().Data;
|
||||
}
|
||||
|
||||
public IDisposable OnChange(Action<IInternalConfiguration, string> listener)
|
||||
{
|
||||
return _changeTokenSource.ChangeToken.RegisterChangeCallback(_ => listener(CurrentValue, ""), null);
|
||||
}
|
||||
|
||||
public IInternalConfiguration CurrentValue => _repo.Get().Data;
|
||||
}
|
||||
}
|
@ -4,18 +4,21 @@ using Ocelot.Configuration.File;
|
||||
using Ocelot.Responses;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Configuration.ChangeTracking;
|
||||
|
||||
namespace Ocelot.Configuration.Repository
|
||||
{
|
||||
public class DiskFileConfigurationRepository : IFileConfigurationRepository
|
||||
{
|
||||
private readonly IOcelotConfigurationChangeTokenSource _changeTokenSource;
|
||||
private readonly string _environmentFilePath;
|
||||
private readonly string _ocelotFilePath;
|
||||
private static readonly object _lock = new object();
|
||||
private const string ConfigurationFileName = "ocelot";
|
||||
|
||||
public DiskFileConfigurationRepository(IWebHostEnvironment hostingEnvironment)
|
||||
public DiskFileConfigurationRepository(IWebHostEnvironment hostingEnvironment, IOcelotConfigurationChangeTokenSource changeTokenSource)
|
||||
{
|
||||
_changeTokenSource = changeTokenSource;
|
||||
_environmentFilePath = $"{AppContext.BaseDirectory}{ConfigurationFileName}{(string.IsNullOrEmpty(hostingEnvironment.EnvironmentName) ? string.Empty : ".")}{hostingEnvironment.EnvironmentName}.json";
|
||||
|
||||
_ocelotFilePath = $"{AppContext.BaseDirectory}{ConfigurationFileName}.json";
|
||||
@ -56,6 +59,7 @@ namespace Ocelot.Configuration.Repository
|
||||
System.IO.File.WriteAllText(_ocelotFilePath, jsonConfiguration);
|
||||
}
|
||||
|
||||
_changeTokenSource.Activate();
|
||||
return Task.FromResult<Response>(new OkResponse());
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.Configuration.ChangeTracking;
|
||||
using Ocelot.Responses;
|
||||
|
||||
namespace Ocelot.Configuration.Repository
|
||||
{
|
||||
@ -10,6 +11,12 @@ namespace Ocelot.Configuration.Repository
|
||||
private static readonly object LockObject = new object();
|
||||
|
||||
private IInternalConfiguration _internalConfiguration;
|
||||
private readonly IOcelotConfigurationChangeTokenSource _changeTokenSource;
|
||||
|
||||
public InMemoryInternalConfigurationRepository(IOcelotConfigurationChangeTokenSource changeTokenSource)
|
||||
{
|
||||
_changeTokenSource = changeTokenSource;
|
||||
}
|
||||
|
||||
public Response<IInternalConfiguration> Get()
|
||||
{
|
||||
@ -23,6 +30,7 @@ namespace Ocelot.Configuration.Repository
|
||||
_internalConfiguration = internalConfiguration;
|
||||
}
|
||||
|
||||
_changeTokenSource.Activate();
|
||||
return new OkResponse();
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
using Ocelot.Configuration.ChangeTracking;
|
||||
|
||||
namespace Ocelot.DependencyInjection
|
||||
{
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Ocelot.Authorisation;
|
||||
using Ocelot.Cache;
|
||||
using Ocelot.Claims;
|
||||
@ -112,6 +115,8 @@ namespace Ocelot.DependencyInjection
|
||||
Services.TryAddSingleton<IDownstreamAddressesCreator, DownstreamAddressesCreator>();
|
||||
Services.TryAddSingleton<IDelegatingHandlerHandlerFactory, DelegatingHandlerHandlerFactory>();
|
||||
Services.TryAddSingleton<ICacheKeyGenerator, CacheKeyGenerator>();
|
||||
Services.TryAddSingleton<IOcelotConfigurationChangeTokenSource, OcelotConfigurationChangeTokenSource>();
|
||||
Services.TryAddSingleton<IOptionsMonitor<IInternalConfiguration>, OcelotConfigurationMonitor>();
|
||||
|
||||
// see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc
|
||||
// could maybe use a scoped data repository
|
||||
@ -215,7 +220,7 @@ namespace Ocelot.DependencyInjection
|
||||
{
|
||||
// see: https://greatrexpectations.com/2018/10/25/decorators-in-net-core-with-dependency-injection
|
||||
var wrappedDescriptor = Services.First(x => x.ServiceType == typeof(IPlaceholders));
|
||||
|
||||
|
||||
var objectFactory = ActivatorUtilities.CreateFactory(
|
||||
typeof(ConfigAwarePlaceholders),
|
||||
new[] { typeof(IPlaceholders) });
|
||||
@ -229,7 +234,7 @@ namespace Ocelot.DependencyInjection
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
private static object CreateInstance(IServiceProvider services, ServiceDescriptor descriptor)
|
||||
{
|
||||
if (descriptor.ImplementationInstance != null)
|
||||
|
@ -15,4 +15,4 @@ using System.Runtime.InteropServices;
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")]
|
||||
[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")]
|
||||
|
Reference in New Issue
Block a user