Ocelot自定義管道中間件


Ocelot是啥就不介紹了哈,網關大家都知道,如果大家看過源碼會發現其核心就是由一個個的管道中間件組成的,當然這也是Net Core的亮點之一。一個請求到來,會按照中間件的注冊順序進行處理,今天的問題出在Ocelot管道中間件這里,現在有個需求是想要在網關層面對數據進行加密解密,前端會對數據進行加密,通過網關的時候進行解密傳遞下游,下游返回數據網關進行加密返回給前端。

所以這里就想在走Ocelot管道前后注冊兩個中間件對請求和結果數據進行處理。然后就按想的去做,但悲催的是,Ocelot處理完后就直接返回數據給調用方了,根本沒有走它后面的管道中間件,查資料才知道Ocelot之后不會再調用下一個管道中間件了,這就蛋疼了,怎么辦??

突然想到Ocelot應該會提供擴展來讓使用者自定義管道中間件吧,回答當然是可以的,本篇我們就來自定義一個管道中間件放在Ocelot中。

首先需要寫一個自己的中間件,需要繼承OcelotMiddleware:

public class TestResponseMiddleware: OcelotMiddleware
    {
        private readonly OcelotRequestDelegate _next;
        public TestResponseMiddleware(OcelotRequestDelegate next,IOcelotLoggerFactory loggerFactory)
            : base(loggerFactory.CreateLogger<TestResponseMiddleware>())
        {
            _next = next;
        }


        public async Task Invoke(DownstreamContext context)
        {
            if (!context.IsError && context.HttpContext.Request.Method.ToUpper() != "OPTIONS")
            {
                //對返回結果進行加密
                //Logger.LogInformation("");
                if (context.HttpContext.Response != null && context.DownstreamResponse.Content.Headers.ContentLength > 0)
                {
                    var result= await context.DownstreamResponse.Content.ReadAsStringAsync();
                    using (var md5 = MD5.Create())
                    {
                        var md5Result = md5.ComputeHash(Encoding.ASCII.GetBytes(result));
                        var strResult = BitConverter.ToString(md5Result);
                        strResult = strResult.Replace("-", "");
                        context.HttpContext.Response.Body.Write(Encoding.UTF8.GetBytes(strResult));
                    }
                }
            }
            else
            {
                await _next.Invoke(context);
            }
        }
    }

這個邏輯就是拿到請求結果之后對數據進行MD5加密,然后再返回。

然后我們新建一個注冊類,創建專門用來注冊管道中間件的方法:

    public static class TestServiceExtension
    {
        public static IOcelotPipelineBuilder UseTestResponseMiddleware(this IOcelotPipelineBuilder builder)
        {
            return builder.UseMiddleware<TestResponseMiddleware>();
        }
    }

然后就是重點了!!我們需要去翻Ocelot的源碼,找到其中注冊管道中間件的地方,然后把那個類文件復制過來,放到自己的項目中引用,你可以理解為修改了源碼來用。我們首先要看的是OcelotMiddlewareExtensions.cs文件,這里是Ocelot管道中間件的調用入口,不信你看UseOcelot擴展方法:

public static class OcelotMiddlewareExtensions
    {
        public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder)
        {
            await builder.UseOcelot(new OcelotPipelineConfiguration());
            return builder;
        }

我們要看的是這里面的另外一個方法,Ocelot管道的CreateOcelotPipeline:

        private static IApplicationBuilder CreateOcelotPipeline(IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration)
        {
            var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices);

            pipelineBuilder.BuildOcelotPipeline(pipelineConfiguration);

            var firstDelegate = pipelineBuilder.Build();

            /*
            inject first delegate into first piece of asp.net middleware..maybe not like this
            then because we are updating the http context in ocelot it comes out correct for
            rest of asp.net..
            */

            builder.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware";

            builder.Use(async (context, task) =>
            {
                var downstreamContext = new DownstreamContext(context);
                await firstDelegate.Invoke(downstreamContext);
            });

            return builder;
        }

