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

2023-11-06 07:32

本文主要是介绍在Ocelot中使用自定义的中间件(一),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

问题描述

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

// GET http://localhost:5010/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"

        }

    ]

}

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

{

    "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的调用。运行一下,调用结果如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

// 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设置:

1

2

3

4

5

6

7

8

9

10

11

12

{

  "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网关不可用。比如下面的方法是不行的:



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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

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即可:

1

2

3

4

5

6

app.UseOcelot((builder, config) =>

{

    builder.BuildCustomOcelotPipeline(config)

    .UseMiddleware<ThemeCssMinUrlReplacer>()

    .Build();

});

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

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



执行结果如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

// 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中使用自定义的中间件(一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/355292

相关文章

Java中的ConcurrentBitSet使用小结

《Java中的ConcurrentBitSet使用小结》本文主要介绍了Java中的ConcurrentBitSet使用小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录一、核心澄清:Java标准库无内置ConcurrentBitSet二、推荐方案:Eclipse

Go语言结构体标签(Tag)的使用小结

《Go语言结构体标签(Tag)的使用小结》结构体标签Tag是Go语言中附加在结构体字段后的元数据字符串,用于提供额外的属性信息,这些信息可以通过反射在运行时读取和解析,下面就来详细的介绍一下Tag的使... 目录什么是结构体标签?基本语法常见的标签用途1.jsON 序列化/反序列化(最常用)2.数据库操作(

Java中ScopeValue的使用小结

《Java中ScopeValue的使用小结》Java21引入的ScopedValue是一种作用域内共享不可变数据的预览API,本文就来详细介绍一下Java中ScopeValue的使用小结,感兴趣的可以... 目录一、Java ScopedValue(作用域值)详解1. 定义与背景2. 核心特性3. 使用方法

spring中Interceptor的使用小结

《spring中Interceptor的使用小结》SpringInterceptor是SpringMVC提供的一种机制,用于在请求处理的不同阶段插入自定义逻辑,通过实现HandlerIntercept... 目录一、Interceptor 的核心概念二、Interceptor 的创建与配置三、拦截器的执行顺

C#中checked关键字的使用小结

《C#中checked关键字的使用小结》本文主要介绍了C#中checked关键字的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录✅ 为什么需要checked? 问题:整数溢出是“静默China编程”的(默认)checked的三种用

SpringBoot全局异常拦截与自定义错误页面实现过程解读

《SpringBoot全局异常拦截与自定义错误页面实现过程解读》本文介绍了SpringBoot中全局异常拦截与自定义错误页面的实现方法,包括异常的分类、SpringBoot默认异常处理机制、全局异常拦... 目录一、引言二、Spring Boot异常处理基础2.1 异常的分类2.2 Spring Boot默

C#中预处理器指令的使用小结

《C#中预处理器指令的使用小结》本文主要介绍了C#中预处理器指令的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录 第 1 名:#if/#else/#elif/#endif✅用途:条件编译(绝对最常用!) 典型场景: 示例

自定义注解SpringBoot防重复提交AOP方法详解

《自定义注解SpringBoot防重复提交AOP方法详解》该文章描述了一个防止重复提交的流程,通过HttpServletRequest对象获取请求信息,生成唯一标识,使用Redis分布式锁判断请求是否... 目录防重复提交流程引入依赖properties配置自定义注解切面Redis工具类controller

Mysql中RelayLog中继日志的使用

《Mysql中RelayLog中继日志的使用》MySQLRelayLog中继日志是主从复制架构中的核心组件,负责将从主库获取的Binlog事件暂存并应用到从库,本文就来详细的介绍一下RelayLog中... 目录一、什么是 Relay Log(中继日志)二、Relay Log 的工作流程三、Relay Lo

使用Redis实现会话管理的示例代码

《使用Redis实现会话管理的示例代码》文章介绍了如何使用Redis实现会话管理,包括会话的创建、读取、更新和删除操作,通过设置会话超时时间并重置,可以确保会话在用户持续活动期间不会过期,此外,展示了... 目录1. 会话管理的基本概念2. 使用Redis实现会话管理2.1 引入依赖2.2 会话管理基本操作