在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中使用自定义的中间件(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 十问十答 Apache 许可证
- 下一篇: .NET Core开发实战(第23课:静