可以看到里面 pipelineBuilder.BuildOcelotPipeline(pipelineConfiguration)這句代碼,這是Ocelot管道中間件的創建方法,我們要修改的就是這兩個地方,這個方法在OcelotPipelineExtensions.cs類文件里,點進去看一下:

 public static class OcelotPipelineExtensions
    {
        public static OcelotRequestDelegate BuildCustomeOcelotPipeline(this IOcelotPipelineBuilder builder,
            OcelotPipelineConfiguration pipelineConfiguration)
        {
            // This is registered to catch any global exceptions that are not handled
            // It also sets the Request Id if anything is set globally
            builder.UseExceptionHandlerMiddleware();

            // If the request is for websockets upgrade we fork into a different pipeline
            builder.MapWhen(context => context.HttpContext.WebSockets.IsWebSocketRequest,
                app =>
                {
                    app.UseDownstreamRouteFinderMiddleware();
                    app.UseDownstreamRequestInitialiser();
                    app.UseLoadBalancingMiddleware();
                    app.UseDownstreamUrlCreatorMiddleware();
                    app.UseWebSocketsProxyMiddleware();
                });

            // Allow the user to respond with absolutely anything they want.
            builder.UseIfNotNull(pipelineConfiguration.PreErrorResponderMiddleware);

            // This is registered first so it can catch any errors and issue an appropriate response
            builder.UseResponderMiddleware();

            // Then we get the downstream route information
            builder.UseDownstreamRouteFinderMiddleware();

            // This security module, IP whitelist blacklist, extended security mechanism
            builder.UseSecurityMiddleware();

            //Expand other branch pipes
            if (pipelineConfiguration.MapWhenOcelotPipeline != null)
            {
                foreach (var pipeline in pipelineConfiguration.MapWhenOcelotPipeline)
                {
                    builder.MapWhen(pipeline);
                }
            }

            // Now we have the ds route we can transform headers and stuff?
            builder.UseHttpHeadersTransformationMiddleware();

            // Initialises downstream request
            builder.UseDownstreamRequestInitialiser();

            // We check whether the request is ratelimit, and if there is no continue processing
            builder.UseRateLimiting();

            // This adds or updates the request id (initally we try and set this based on global config in the error handling middleware)
            // If anything was set at global level and we have a different setting at re route level the global stuff will be overwritten
            // This means you can get a scenario where you have a different request id from the first piece of middleware to the request id middleware.
            builder.UseRequestIdMiddleware();

            // Allow pre authentication logic. The idea being people might want to run something custom before what is built in.
            builder.UseIfNotNull(pipelineConfiguration.PreAuthenticationMiddleware);

            // Now we know where the client is going to go we can authenticate them.
            // We allow the ocelot middleware to be overriden by whatever the
            // user wants
            if (pipelineConfiguration.AuthenticationMiddleware == null)
            {
                builder.UseAuthenticationMiddleware();
            }
            else
            {
                builder.Use(pipelineConfiguration.AuthenticationMiddleware);
            }

            // The next thing we do is look at any claims transforms in case this is important for authorisation
            builder.UseClaimsToClaimsMiddleware();

            // Allow pre authorisation logic. The idea being people might want to run something custom before what is built in.
            builder.UseIfNotNull(pipelineConfiguration.PreAuthorisationMiddleware);

            // Now we have authenticated and done any claims transformation we
            // can authorise the request
            // We allow the ocelot middleware to be overriden by whatever the
            // user wants
            if (pipelineConfiguration.AuthorisationMiddleware == null)
            {
                builder.UseAuthorisationMiddleware();
            }
            else
            {
                builder.Use(pipelineConfiguration.AuthorisationMiddleware);
            }

            // Now we can run the claims to headers transformation middleware
            builder.UseClaimsToHeadersMiddleware();

            // Allow the user to implement their own query string manipulation logic
            builder.UseIfNotNull(pipelineConfiguration.PreQueryStringBuilderMiddleware);

            // Now we can run any claims to query string transformation middleware
            builder.UseClaimsToQueryStringMiddleware();

            // Get the load balancer for this request
            builder.UseLoadBalancingMiddleware();

            // This takes the downstream route we retrieved earlier and replaces any placeholders with the variables that should be used
            builder.UseDownstreamUrlCreatorMiddleware();

            // Not sure if this is the best place for this but we use the downstream url
            // as the basis for our cache key.
            builder.UseOutputCacheMiddleware();

            //We fire off the request and set the response on the scoped data repo
            builder.UseHttpRequesterMiddleware();

            //添加自定義測試中間件
         builder.UseTestResponseMiddleware();

            return builder.Build();
        }

        private static void UseIfNotNull(this IOcelotPipelineBuilder builder,
            Func<DownstreamContext, Func<Task>, Task> middleware)
        {
            if (middleware != null)
            {
                builder.Use(middleware);
            }
        }
    }

我們可以看到其實就是一個個的擴展方法的調用,也是我們所說的中間件,我在最后把剛才寫的中間件加了上去,就是標紅的那里,現在所有進入Ocelot的請求就都會走我們的中間件處理了,使用的時候需要將修改的OcelotPipelineExtensions.cs類文件放在自己項目里哦。

 

大功告成!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM