Ocelot簡易教程(七)之配置文件數據庫存儲插件源碼解析
作者:依樂祝
原文地址:https://www.cnblogs.com/yilezhu/p/9852711.html
上篇文章給大家分享了如何集成我寫的一個Ocelot擴展插件把Ocelot的配置存儲到數據庫中。並沒有對實現原理進行相應的闡述。今天抽空把實現的原理給大家說道說道。明白原理後,大家就可以自行改寫進行擴展來滿足自身需要了!
再次感覺張隊的審稿,並給出的修改意見!
源碼解析過程
大家可以自行分析Ocelot的源碼,我通過分析ocelot的源碼得出,如果要實現重寫配置文件的方式,只需要寫一個類來實現IFileConfigurationRepository這個接口即可。
代碼如下:
/// <summary> /// yilezhu /// 2018.10.22 /// 實現從SQLSERVER數據庫中提取配置信息 /// </summary> public class SqlServerFileConfigurationRepository : IFileConfigurationRepository { private readonly IOcelotCache<FileConfiguration> _cache; private readonly IOcelotLogger _logger; private readonly ConfigAuthLimitCacheOptions _option; public SqlServerFileConfigurationRepository(ConfigAuthLimitCacheOptions option, IOcelotCache<FileConfiguration> cache, IOcelotLoggerFactory loggerFactory) { _option = option; _cache = cache; _logger = loggerFactory.CreateLogger<SqlServerFileConfigurationRepository>(); } public Task<Response> Set(FileConfiguration fileConfiguration) { _cache.AddAndDelete(_option.CachePrefix + "FileConfiguration", fileConfiguration, TimeSpan.FromSeconds(1800), ""); return Task.FromResult((Response)new OkResponse()); } /// <summary> /// 提取配置信息 /// </summary> /// <returns></returns> public async Task<Response<FileConfiguration>> Get() { var config = _cache.Get(_option.CachePrefix + "FileConfiguration", ""); if (config != null) { return new OkResponse<FileConfiguration>(config); } #region 提取配置信息 var file = new FileConfiguration(); string glbsql = "select top 1 * from OcelotGlobalConfiguration where IsDefault=1"; //提取全局配置信息 using (var connection = new SqlConnection(_option.DbConnectionStrings)) { var result = await connection.QueryFirstOrDefaultAsync<OcelotGlobalConfiguration>(glbsql); if (result != null) { var glb = new FileGlobalConfiguration(); glb.BaseUrl = result.BaseUrl; glb.DownstreamScheme = result.DownstreamScheme; glb.RequestIdKey = result.RequestIdKey; if (!String.IsNullOrEmpty(result.HttpHandlerOptions)) { glb.HttpHandlerOptions = result.HttpHandlerOptions.ToObject<FileHttpHandlerOptions>(); } if (!String.IsNullOrEmpty(result.LoadBalancerOptions)) { glb.LoadBalancerOptions = result.LoadBalancerOptions.ToObject<FileLoadBalancerOptions>(); } if (!String.IsNullOrEmpty(result.QoSOptions)) { glb.QoSOptions = result.QoSOptions.ToObject<FileQoSOptions>(); } if (!String.IsNullOrEmpty(result.ServiceDiscoveryProvider)) { glb.ServiceDiscoveryProvider = result.ServiceDiscoveryProvider.ToObject<FileServiceDiscoveryProvider>(); } file.GlobalConfiguration = glb; //提取路由信息 string routesql = "select * from OcelotReRoutes where OcelotGlobalConfigurationId=@OcelotGlobalConfigurationId and IsStatus=1"; var routeresult = (await connection.QueryAsync<OcelotReRoutes>(routesql, new { OcelotGlobalConfigurationId=result.Id })).AsList(); if (routeresult != null && routeresult.Count > 0) { var reroutelist = new List<FileReRoute>(); foreach (var model in routeresult) { var m = new FileReRoute(); if (!String.IsNullOrEmpty(model.AuthenticationOptions)) { m.AuthenticationOptions = model.AuthenticationOptions.ToObject<FileAuthenticationOptions>(); } if (!String.IsNullOrEmpty(model.CacheOptions)) { m.FileCacheOptions = model.CacheOptions.ToObject<FileCacheOptions>(); } if (!String.IsNullOrEmpty(model.DelegatingHandlers)) { m.DelegatingHandlers = model.DelegatingHandlers.ToObject<List<string>>(); } if (!String.IsNullOrEmpty(model.LoadBalancerOptions)) { m.LoadBalancerOptions = model.LoadBalancerOptions.ToObject<FileLoadBalancerOptions>(); } if (!String.IsNullOrEmpty(model.QoSOptions)) { m.QoSOptions = model.QoSOptions.ToObject<FileQoSOptions>(); } if (!String.IsNullOrEmpty(model.DownstreamHostAndPorts)) { m.DownstreamHostAndPorts = model.DownstreamHostAndPorts.ToObject<List<FileHostAndPort>>(); } //開始賦值 m.DownstreamPathTemplate = model.DownstreamPathTemplate; m.DownstreamScheme = model.DownstreamScheme; m.Key = model.Key; m.Priority = model.Priority ?? 0; m.RequestIdKey = model.RequestIdKey; m.ServiceName = model.ServiceName; m.Timeout = model.Timeout ?? 0; m.UpstreamHost = model.UpstreamHost; if (!String.IsNullOrEmpty(model.UpstreamHttpMethod)) { m.UpstreamHttpMethod = model.UpstreamHttpMethod.ToObject<List<string>>(); } m.UpstreamPathTemplate = model.UpstreamPathTemplate; reroutelist.Add(m); } file.ReRoutes = reroutelist; } } else { throw new Exception("未監測到配置信息"); } } #endregion if (file.ReRoutes == null || file.ReRoutes.Count == 0) { return new OkResponse<FileConfiguration>(null); } return new OkResponse<FileConfiguration>(file); } }
當然,既然我們已經重新實現了這個接口,那麽就得進行相應的DI了。這裏我們擴展下IOcelotBuilder方法,代碼如下,主要就是進行相應的服務的DI:
/// <summary> /// yilezhu /// 2018.10.22 /// 基於Ocelot擴展的依賴註入 /// </summary> public static class ServiceCollectionExtensions { /// <summary> /// 添加默認的註入方式,所有需要傳入的參數都是用默認值 /// </summary> /// <param name="builder"></param> /// <returns></returns> public static IOcelotBuilder AddAuthLimitCache(this IOcelotBuilder builder, Action<ConfigAuthLimitCacheOptions> option) { builder.Services.Configure(option); builder.Services.AddSingleton( resolver => resolver.GetRequiredService<IOptions<ConfigAuthLimitCacheOptions>>().Value); #region 註入其他配置信息 //重寫提取Ocelot配置信息, builder.Services.AddSingleton(DataBaseConfigurationProvider.Get); //builder.Services.AddHostedService<FileConfigurationPoller>(); builder.Services.AddSingleton<IFileConfigurationRepository, SqlServerFileConfigurationRepository>(); //註入自定義限流配置 //註入認證信息 #endregion return builder; } }
接下來就是重寫,OcelotBuild裏面配置文件的獲取方式了。這裏我選擇的是對IApplicationBuilder進行擴展,因為這樣方便做一些其他的事情,比如,重寫限流,集成自定義的驗證等等。具體代碼如下:
/// <summary>
/// yilezhu
/// 2018.10.22
/// 擴展IApplicationBuilder,新增use方法
/// </summary>
public static class OcelotMiddlewareExtensions
{
/// <summary>
/// 擴展UseOcelot
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static async Task<IApplicationBuilder> UseAhphOcelot(this IApplicationBuilder builder)
{
await builder.UseAhphOcelot(new OcelotPipelineConfiguration());
return builder;
}
/// <summary>
/// 重寫Ocelot,帶參數
/// </summary>
/// <param name="builder"></param>
/// <param name="pipelineConfiguration"></param>
/// <returns></returns>
public static async Task<IApplicationBuilder> UseAhphOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration)
{
var configuration = await CreateConfiguration(builder);
ConfigureDiagnosticListener(builder);
return CreateOcelotPipeline(builder, pipelineConfiguration);
}
private static async Task<IInternalConfiguration> CreateConfiguration(IApplicationBuilder builder)
{
// make configuration from file system?
// earlier user needed to add ocelot files in startup configuration stuff, asp.net will map it to this
//var fileConfig = builder.ApplicationServices.GetService<IOptionsMonitor<FileConfiguration>>();
var fileConfig = await builder.ApplicationServices.GetService<IFileConfigurationRepository>().Get();
// now create the config
var internalConfigCreator = builder.ApplicationServices.GetService<IInternalConfigurationCreator>();
var internalConfig = await internalConfigCreator.Create(fileConfig.Data);
//Configuration error, throw error message
if (internalConfig.IsError)
{
ThrowToStopOcelotStarting(internalConfig);
}
// now save it in memory
var internalConfigRepo = builder.ApplicationServices.GetService<IInternalConfigurationRepository>();
internalConfigRepo.AddOrReplace(internalConfig.Data);
//fileConfig.OnChange(async (config) =>
//{
// var newInternalConfig = await internalConfigCreator.Create(config);
// internalConfigRepo.AddOrReplace(newInternalConfig.Data);
//});
var adminPath = builder.ApplicationServices.GetService<IAdministrationPath>();
var configurations = builder.ApplicationServices.GetServices<OcelotMiddlewareConfigurationDelegate>();
// Todo - this has just been added for consul so far...will there be an ordering problem in the future? Should refactor all config into this pattern?
foreach (var configuration in configurations)
{
await configuration(builder);
}
if (AdministrationApiInUse(adminPath))
{
//We have to make sure the file config is set for the ocelot.env.json and ocelot.json so that if we pull it from the
//admin api it works...boy this is getting a spit spags boll.
var fileConfigSetter = builder.ApplicationServices.GetService<IFileConfigurationSetter>();
// await SetFileConfig(fileConfigSetter, fileConfig.Data);
}
return GetOcelotConfigAndReturn(internalConfigRepo);
}
private static bool AdministrationApiInUse(IAdministrationPath adminPath)
{
return adminPath != null;
}
//private static async Task SetFileConfig(IFileConfigurationSetter fileConfigSetter, IOptionsMonitor<FileConfiguration> fileConfig)
//{
// var response = await fileConfigSetter.Set(fileConfig.CurrentValue);
// if (IsError(response))
// {
// ThrowToStopOcelotStarting(response);
// }
//}
private static bool IsError(Response response)
{
return response == null || response.IsError;
}
private static IInternalConfiguration GetOcelotConfigAndReturn(IInternalConfigurationRepository provider)
{
var ocelotConfiguration = provider.Get();
if (ocelotConfiguration?.Data == null || ocelotConfiguration.IsError)
{
ThrowToStopOcelotStarting(ocelotConfiguration);
}
return ocelotConfiguration.Data;
}
private static void ThrowToStopOcelotStarting(Response config)
{
throw new Exception($"Unable to start Ocelot, errors are: {string.Join(",", config.Errors.Select(x => x.ToString()))}");
}
private static IApplicationBuilder CreateOcelotPipeline(IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration)
{
var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices);
//重寫自定義管道
pipelineBuilder.BuildAhphOcelotPipeline(pipelineConfiguration);
var firstDelegate = pipelineBuilder.Build();
/*
inject first delegate into first piece of asp.net middleware..maybe not like this
then because we are updating the http context in ocelot it comes out correct for
rest of asp.net..
*/
builder.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware";
builder.Use(async (context, task) =>
{
var downstreamContext = new DownstreamContext(context);
await firstDelegate.Invoke(downstreamContext);
});
return builder;
}
private static void ConfigureDiagnosticListener(IApplicationBuilder builder)
{
var env = builder.ApplicationServices.GetService<IHostingEnvironment>();
var listener = builder.ApplicationServices.GetService<OcelotDiagnosticListener>();
var diagnosticListener = builder.ApplicationServices.GetService<DiagnosticListener>();
diagnosticListener.SubscribeWithAdapter(listener);
}
}
這其中最主要的代碼就是,重寫配置文件獲取這塊。我在下面進行了截圖,並圈出來了,大家自行查看吧。
代碼重寫好了。由於我們服務註冊時通過擴展IOcelotBuilder,所以,我們需要在ConfigureServices方法引入Ocelot服務的時候比Ocelot多寫一個方法,並傳入相關的配置信息,如下所示:
services.AddOcelot()//註入Ocelot服務
.AddAuthLimitCache(option=> {
option.DbConnectionStrings = "Server=.;Database=Ocelot;User ID=sa;Password=1;";
});
這裏的目的就是為了註入我們實現了IFileConfigurationRepository接口的SqlServerFileConfigurationRepository這個類。
接下來就是在管道中使用我們重寫的Ocelot服務了。如下所示,在Configure方法中按如下代碼進行使用:
app.UseAhphOcelot().Wait();
好了,以上就是實現的整個過程了。經過這麽一分析是不是覺得很簡單呢。當然具體為什麽按照上面處理就能夠從數據庫獲取配置了呢,這個還需要你分析了源碼後才能了解。我也只是給你引路,傳達我實現的思路。
源碼
https://github.com/yilezhu/Ocelot.ConfigAuthLimitCache
總結
今天抽空對上篇文章進行了補充說明,目的是給大家闡述下,配置文件存儲到數據庫中的實現過程及原理。讓你能夠根據自身需要來進行改寫來滿足你的業務需求。當然我也只是給你引路,具體為什麽這樣實現下就能夠成功呢?答案在Ocelot的源碼中。
如果你想了解更多Ocelot的定制相關的·內容可以看我一個朋友寫的系列博客,博客地址:https://www.cnblogs.com/jackcao/p/9928879.html 【.NET Core微服務實戰-統一身份認證】網關篇,這裏給你詳細介紹了如何進行自定義限流以及客戶端授權並重寫Ocelot緩存成Redis!有興趣的可以看下!
Ocelot簡易教程目錄
- Ocelot簡易教程(一)之Ocelot是什麽
- Ocelot簡易教程(二)之快速開始1
- Ocelot簡易教程(二)之快速開始2
- Ocelot簡易教程(三)之主要特性及路由詳解
- Ocelot簡易教程(四)之請求聚合以及服務發現
- Ocelot簡易教程(五)之集成IdentityServer認證以及授權
- Ocelot簡易教程(六)之重寫配置文件存儲方式並優化響應數據
- Ocelot簡易教程(七)之配置文件數據庫存儲插件源碼解析
Ocelot簡易教程(七)之配置文件數據庫存儲插件源碼解析