在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中使用自定义的中间件(一)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

Programming the Mobile Web

Programming the Mobile Web

Maximiliano Firtman / O'Reilly Media / 2010-07-23 / $44.99

* Learn how to use your existing skills to move into mobile web development * Discover the new possibilities of mobile web development, and understand its limitations * Get detailed coverage of ......一起来看看 《Programming the Mobile Web》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具