在Ocelot中使用自定义的中间件(一)

栏目: IT技术 · 发布时间: 4年前

内容简介:(总访问量:5;当日访问量:5)

Ocelot是ASP.NET Core下的API网关的一种实现,在微服务架构领域发挥了非常重要的作用。本文不会从整个微服务架构的角度来介绍Ocelot,而是介绍一下最近在学习过程中遇到的一个问题,以及如何使用中间件(Middleware)来解决这样的问题。

问题描述

在上文中,我介绍了一种在Angular站点里基于Bootstrap切换主题的方法。之后,我将多个主题的boostrap.min.css文件放到一个ASP.NET Core Web API的站点上,并用静态文件的方式进行分发,在完成这部分工作之后,调用这个Web API,就可以从服务端获得主题信息以及所对应的样式文件。例如:

// GET <a href="http://localhost:5010/api/themes">http://localhost:5010/api/themes</a>
{
    "version": "1.0.0",
    "themes": [
        {
            "name": "蔚蓝 (Cerulean)",
            "description": "Cerulean",
            "category": "light",
            "cssMin": "http://localhost:5010/themes/cerulean/bootstrap.min.css",
            "navbarClass": "navbar-dark",
            "navbarBackgroundClass": "bg-primary",
            "footerTextClass": "text-light",
            "footerLinkClass": "text-light",
            "footerBackgroundClass": "bg-primary"
        },
        {
            "name": "机械 (Cyborg)",
            "description": "Cyborg",
            "category": "dark",
            "cssMin": "http://localhost:5010/themes/cyborg/bootstrap.min.css",
            "navbarClass": "navbar-dark",
            "navbarBackgroundClass": "bg-dark",
            "footerTextClass": "text-dark",
            "footerLinkClass": "text-dark",
            "footerBackgroundClass": "bg-light"
        }
    ]
}

当然,整个项目中不仅仅是有这个themes API,还有另外2-3个服务在后台运行,项目是基于微服务架构的。为了能够让前端有统一的API接口,我使用Ocelot作为服务端的API网关,以便为Angular站点提供API服务。于是,我定义了如下ReRoute规则:

{
    "ReRoutes": [
        {
            "DownstreamPathTemplate": "/api/themes",
            "DownstreamScheme": "http",
            "DownstreamHostAndPorts": [
                {
                    "Host": "localhost",
                    "Port": 5010
                }
            ],
            "UpstreamPathTemplate": "/themes-api/themes",
            "UpstreamHttpMethod": [ "Get" ]
        }
    ]
}

假设API网关运行在 http://localhost:9023 ,那么基于上面的ReRoute规则,通过访问 http://localhost:9023/themes-api/themes ,即可转发到后台的 http://localhost:5010/api/themes ,完成API的调用。运行一下,调用结果如下:

// GET http://localhost:9023/themes-api/themes
{
    "version": "1.0.0",
    "themes": [
        {
            "name": "蔚蓝 (Cerulean)",
            "description": "Cerulean",
            "category": "light",
            "cssMin": "http://localhost:5010/themes/cerulean/bootstrap.min.css",
            "navbarClass": "navbar-dark",
            "navbarBackgroundClass": "bg-primary",
            "footerTextClass": "text-light",
            "footerLinkClass": "text-light",
            "footerBackgroundClass": "bg-primary"
        },
        {
            "name": "机械 (Cyborg)",
            "description": "Cyborg",
            "category": "dark",
            "cssMin": "http://localhost:5010/themes/cyborg/bootstrap.min.css",
            "navbarClass": "navbar-dark",
            "navbarBackgroundClass": "bg-dark",
            "footerTextClass": "text-dark",
            "footerLinkClass": "text-dark",
            "footerBackgroundClass": "bg-light"
        }
    ]
}

看上去一切正常,但是,每个主题设置的css文件地址仍然还是指向下游服务的URL地址,比如上面的cssMin中,还是使用的 http://localhost:5010 。从部署的角度,外部是无法访问除了API网关以外的其它服务的,于是,这就造成了css文件无法被访问的问题。

解决这个问题的思路很简单,就是API网关在返回response的时候,将cssMin的地址替换掉。如果在Ocelot的配置中加入以下ReRoute设置:

{
  "DownstreamPathTemplate": "/themes/{name}/bootstrap.min.css",
  "DownstreamScheme": "http",
  "DownstreamHostAndPorts": [
    {
      "Host": "localhost",
      "Port": 5010
    }
  ],
  "UpstreamPathTemplate": "/themes-api/theme-css/{name}",
  "UpstreamHttpMethod": [ "Get" ]
}

那么只需要将下游response中cssMin的值(比如 http://localhost:5010/themes/cyborg/bootstrap.min.css )替换为Ocelot网关中设置的上游URL(比如 http://localhost:9023/themes-api/theme-css/cyborg ),然后将替换后的response返回给API调用方即可。这个过程,可以使用Ocelot中间件完成。

使用Ocelot中间件

Ocelot中间件是继承于OcelotMiddleware类的子类,并且可以在Startup.Configure方法中,通过app.UseOcelot方法将中间件注入到Ocelot管道中,然而,简单地调用IOcelotPipelineBuilder的UseMiddleware方法是不行的,它会导致整个Ocelot网关不可用。比如下面的方法是不行的:

app.UseOcelot((builder, config) =>
{
    builder.UseMiddleware<ThemeCssMinUrlReplacer>();
});

这是因为没有将Ocelot的其它Middleware加入到管道中,Ocelot管道中只有ThemeCssMinUrlReplacer中间件。要解决这个问题,我目前的方法就是通过使用扩展方法,将所有Ocelot中间全部注册好,然后再注册自定义的中间件,比如:

public static IOcelotPipelineBuilder BuildCustomOcelotPipeline(this IOcelotPipelineBuilder builder,
    OcelotPipelineConfiguration pipelineConfiguration)
{
    builder.UseExceptionHandlerMiddleware();
    builder.MapWhen(context => context.HttpContext.WebSockets.IsWebSocketRequest,
        app =>
        {
            app.UseDownstreamRouteFinderMiddleware();
            app.UseDownstreamRequestInitialiser();
            app.UseLoadBalancingMiddleware();
            app.UseDownstreamUrlCreatorMiddleware();
            app.UseWebSocketsProxyMiddleware();
        });
    builder.UseIfNotNull(pipelineConfiguration.PreErrorResponderMiddleware);
    builder.UseResponderMiddleware();
    builder.UseDownstreamRouteFinderMiddleware();
    builder.UseSecurityMiddleware();
    if (pipelineConfiguration.MapWhenOcelotPipeline != null)
    {
        foreach (var pipeline in pipelineConfiguration.MapWhenOcelotPipeline)
        {
            builder.MapWhen(pipeline);
        }
    }
    builder.UseHttpHeadersTransformationMiddleware();
    builder.UseDownstreamRequestInitialiser();
    builder.UseRateLimiting();

    builder.UseRequestIdMiddleware();
    builder.UseIfNotNull(pipelineConfiguration.PreAuthenticationMiddleware);
    if (pipelineConfiguration.AuthenticationMiddleware == null)
    {
        builder.UseAuthenticationMiddleware();
    }
    else
    {
        builder.Use(pipelineConfiguration.AuthenticationMiddleware);
    }
    builder.UseClaimsToClaimsMiddleware();
    builder.UseIfNotNull(pipelineConfiguration.PreAuthorisationMiddleware);
    if (pipelineConfiguration.AuthorisationMiddleware == null)
    {
        builder.UseAuthorisationMiddleware();
    }
    else
    {
        builder.Use(pipelineConfiguration.AuthorisationMiddleware);
    }
    builder.UseClaimsToHeadersMiddleware();
    builder.UseIfNotNull(pipelineConfiguration.PreQueryStringBuilderMiddleware);
    builder.UseClaimsToQueryStringMiddleware();
    builder.UseLoadBalancingMiddleware();
    builder.UseDownstreamUrlCreatorMiddleware();
    builder.UseOutputCacheMiddleware();
    builder.UseHttpRequesterMiddleware();
    
    return builder;
}

然后再调用app.UseOcelot即可:

app.UseOcelot((builder, config) =>
{
    builder.BuildCustomOcelotPipeline(config)
    .UseMiddleware<ThemeCssMinUrlReplacer>()
    .Build();
});

这种做法其实听起来不是特别的优雅,但是目前也没找到更合适的方式来解决Ocelot中间件注册的问题。

以下便是ThemeCssMinUrlReplacer中间件的代码,可以看到,我们使用正则表达式替换了cssMin的URL部分,使得css文件的地址可以正确被返回:

public class ThemeCssMinUrlReplacer : OcelotMiddleware
{
    private readonly Regex regex = new Regex(@"\w+://[a-zA-Z0-9]+(\:\d+)?/themes/(?<theme_name>[a-zA-Z0-9_]+)/bootstrap.min.css");
    private const string ReplacementTemplate = "/themes-api/theme-css/{name}";
    private readonly OcelotRequestDelegate next;
    public ThemeCssMinUrlReplacer2(OcelotRequestDelegate next,
        IOcelotLoggerFactory loggerFactory) : base(loggerFactory.CreateLogger<ThemeCssMinUrlReplacer2>())
        => this.next = next;

    public async Task Invoke(DownstreamContext context)
    {
        if (!string.Equals(context.DownstreamReRoute.DownstreamPathTemplate.Value, "/api/themes"))
        {
            await next(context);
        }

        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 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);
    }

}

执行结果如下:

// GET http://localhost:9023/themes-api/themes
{
  "version": "1.0.0",
  "themes": [
    {
      "name": "蔚蓝 (Cerulean)",
      "description": "Cerulean",
      "category": "light",
      "cssMin": "http://localhost:9023/themes-api/theme-css/cerulean",
      "navbarClass": "navbar-dark",
      "navbarBackgroundClass": "bg-primary",
      "footerTextClass": "text-light",
      "footerLinkClass": "text-light",
      "footerBackgroundClass": "bg-primary"
    },
    {
      "name": "机械 (Cyborg)",
      "description": "Cyborg",
      "category": "dark",
      "cssMin": "http://localhost:9023/themes-api/theme-css/cyborg",
      "navbarClass": "navbar-dark",
      "navbarBackgroundClass": "bg-dark",
      "footerTextClass": "text-dark",
      "footerLinkClass": "text-dark",
      "footerBackgroundClass": "bg-light"
    }
  ]
}

总结

本文介绍了使用Ocelot中间件实现下游服务response body的替换任务,在ThemeCssMinUrlReplacer的实现代码中,我们使用了context.DownstreamReRoute.DownstreamPathTemplate.Value来判断当前执行的URL是否需要由该中间件进行处理,以避免不必要的中间件逻辑执行。这个设计可以再优化一下,使用一个简单的框架让 程序员 可以通过Ocelot的配置文件来更为灵活地使用Ocelot中间件,下文介绍这部分内容。

在Ocelot中使用自定义的中间件(一)

(总访问量:5;当日访问量:5)


以上所述就是小编给大家介绍的《在Ocelot中使用自定义的中间件(一)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

微信营销与运营

微信营销与运营

秦阳、秋叶 / 人民邮电出版社 / 2016-12-1 / 39.80

《微信营销与运营》共分七章。第1章重点介绍了微信营销的概念、价值和特征,引导读者全面认识微信营销;第2章介绍了个人微信号的运营技巧和手法;第3章重点介绍了微信公众平台的基础操作入门,申请适合自己的公众平台类型并进行基本设置;第4章介绍了微信运营的规划策略,落实公众号的定位、内容问题;第5章介绍微信运营中包括排版、增加粉丝、提升阅读量等运营实战中的经验和手法,并了解微信运营的整个运营框架体系;第6章......一起来看看 《微信营销与运营》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

URL 编码/解码
URL 编码/解码

URL 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具