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類文件放在自己項目里哦。
大功告成!
