.NET Core开源API网关 – Ocelot中文文档
Ocelot是一個用.NET Core實現并且開源的API網關,它功能強大,包括了:路由、請求聚合、服務發現、認證、鑒權、限流熔斷、并內置了負載均衡器與Service Fabric、Butterfly Tracing集成。這些功能只都只需要簡單的配置即可完成,下面我們會對這些功能的配置一一進行說明。
介紹
簡單的來說Ocelot是一堆的asp.net core?middleware組成的一個管道。當它拿到請求之后會用一個request builder來構造一個HttpRequestMessage發到下游的真實服務器,等下游的服務返回response之后再由一個middleware將它返回的HttpResponseMessage映射到HttpResponse上。
API網關——?它是系統的暴露在外部的一個訪問入口。這個有點像代理訪問的家伙,就像一個公司的門衛承擔著尋址、限制進入、安全檢查、位置引導、等等功能。
Ocelot的基本使用
用一臺web service來host Ocelot,在這里有一個json配置文件,里面設置了所有對當前這個網關的配置。它會接收所有的客戶端請求,并路由到對應的下游服務器進行處理,再將請求結果返回。而這個上下游請求的對應關系也被稱之為路由。
集成Identity Server
當我們涉及到認證和鑒權的時候,我們可以跟Identity Server進行結合。當網關需要請求認證信息的時候會與Identity Server服務器進行交互來完成。
網關集群
只有一個網關是很危險的,也就是我們通常所講的單點,只要它掛了,所有的服務全掛。這顯然無法達到高可用,所以我們也可以部署多臺網關。當然這個時候在多臺網關前,你還需要一臺負載均衡器。
Consul?服務發現
在Ocelot已經支持簡單的負載功能,也就是當下游服務存在多個結點的時候,Ocelot能夠承擔起負載均衡的作用。但是它不提供健康檢查,服務的注冊也只能通過手動在配置文件里面添加完成。這不夠靈活并且在一定程度下會有風險。這個時候我們就可以用Consul來做服務發現,它能與Ocelot完美結合。
集成網關
在asp.net core 2.0里通過nuget即可完成集成,或者命令行dotnet add package Ocelot以及通過vs2017 UI添加Ocelot nuget引用都可以。
Install-Package Ocelot配置
我們需要添加一個.json的文件用來添加Ocelot的配置,以下是最基本的配置信息。
{"ReRoutes": [],"GlobalConfiguration": {"BaseUrl": "https://api.mybusiness.com"} }要特別注意一下BaseUrl是我們外部暴露的Url,比如我們的Ocelot運行在http://123.111.1.1的一個地址上,但是前面有一個?nginx綁定了域名http://api.jessetalk.cn,那這里我們的BaseUrl就是 http://api.jessetalk.cn。
將配置文件加入ASP.NET Core Configuration
我們需要通過WebHostBuilder將我們添加的json文件添加進asp.net core的配置
public static IWebHost BuildWebHost(string[] args) => ? ?WebHost.CreateDefaultBuilder(args) ? ? ? ?.ConfigureAppConfiguration( (hostingContext,builder) => { ? ? ? ? ? ?builder ? ? ? ? ? ?.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) ? ? ? ? ? ?.AddJsonFile("Ocelot.json"); ? ? ? ?}) ? ? ? ?.UseStartup<Startup>() ? ? ? ?.Build();?
配置依賴注入與中間件
在startup.cs中我們首先需要引用兩個命名空間
using Ocelot.DependencyInjection;using Ocelot.Middleware;接下來就是添加依賴注入和中間件
public void ConfigureServices(IServiceCollection services) { ? ?services.AddOcelot(); }public void Configure(IApplicationBuilder app, IHostingEnvironment env) { ? ?if (env.IsDevelopment()) ? ?{ ? ? ? ?app.UseDeveloperExceptionPage(); ? ?} ? ?app.UseOcelot().Wait(); }Ocelot功能介紹
通過配置文件可以完成對Ocelot的功能配置:路由、服務聚合、服務發現、認證、鑒權、限流、熔斷、緩存、Header頭傳遞等。在配置文件中包含兩個根節點:ReRoutes和GlobalConfiguration。ReRoutes是一個數組,其中的每一個元素代表了一個路由,我們可以針對每一個路由進行以上功能配置。下面是一個完整的路由配置
{"DownstreamPathTemplate": "/","UpstreamPathTemplate": "/","UpstreamHttpMethod": [ ? ? ? ? ? ? ?"Get" ? ? ? ? ?], ? ? ? ? ?"AddHeadersToRequest": {}, ? ? ? ? ?"AddClaimsToRequest": {}, ? ? ? ? ?"RouteClaimsRequirement": {}, ? ? ? ? ?"AddQueriesToRequest": {}, ? ? ? ? ?"RequestIdKey": "", ? ? ? ? ?"FileCacheOptions": { ? ? ? ? ? ? ?"TtlSeconds": 0, ? ? ? ? ? ? ?"Region": "" ? ? ? ? ?}, ? ? ? ? ?"ReRouteIsCaseSensitive": false, ? ? ? ? ?"ServiceName": "", ? ? ? ? ?"DownstreamScheme": "http", ? ? ? ? ?"DownstreamHostAndPorts": [ ? ? ? ? ? ? ?{ ? ? ? ? ? ? ? ? ?"Host": "localhost", ? ? ? ? ? ? ? ? ?"Port": 51876, ? ? ? ? ? ? ?} ? ? ? ? ?], ? ? ? ? ?"QoSOptions": { ? ? ? ? ? ? ?"ExceptionsAllowedBeforeBreaking": 0, ? ? ? ? ? ? ?"DurationOfBreak": 0, ? ? ? ? ? ? ?"TimeoutValue": 0 ? ? ? ? ?}, ? ? ? ? ?"LoadBalancer": "", ? ? ? ? ?"RateLimitOptions": { ? ? ? ? ? ? ?"ClientWhitelist": [], ? ? ? ? ? ? ?"EnableRateLimiting": false, ? ? ? ? ? ? ?"Period": "", ? ? ? ? ? ? ?"PeriodTimespan": 0, ? ? ? ? ? ? ?"Limit": 0 ? ? ? ? ?}, ? ? ? ? ?"AuthenticationOptions": { ? ? ? ? ? ? ?"AuthenticationProviderKey": "", ? ? ? ? ? ? ?"AllowedScopes": [] ? ? ? ? ?}, ? ? ? ? ?"HttpHandlerOptions": { ? ? ? ? ? ? ?"AllowAutoRedirect": true, ? ? ? ? ? ? ?"UseCookieContainer": true, ? ? ? ? ? ? ?"UseTracing": true ? ? ? ? ?}, ? ? ? ? ?"UseServiceDiscovery": false ? ? ?}Downstream是下游服務配置
UpStream是上游服務配置
Aggregates?服務聚合配置
ServiceName, LoadBalancer, UseServiceDiscovery?配置服務發現
AuthenticationOptions?配置服務認證
RouteClaimsRequirement?配置Claims鑒權
RateLimitOptions為限流配置
FileCacheOptions?緩存配置
QosOptions?服務質量與熔斷
DownstreamHeaderTransform頭信息轉發
我們接下來將對這些功能一一進行介紹和配置
路由
路由是API網關最基本也是最核心的功能、ReRoutes下就是由多個路由節點組成。
{"ReRoutes": [] }而每一個路由由以下幾個基本信息組成:
下面這個配置信息就是將用戶的請求 /post/1?轉發到 localhost/api/post/1
{"DownstreamPathTemplate": "/api/post/{postId}","DownstreamScheme": "https","DownstreamHostAndPorts": [ ? ? ? ? ? ?{ ? ? ? ? ? ? ? ?"Host": "localhost", ? ? ? ? ? ? ? ?"Port": 80, ? ? ? ? ? ?} ? ? ? ?], ? ?"UpstreamPathTemplate": "/post/{postId}", ? ?"UpstreamHttpMethod": [ "Get"] }DownstreamPathTemplate:下游戲
DownstreamScheme:下游服務http schema
DownstreamHostAndPorts:下游服務的地址,如果使用LoadBalancer的話這里可以填多項
UpstreamPathTemplate:?上游也就是用戶輸入的請求Url模板
UpstreamHttpMethod:?上游請求http方法,可使用數組
萬能模板
萬能模板即所有請求全部轉發,UpstreamPathTemplate?與DownstreamPathTemplate?設置為 “/{url}”
{"DownstreamPathTemplate": "/{url}","DownstreamScheme": "https","DownstreamHostAndPorts": [ ? ? ? ? ? ?{ ? ? ? ? ? ? ? ?"Host": "localhost", ? ? ? ? ? ? ? ?"Port": 80, ? ? ? ? ? ?} ? ? ? ?], ? ?"UpstreamPathTemplate": "/{url}", ? ?"UpstreamHttpMethod": [ "Get" ] }萬能模板的優先級最低,只要有其它的路由模板,其它的路由模板則會優先生效。
上游Host
上游Host也是路由用來判斷的條件之一,由客戶端訪問時的Host來進行區別。比如當a.jesetalk.cn/users/{userid}和b.jessetalk.cn/users/{userid}兩個請求的時候可以進行區別對待。
Prioirty優先級
對多個產生沖突的路由設置優化級
比如你有同樣兩個路由,當請求/goods/delete的時候,則下面那個會生效。也就是說Prority是大的會被優先選擇。
路由負載均衡
當下游服務有多個結點的時候,我們可以在DownstreamHostAndPorts中進行配置。
{"DownstreamPathTemplate": "/api/posts/{postId}","DownstreamScheme": "https","DownstreamHostAndPorts": [ ? ? ? ? ? ?{ ? ? ? ? ? ? ? ?"Host": "10.0.1.10", ? ? ? ? ? ? ? ?"Port": 5000, ? ? ? ? ? ?}, ? ? ? ? ? ?{ ? ? ? ? ? ? ? ?"Host": "10.0.1.11", ? ? ? ? ? ? ? ?"Port": 5000, ? ? ? ? ? ?} ? ? ? ?], ? ?"UpstreamPathTemplate": "/posts/{postId}", ? ?"LoadBalancer": "LeastConnection", ? ?"UpstreamHttpMethod": [ "Put", "Delete" ] }LoadBalancer將決定負載均衡的算法
LeastConnection –?將請求發往最空閑的那個服務器
RoundRobin –?輪流發送
NoLoadBalance –?總是發往第一個請求或者是服務發現
在負載均衡這里,我們還可以和Consul結合來使用服務發現,我們將在后面的小節中進行詳述。
請求聚合
即將多個API請求結果合并為一個返回。要實現請求聚合我們需要給其它參與的路由起一個Key。
{"ReRoutes": [{"DownstreamPathTemplate": "/","UpstreamPathTemplate": "/laura", ? ? ? ? ? ?"UpstreamHttpMethod": [ ? ? ? ? ? ? ? ?"Get" ? ? ? ? ? ?], ? ? ? ? ? ?"DownstreamScheme": "http", ? ? ? ? ? ?"DownstreamHostAndPorts": [ ? ? ? ? ? ? ? ?{ ? ? ? ? ? ? ? ? ? ?"Host": "localhost", ? ? ? ? ? ? ? ? ? ?"Port": 51881 ? ? ? ? ? ? ? ?} ? ? ? ? ? ?], ? ? ? ? ? ?"Key": "Laura" ? ? ? ?}, ? ? ? ?{ ? ? ? ? ? ?"DownstreamPathTemplate": "/", ? ? ? ? ? ?"UpstreamPathTemplate": "/tom", ? ? ? ? ? ?"UpstreamHttpMethod": [ ? ? ? ? ? ? ? ?"Get" ? ? ? ? ? ?], ? ? ? ? ? ?"DownstreamScheme": "http", ? ? ? ? ? ?"DownstreamHostAndPorts": [ ? ? ? ? ? ? ? ?{ ? ? ? ? ? ? ? ? ? ?"Host": "localhost", ? ? ? ? ? ? ? ? ? ?"Port": 51882 ? ? ? ? ? ? ? ?} ? ? ? ? ? ?], ? ? ? ? ? ?"Key": "Tom" ? ? ? ?} ? ?], ? ?"Aggregates": [ ? ? ? ?{ ? ? ? ? ? ?"ReRouteKeys": [ ? ? ? ? ? ? ? ?"Tom", ? ? ? ? ? ? ? ?"Laura" ? ? ? ? ? ?], ? ? ? ? ? ?"UpstreamPathTemplate": "/" ? ? ? ?} ? ?] }當我們請求/的時候,會將/tom和/laura兩個結果合并到一個response返回
{"Tom":{"Age": 19},"Laura":{"Age": 25}}需要注意的是:
聚合服務目前只支持返回json
目前只支持Get方式請求下游服務
任何下游的response header并會被丟棄
如果下游服務返回404,聚合服務只是這個key的value為空,它不會返回404
有一些其它的功能會在將來實現
下游服務很慢的處理
做一些像 GraphQL的處理對下游服務返回結果進行處理
404的處理
限流
對請求進行限流可以防止下游服務器因為訪問過載而崩潰,這個功能就是我們的張善友張隊進添加進去的。非常優雅的實現,我們只需要在路由下加一些簡單的配置即可以完成。
"RateLimitOptions": { ? ?"ClientWhitelist": [], ? ?"EnableRateLimiting": true, ? ?"Period": "1s", ? ?"PeriodTimespan": 1, ? ?"Limit": 1 }ClientWihteList?白名單
EnableRateLimiting?是否啟用限流
Period?統計時間段:1s, 5m, 1h, 1d
PeroidTimeSpan?多少秒之后客戶端可以重試
Limit?在統計時間段內允許的最大請求數量
在 GlobalConfiguration下我們還可以進行以下配置
"RateLimitOptions": { ?"DisableRateLimitHeaders": false, ?"QuotaExceededMessage": "Customize Tips!", ?"HttpStatusCode": 999, ?"ClientIdHeader" : "Test" }Http頭 ?X-Rate-Limit?和 Retry-After?是否禁用
QuotaExceedMessage?當請求過載被截斷時返回的消息
HttpStatusCode?當請求過載被截斷時返回的http status
ClientIdHeader?用來識別客戶端的請求頭,默認是 ClientId
服務質量與熔斷
熔斷的意思是停止將請求轉發到下游服務。當下游服務已經出現故障的時候再請求也是功而返,并且增加下游服務器和API網關的負擔。這個功能是用的Pollly來實現的,我們只需要為路由做一些簡單配置即可
"QoSOptions": { ? ?"ExceptionsAllowedBeforeBreaking":3, ? ?"DurationOfBreak":5, ? ?"TimeoutValue":5000 }ExceptionsAllowedBeforeBreaking?允許多少個異常請求
DurationOfBreak?熔斷的時間,單位為秒
TimeoutValue?如果下游請求的處理時間超過多少則自如將請求設置為超時
緩存
Ocelot可以對下游請求結果進行緩存 ,目前緩存的功能還不是很強大。它主要是依賴于CacheManager?來實現的,我們只需要在路由下添加以下配置即可
"FileCacheOptions": { "TtlSeconds": 15, "Region": "somename" }Region是對緩存進行的一個分區,我們可以調用Ocelot的 administration API來移除某個區下面的緩存 。
認證
如果我們需要對下游API進行認證以及鑒權服務的,則首先Ocelot?網關這里需要添加認證服務。這和我們給一個單獨的API或者ASP.NET Core Mvc添加認證服務沒有什么區別。
public void ConfigureServices(IServiceCollection services) { ? ?var authenticationProviderKey = "TestKey"; ? ?services.AddAuthentication() ? ? ? ?.AddJwtBearer(authenticationProviderKey, x => ? ? ? ?{ ? ? ? ?}); }然后在ReRoutes的路由模板中的AuthenticationOptions進行配置,只需要我們的AuthenticationProviderKey一致即可。
"ReRoutes": [{ ? ? ? ?"DownstreamHostAndPorts": [{ ? ? ? ? ? ? ? ?"Host": "localhost", ? ? ? ? ? ? ? ?"Port": 51876, ? ? ? ? ? ?} ? ? ? ?], ? ? ? ?"DownstreamPathTemplate": "/", ? ? ? ?"UpstreamPathTemplate": "/", ? ? ? ?"UpstreamHttpMethod": ["Post"], ? ? ? ?"ReRouteIsCaseSensitive": false, ? ? ? ?"DownstreamScheme": "http", ? ? ? ?"AuthenticationOptions": { ? ? ? ? ? ?"AuthenticationProviderKey": "TestKey", ? ? ? ? ? ?"AllowedScopes": [] ? ? ? ?} ? ?}]JWT Tokens
要讓網關支持JWT?的認證其實和讓API支持JWT? Token的認證是一樣的
public void ConfigureServices(IServiceCollection services) { ? ?var authenticationProviderKey = "TestKey"; ? ?services.AddAuthentication() ? ? ? ?.AddJwtBearer(authenticationProviderKey, x => ? ? ? ?{ ? ? ? ? ? ?x.Authority = "test"; ? ? ? ? ? ?x.Audience = "test"; ? ? ? ?}); ? ?services.AddOcelot(); }Identity Server Bearer Tokens
添加Identity Server的認證也是一樣
public void ConfigureServices(IServiceCollection services) { ? ?var authenticationProviderKey = "TestKey"; ? ?var options = o => ? ? ? ?{ ? ? ? ? ? ?o.Authority = "https://whereyouridentityserverlives.com"; ? ? ? ? ? ?o.ApiName = "api"; ? ? ? ? ? ?o.SupportedTokens = SupportedTokens.Both; ? ? ? ? ? ?o.ApiSecret = "secret"; ? ? ? ?}; ? ?services.AddAuthentication() ? ? ? ?.AddIdentityServerAuthentication(authenticationProviderKey, options); ? ?services.AddOcelot(); }Allowed Scopes
這里的Scopes將從當前?token?中的 claims中來獲取,我們的鑒權服務將依靠于它來實現 。當前路由的下游API需要某個權限時,我們需要在這里聲明 。和oAuth2中的 scope意義一致。
鑒權
我們通過認證中的AllowedScopes?拿到claims之后,如果要進行權限的鑒別需要添加以下配置
"RouteClaimsRequirement": { ? ?"UserType": "registered" }當前請求上下文的token中所帶的claims如果沒有?name=”UserType”?并且 value=”registered”?的話將無法訪問下游服務。
請求頭轉化
請求頭轉發分兩種:轉化之后傳給下游和從下游接收轉化之后傳給客戶端。在Ocelot的配置里面叫做Pre? Downstream Request和Post Downstream Request。目前的轉化只支持查找和替換。我們用到的配置主要是 UpstreamHeaderTransform?和 DownstreamHeaderTransform
Pre Downstream Request
"Test": "http://www.bbc.co.uk/, http://ocelot.com/"比如我們將客戶端傳過來的Header中的 Test?值改為 http://ocelot.com/之后再傳給下游
"UpstreamHeaderTransform": { ? ?"Test": "http://www.bbc.co.uk/, http://ocelot.com/" },Post Downstream Request
而我們同樣可以將下游Header中的Test再轉為 http://www.bbc.co.uk/之后再轉給客戶端。
"DownstreamHeaderTransform": { ? ?"Test": "http://www.bbc.co.uk/, http://ocelot.com/" },變量
在請求頭轉化這里Ocelot為我們提供了兩個變量:BaseUrl和DownstreamBaseUrl。BaseUrl就是我們在GlobalConfiguration里面配置的BaseUrl,后者是下游服務的Url。這里用301跳轉做一個示例如何使用這兩個變量。
默認的301跳轉,我們會返回一個Location的頭,于是我們希望將http://www.bbc.co.uk?替換為 http://ocelot.com,后者者網關對外的域名。
"DownstreamHeaderTransform": { ? ?"Location": "http://www.bbc.co.uk/, http://ocelot.com/" }, "HttpHandlerOptions": { ? ?"AllowAutoRedirect": false, },我們通過DownstreamHeaderTranfrom將下游返回的請求頭中的Location替換為了網關的域名,而不是下游服務的域名。所以在這里我們也可以使用BaseUrl來做為變量替換。
"DownstreamHeaderTransform": { ? ?"Location": "http://localhost:6773, {BaseUrl}" }, "HttpHandlerOptions": { ? ?"AllowAutoRedirect": false, },當我們的下游服務有多個的時候,我們就沒有辦法找到前面的那個http://localhost:6773,因為它可能是多個值。所以這里我們可以使用DownstreamBaseUrl。
"DownstreamHeaderTransform": { ? ?"Location": "{DownstreamBaseUrl}, {BaseUrl}" }, "HttpHandlerOptions": { ? ?"AllowAutoRedirect": false, },Claims轉化
Claims轉化功能可以將Claims中的值轉化到請求頭、Query String、或者下游的Claims中,對于Claims的轉化,比較特殊的一點是它提供了一種對字符串進行解析的方法。舉個例子,比如我們有一個sub的claim。這個claims的 name=”sub” value=”usertypevalue|useridvalue”,實際上我們不會弄這么復雜的value,它是拼接來的,但是我們為了演示這個字符串解析的功能,所以使用了這么一個復雜的value。
Ocelot為我們提供的功能分為三段,第一段是Claims[sub],很好理解[]?里面是我們的claim的名稱。第二段是? >?表示對字符串進行拆分,?后面跟著拆分完之后我們要取的那個數組里面的某一個元素用 value[index]來表示,取第0位元素也可以直接用value。第三段也是以 > 開頭后面跟著我們的分隔符,在我們上面的例子分隔符是 |
所以在這里如果我們要取? usertype這個claim就會這樣寫: Claims[sub] > value[0] > |
Claim取到之后我們如果要放到請求頭、QueryString、以及Claim當中對應有以下三個配置。
Claims to Claims?
"AddClaimsToRequest": { ? ?"UserType": "Claims[sub] > value[0] > |", ? ?"UserId": "Claims[sub] > value[1] > |" }Claims to Headers?
"AddHeadersToRequest": { ? ?"CustomerId": "Claims[sub] > value[1] > |" }這里我們還是用的上面那個 sub = usertypevalue|useridvalue?的claim來進行處理和轉化。
Claims to Query String
"AddQueriesToRequest": { ? ?"LocationId": "Claims[LocationId] > value", }這里沒有進行分隔,所以直接取了value。
Consul服務發現
由于Consul服務發現更多的是Consul的安裝、配置、以及使用,所以本小節內容將由另一篇文章來進行詳細介紹,歡迎關注。
相關文章:
Ocelot——初識基于.Net Core的API網關
Ocelot API網關的實現剖析
微服務網關Ocelot
API網關Ocelot 使用Polly 處理部分失敗問題
談談微服務中的 API 網關(API Gateway)
Ocelot網關
Ocelot統一權限驗證
應用監控怎么做?
ASP.NET Core之跨平臺的實時性能監控
.Net Core 2.0+ InfluxDB+Grafana+App Metrics 實現跨平臺的實時性能監控
應用程序的8個關鍵性能指標以及測量方法
使用Metrics監控應用程序的性能
下一個計劃 : .NET/.NET Core應用性能管理
Ocelot監控
Ocelot 集成Butterfly 實現分布式跟蹤
Ocelot中使用Butterfly實踐
Ocelot + Consul實踐
原文地址:http://www.jessetalk.cn/2018/03/19/net-core-apigateway-ocelot-docs/
.NET社區新聞,深度好文,歡迎訪問公眾號文章匯總 http://www.csharpkit.com
總結
以上是生活随笔為你收集整理的.NET Core开源API网关 – Ocelot中文文档的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: RabbitMQ教程C#版 “Hello
- 下一篇: ABP前端使用阿里云angular2 U