让 Ocelot 与 asp.net core “共存”

让 Ocelot 与 asp.net core “共存”

Intro#

我们的 API 之前是一个单体应用,各个模块的服务是通过 Assembly 集成在一起,最后部署在一个 web server 下的。

我们已经在拆分服务并且在 Ocelot 的基础上封装了我们自己的网关,但是服务还没有完全拆分,于是有这么一个需求,对于 Ocelot 配置的路由去交给 Ocelot 去转发到真正的服务地址,而那些 Ocelot 没有定义的路由则让交给 AspNetCore 去处理。

实现原理#

实现原理是让 Ocelot 作为一个动态分支路由,只有当 Ocelot 配置了对应路由的下游地址才走 Ocelot 的分支,才把请求交给 Ocelot 处理。

我们可以使用 MapWhen 来处理,接下来就需要知道怎么样判断 Ocelot 是否配置了某一个路由,Ocelot 内部的处理管道,在向下游请求之前是要找到对应匹配的下游路由,所以我们去看一看 Ocelot 的源码,看看 Ocelot 内部是怎么找下游路由的,Ocelot 找下游路由中间件源码

        public async Task Invoke(DownstreamContext context)
        {
            var upstreamUrlPath = context.HttpContext.Request.Path.ToString();

            var upstreamQueryString = context.HttpContext.Request.QueryString.ToString();

            var upstreamHost = context.HttpContext.Request.Headers["Host"];

            Logger.LogDebug($"Upstream url path is {upstreamUrlPath}");

            var provider = _factory.Get(context.Configuration);

            // 获取下游路由
            var downstreamRoute = provider.Get(upstreamUrlPath, upstreamQueryString, context.HttpContext.Request.Method, context.Configuration, upstreamHost);

            if (downstreamRoute.IsError)
            {
                Logger.LogWarning($"{MiddlewareName} setting pipeline errors. IDownstreamRouteFinder returned {downstreamRoute.Errors.ToErrorString()}");

                SetPipelineError(context, downstreamRoute.Errors);
                return;
            }            
            
            var downstreamPathTemplates = string.Join(", ", downstreamRoute.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamPathTemplate.Value));
            
            Logger.LogDebug($"downstream templates are {downstreamPathTemplates}");

            context.TemplatePlaceholderNameAndValues = downstreamRoute.Data.TemplatePlaceholderNameAndValues;

            await _multiplexer.Multiplex(context, downstreamRoute.Data.ReRoute, _next);
        }

通过上面的源码,我们就可以判断 Ocelot 是否有与请求相匹配的下游路由信息

实现#

既然找到了 Ocelot 如何找下游路由,就先给 Ocelot 加一个扩展吧,实现代码如下,Ocelot 扩展完整代码

        public static IApplicationBuilder UseOcelotWhenRouteMatch(this IApplicationBuilder app,
            Action<IOcelotPipelineBuilder, OcelotPipelineConfiguration> builderAction)
            => UseOcelotWhenRouteMatch(app, builderAction, new OcelotPipelineConfiguration());

        public static IApplicationBuilder UseOcelotWhenRouteMatch(this IApplicationBuilder app,
            Action<OcelotPipelineConfiguration> pipelineConfigurationAction,
            Action<IOcelotPipelineBuilder, OcelotPipelineConfiguration> builderAction)
        {
            var pipelineConfiguration = new OcelotPipelineConfiguration();
            pipelineConfigurationAction?.Invoke(pipelineConfiguration);
            return UseOcelotWhenRouteMatch(app, builderAction, pipelineConfiguration);
        }

        public static IApplicationBuilder UseOcelotWhenRouteMatch(this IApplicationBuilder app, Action<IOcelotPipelineBuilder, OcelotPipelineConfiguration> builderAction, OcelotPipelineConfiguration configuration)
        {
            app.MapWhen(context =>
            {
                // 获取 OcelotConfiguration
                var internalConfigurationResponse =
                    context.RequestServices.GetRequiredService<IInternalConfigurationRepository>().Get();
                if (internalConfigurationResponse.IsError || internalConfigurationResponse.Data.ReRoutes.Count == 0)
                {
                    // 如果没有配置路由信息,不符合分支路由的条件,直接退出
                    return false;
                }

                var internalConfiguration = internalConfigurationResponse.Data;
                var downstreamRouteFinder = context.RequestServices
                    .GetRequiredService<IDownstreamRouteProviderFactory>()
                    .Get(internalConfiguration);
                // 根据请求以及上面获取的Ocelot配置获取下游路由
                var response = downstreamRouteFinder.Get(context.Request.Path, context.Request.QueryString.ToString(),
                    context.Request.Method, internalConfiguration, context.Request.Host.ToString());
                // 如果有匹配路由则满足该分支路由的条件,交给 Ocelot 处理
                return !response.IsError
                       && !string.IsNullOrEmpty(response.Data?.ReRoute?.DownstreamReRoute?.FirstOrDefault()
                           ?.DownstreamScheme);
            }, appBuilder => appBuilder.UseOcelot(builderAction, configuration).Wait());

            return app;
        }

