zoukankan      html  css  js  c++  java
  • 在Ocelot中使用自定义的中间件(二)

    在上文中,我介绍了如何在Ocelot中使用自定义的中间件来修改下游服务的response body。今天,我们再扩展一下设计,让我们自己设计的中间件变得更为通用,使其能够应用在不同的Route上。比如,我们可以设计一个通用的替换response body的中间件,然后将其应用在多个Route上。

    Ocelot的配置文件

    我们可以将Ocelot的配置信息写在appsettings.json中,当然也可以将其放在单独的json文件里,然后通过ConfigureAppConfiguration的调用,将单独的json文件添加到配置系统中。无论如何,基于JSON文件的Ocelot配置都是可以加入我们自定义的内容的,基于数据库的或者其它存储的配置文件信息或许扩展起来并不方便,因此,使用JSON文件作为配置源还是一个不错的选择。比如,我们可以在ReRoute的某个配置中添加以下内容:

    {
      "DownstreamPathTemplate": "/api/themes",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 5010
        }
      ],
      "UpstreamPathTemplate": "/themes-api/themes",
      "UpstreamHttpMethod": [ "Get" ],
      "CustomMiddlewares": [
        {
          "Name": "themeCssMinUrlReplacer",
          "Enabled": true,
          "Config": {
            "replacementTemplate": "/themes-api/theme-css/{name}"
          }
        }
      ]
    }
    

    然后就需要有一个方法能够解析这部分配置内容。为了方便处理,可以增加以下配置Model,专门存放CustomMiddlewares下的配置信息:

    public class CustomMiddlewareConfiguration
    {
        public string DownstreamPathTemplate { get; set; }
        public string UpstreamPathTemplate { get; set; }
        public int ReRouteConfigurationIndex { get; set; }
        public string Name { get; set; }
        public bool Enabled { get; set; }
        public Dictionary<string, object> Config { get; set; }
    }
    

    然后定义下面的扩展方法,用以从IConfiguration对象中解析出所有的CustomMiddleware的配置信息:

    public static IEnumerable<CustomMiddlewareConfiguration> GetCustomMiddlewareConfigurations(this IConfiguration config)
    {
        var reRoutesConfigSection = config.GetSection("ReRoutes");
        if (reRoutesConfigSection.Exists())
        {
            var reRoutesConfigList = reRoutesConfigSection.GetChildren();
            for (var idx = 0; idx < reRoutesConfigList.Count(); idx++)
            {
                var reRouteConfigSection = reRoutesConfigList.ElementAt(idx);
                var upstreamPathTemplate = reRouteConfigSection.GetSection("UpstreamPathTemplate").Value;
                var downstreamPathTemplate = reRouteConfigSection.GetSection("DownstreamPathTemplate").Value;
                var customMidwareConfigSection = reRouteConfigSection.GetSection("CustomMiddlewares");
                if (customMidwareConfigSection.Exists())
                {
                    var customMidwareConfigList = customMidwareConfigSection.GetChildren();
                    foreach (var customMidwareConfig in customMidwareConfigList)
                    {
                        var customMiddlewareConfiguration = customMidwareConfig.Get<CustomMiddlewareConfiguration>();
                        customMiddlewareConfiguration.UpstreamPathTemplate = upstreamPathTemplate;
                        customMiddlewareConfiguration.DownstreamPathTemplate = downstreamPathTemplate;
                        customMiddlewareConfiguration.ReRouteConfigurationIndex = idx;
                        yield return customMiddlewareConfiguration;
                    }
                }
            }
        }
    
        yield break;
    }
    

    CustomMiddleware基类

    为了提高程序员的开发体验,我们引入CustomMiddleware基类,在Invoke方法中,CustomMiddleware对象会读取所有的CustomMiddleware配置信息,并找到属于当前ReRoute的CustomMiddleware配置信息,从而决定当前的CustomMiddleware是否应该被执行。相关代码如下:

    public abstract class CustomMiddleware : OcelotMiddleware
    {
        #region Private Fields
    
        private readonly ICustomMiddlewareConfigurationManager customMiddlewareConfigurationManager;
        private readonly OcelotRequestDelegate next;
    
        #endregion Private Fields
    
        #region Protected Constructors
    
        protected CustomMiddleware(OcelotRequestDelegate next,
            ICustomMiddlewareConfigurationManager customMiddlewareConfigurationManager,
            IOcelotLogger logger) : base(logger)
        {
            this.next = next;
            this.customMiddlewareConfigurationManager = customMiddlewareConfigurationManager;
        }
    
        #endregion Protected Constructors
    
        #region Public Methods
    
        public async Task Invoke(DownstreamContext context)
        {
            var customMiddlewareConfigurations = from cmc in this
                                                    .customMiddlewareConfigurationManager
                                                    .GetCustomMiddlewareConfigurations()
                                                 where cmc.DownstreamPathTemplate == context
                                                        .DownstreamReRoute
                                                        .DownstreamPathTemplate
                                                        .Value &&
                                                       cmc.UpstreamPathTemplate == context
                                                        .DownstreamReRoute
                                                        .UpstreamPathTemplate
                                                        .OriginalValue
                                                 select cmc;
    
            var thisMiddlewareName = this.GetType().GetCustomAttribute<CustomMiddlewareAttribute>(false)?.Name;
            var customMiddlewareConfiguration = customMiddlewareConfigurations.FirstOrDefault(x => x.Name == thisMiddlewareName);
            if (customMiddlewareConfiguration?.Enabled ?? false)
            {
                await this.DoInvoke(context, customMiddlewareConfiguration);
            }
    
            await this.next(context);
        }
    
        #endregion Public Methods
    
        #region Protected Methods
    
        protected abstract Task DoInvoke(DownstreamContext context, CustomMiddlewareConfiguration configuration);
    
        #endregion Protected Methods
    }
    

    接下来就简单了,只需要让自定义的Ocelot中间件继承于CustomMiddleware基类就行了,当然,为了解耦类型名称与中间件名称,使用一个自定义的CustomMiddlewareAttribute:

    [CustomMiddleware("themeCssMinUrlReplacer")]
    public class ThemeCssMinUrlReplacer : CustomMiddleware
    {
        private readonly Regex regex = new Regex(@"w+://[a-zA-Z0-9]+(:d+)?/themes/(?<theme_name>[a-zA-Z0-9_]+)/bootstrap.min.css");
        public ThemeCssMinUrlReplacer(OcelotRequestDelegate next,
            ICustomMiddlewareConfigurationManager customMiddlewareConfigurationManager,
            IOcelotLoggerFactory loggerFactory) 
            : base(next, customMiddlewareConfigurationManager, loggerFactory.CreateLogger<ThemeCssMinUrlReplacer>())
        {
        }
    
        protected override async Task DoInvoke(DownstreamContext context, CustomMiddlewareConfiguration configuration)
        {
            var downstreamResponseString = await context.DownstreamResponse.Content.ReadAsStringAsync();
            var downstreamResponseJson = JObject.Parse(downstreamResponseString);
            var themesArray = (JArray)downstreamResponseJson["themes"];
            foreach(var token in themesArray)
            {
                var cssMinToken = token["cssMin"];
                var cssMinValue = cssMinToken.Value<string>();
                if (regex.IsMatch(cssMinValue))
                {
                    var themeName = regex.Match(cssMinValue).Groups["theme_name"].Value;
                    var replacementTemplate = configuration.Config["replacementTemplate"].ToString();
                    var replacement = $"{context.HttpContext.Request.Scheme}://{context.HttpContext.Request.Host}{replacementTemplate}"
                        .Replace("{name}", themeName);
                    cssMinToken.Replace(replacement);
                }
            }
    
            context.DownstreamResponse = new DownstreamResponse(
                new StringContent(downstreamResponseJson.ToString(Formatting.None), Encoding.UTF8, "application/json"),
                context.DownstreamResponse.StatusCode, context.DownstreamResponse.Headers, context.DownstreamResponse.ReasonPhrase);
        }
    }
    

    自定义中间件的注册

    在上文介绍的BuildCustomOcelotPipeline扩展方法中,加入以下几行,就完成所有自定义中间件的注册:

    var customMiddlewareTypes = from type in typeof(Startup).Assembly.GetTypes()
                                where type.BaseType == typeof(CustomMiddleware) && 
                                      type.IsDefined(typeof(CustomMiddlewareAttribute), false)
                                select type;
    foreach (var customMiddlewareType in customMiddlewareTypes)
    {
        builder.UseMiddleware(customMiddlewareType);
    }
    

    当然,app.UseOcelot的调用要调整为:

    app.UseOcelot((b, c) => b.BuildCustomOcelotPipeline(c).Build()).Wait();

    运行

    重新运行API网关,得到结果跟之前的一样。所不同的是,我们可以将ThemeCssMinUrlReplacer在其它的ReRoute配置上重用了。

    image

  • 相关阅读:
    使用FolderBrowserDialog组件选择文件夹
    使用OpenFileDialog组件打开多个文
    使用OpenFileDialog组件打开对话框
    获取弹出对话框的相关返回值
    PAT 甲级 1139 First Contact (30 分)
    PAT 甲级 1139 First Contact (30 分)
    PAT 甲级 1138 Postorder Traversal (25 分)
    PAT 甲级 1138 Postorder Traversal (25 分)
    PAT 甲级 1137 Final Grading (25 分)
    PAT 甲级 1137 Final Grading (25 分)
  • 原文地址:https://www.cnblogs.com/daxnet/p/custom-middleware-in-ocelot-part-2.html
Copyright © 2011-2022 走看看