使用#

在 Startup 里

ConfigurationServices 配置 mvc 和 Ocelot

Configure 方法里配置 ocelot 和 mvc


app.UseOcelotWhenRouteMatch((ocelotBuilder, pipelineConfiguration) =>
                            {
                                // This is registered to catch any global exceptions that are not handled
                                // It also sets the Request Id if anything is set globally
                                ocelotBuilder.UseExceptionHandlerMiddleware();
                                // This is registered first so it can catch any errors and issue an appropriate response
                                ocelotBuilder.UseResponderMiddleware();
                                ocelotBuilder.UseDownstreamRouteFinderMiddleware();
                                ocelotBuilder.UseDownstreamRequestInitialiser();
                                ocelotBuilder.UseRequestIdMiddleware();
                                ocelotBuilder.UseMiddleware<ClaimsToHeadersMiddleware>();
                                ocelotBuilder.UseLoadBalancingMiddleware();
                                ocelotBuilder.UseDownstreamUrlCreatorMiddleware();
                                ocelotBuilder.UseOutputCacheMiddleware();
                                ocelotBuilder.UseMiddleware<HttpRequesterMiddleware>();
                                // cors headers
                                ocelotBuilder.UseMiddleware<CorsMiddleware>();
                            });

app.UseMvc();

新建一个 TestController

    [Route("/api/[controller]")]
    public class TestController : ControllerBase
    {
        public IActionResult Get()
        {
            return Ok(new
            {
                Tick = DateTime.UtcNow.Ticks,
                Msg = "Hello Ocelot",
            });
        }
    }

具体代码可以参考这个 网关示例项目

示例项目的 Ocelot 配置是存在 Redis 里面的,配置的 ReRoutes 如下:

{
  "ReRoutes": [
    {
      "DownstreamPathTemplate": "/api.php?key=free&appid=0&msg={everything}",
      "UpstreamPathTemplate": "/api/chat/{everything}",
      "UpstreamHttpMethod": [
        "Get",
        "POST",
        "PUT",
        "PATCH",
        "DELETE",
        "OPTIONS"
      ],
      "AddHeadersToRequest": {
      },
      "RequestIdKey": "RequestId",
      "ReRouteIsCaseSensitive": false,
      "ServiceName": "",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "api.qingyunke.com",
          "Port": 80
        }
      ],
      "DangerousAcceptAnyServerCertificateValidator": false
    }
  ],
  "GlobalConfiguration": {
      "HttpHandlerOptions": {
        "AllowAutoRedirect": false,
        "UseCookieContainer": false,
        "UseTracing": false
      }
  }
}

运行项目进行测试:

访问 Ocelot 定义的路由 http://localhost:65125/api/chat/hello ,返回信息如图所示:

ocelot-forward-route.png

访问 Mvc 定义的路由 http://localhost:65125/api/test,返回信息如图所示:

mvc-roite

上面正常的返回就表示我们的 Ocelot 和 Mvc 同时工作了~

Reference#

作者:weihanli

出处:https://www.cnblogs.com/weihanli/p/integrate-ocelot-route-with-aspnetcore.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   WeihanLi  阅读(1830)  评论(8编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
历史上的今天:
2015-05-22 Get a developer license for windows store app
点击右上角即可分享
微信分享提示
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu