Ocelot是一個用.NET Core實現而且開源的API網關,它功能強大,包括了:路由、請求聚合、服務發現、認證、鑑權、限流熔斷、並內置了負載均衡器與Service Fabric、Butterfly Tracing集成。這些功能只都只須要簡單的配置便可完成,下面咱們會對這些功能的配置一一進行說明。算法
在asp.net core 2.0裏經過nuget便可完成集成,或者命令行dotnet add package Ocelot以及經過vs2017 UI添加Ocelot nuget引用均可以。sql
Install-Package Ocelot
基本的配置信息。json
{
"ReRoutes": [], "GlobalConfiguration": { "BaseUrl": "https://api.mybusiness.com" } }
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中咱們首先須要引用兩個命名空間api
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(); }
當下遊服務有多個結點的時候,咱們能夠在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將決定負載均衡的算法服務器
對請求進行限流能夠防止下游服務器由於訪問過載而崩潰,這個功能就是咱們的張善友張隊進添加進去的。很是優雅的實現,咱們只須要在路由下加一些簡單的配置便可以完成。app
"RateLimitOptions": { "ClientWhitelist": [], "EnableRateLimiting": true, "Period": "1s", "PeriodTimespan": 1, "Limit": 1 }
在 GlobalConfiguration下咱們還能夠進行如下配置負載均衡
"RateLimitOptions": { "DisableRateLimitHeaders": false, "QuotaExceededMessage": "Customize Tips!", "HttpStatusCode": 999, "ClientIdHeader" : "Test" }
熔斷的意思是中止將請求轉發到下游服務。當下遊服務已經出現故障的時候再請求也是功而返,而且增長下游服務器和API網關的負擔。這個功能是用的Pollly來實現的,咱們只須要爲路由作一些簡單配置便可asp.net
"QoSOptions": { "ExceptionsAllowedBeforeBreaking":3, "DurationOfBreak":5, "TimeoutValue":5000 }
若是咱們須要對下游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。
NanoFabric 配置
1 { 2 "ReRoutes": [ 3 { 4 "DownstreamPathTemplate": "/api/values", 5 "DownstreamScheme": "http", 6 "UpstreamPathTemplate": "/api/values", 7 "UpstreamHttpMethod": [ "Get" ], 8 "ServiceName": "SampleService_Kestrel", 9 "LoadBalancerOptions": { 10 "Type": "LeastConnection" 11 }, 12 "UseServiceDiscovery": true, 13 "FileCacheOptions": { "TtlSeconds": 15 } 14 }, 15 { 16 "DownstreamPathTemplate": "/", 17 "DownstreamScheme": "http", 18 "DownstreamHostAndPorts": [ 19 { 20 "Host": "localhost", 21 "Port": 52876 22 } 23 ], 24 "UpstreamPathTemplate": "/identityserverexample", 25 "UpstreamHttpMethod": [ "Get" ], 26 "QoSOptions": { 27 "ExceptionsAllowedBeforeBreaking": 3, 28 "DurationOfBreak": 10, 29 "TimeoutValue": 5000 30 }, 31 "AuthenticationOptions": { 32 "AuthenticationProviderKey": "TestKey", 33 "AllowedScopes": [ 34 "openid", 35 "offline_access" 36 ] 37 }, 38 "AddHeadersToRequest": { 39 "CustomerId": "Claims[CustomerId] > value", 40 "LocationId": "Claims[LocationId] > value", 41 "UserType": "Claims[sub] > value[0] > |", 42 "UserId": "Claims[sub] > value[1] > |" 43 }, 44 "AddClaimsToRequest": { 45 "CustomerId": "Claims[CustomerId] > value", 46 "LocationId": "Claims[LocationId] > value", 47 "UserType": "Claims[sub] > value[0] > |", 48 "UserId": "Claims[sub] > value[1] > |" 49 }, 50 "AddQueriesToRequest": { 51 "CustomerId": "Claims[CustomerId] > value", 52 "LocationId": "Claims[LocationId] > value", 53 "UserType": "Claims[sub] > value[0] > |", 54 "UserId": "Claims[sub] > value[1] > |" 55 }, 56 "RouteClaimsRequirement": { 57 "UserType": "registered" 58 }, 59 "RequestIdKey": "OcRequestId" 60 }, 61 { 62 "DownstreamPathTemplate": "/posts", 63 "DownstreamScheme": "https", 64 "DownstreamHostAndPorts": [ 65 { 66 "Host": "jsonplaceholder.typicode.com", 67 "Port": 443 68 } 69 ], 70 "UpstreamPathTemplate": "/posts", 71 "UpstreamHttpMethod": [ "Get" ], 72 "QoSOptions": { 73 "ExceptionsAllowedBeforeBreaking": 3, 74 "DurationOfBreak": 10, 75 "TimeoutValue": 5000 76 } 77 }, 78 { 79 "DownstreamPathTemplate": "/posts/{postId}", 80 "DownstreamScheme": "http", 81 "DownstreamHostAndPorts": [ 82 { 83 "Host": "jsonplaceholder.typicode.com", 84 "Port": 80 85 } 86 ], 87 "UpstreamPathTemplate": "/posts/{postId}", 88 "UpstreamHttpMethod": [ "Get" ], 89 "RequestIdKey": "ReRouteRequestId", 90 "QoSOptions": { 91 "ExceptionsAllowedBeforeBreaking": 3, 92 "DurationOfBreak": 10, 93 "TimeoutValue": 5000 94 } 95 }, 96 { 97 "DownstreamPathTemplate": "/posts/{postId}/comments", 98 "DownstreamScheme": "http", 99 "DownstreamHostAndPorts": [ 100 { 101 "Host": "jsonplaceholder.typicode.com", 102 "Port": 80 103 } 104 ], 105 "UpstreamPathTemplate": "/posts/{postId}/comments", 106 "UpstreamHttpMethod": [ "Get" ], 107 "QoSOptions": { 108 "ExceptionsAllowedBeforeBreaking": 3, 109 "DurationOfBreak": 10, 110 "TimeoutValue": 5000 111 } 112 }, 113 { 114 "DownstreamPathTemplate": "/comments", 115 "DownstreamScheme": "http", 116 "DownstreamHostAndPorts": [ 117 { 118 "Host": "jsonplaceholder.typicode.com", 119 "Port": 80 120 } 121 ], 122 "UpstreamPathTemplate": "/comments", 123 "UpstreamHttpMethod": [ "Get" ], 124 "QoSOptions": { 125 "ExceptionsAllowedBeforeBreaking": 3, 126 "DurationOfBreak": 10, 127 "TimeoutValue": 5000 128 } 129 }, 130 { 131 "DownstreamPathTemplate": "/posts", 132 "DownstreamScheme": "http", 133 "DownstreamHostAndPorts": [ 134 { 135 "Host": "jsonplaceholder.typicode.com", 136 "Port": 80 137 } 138 ], 139 "UpstreamPathTemplate": "/posts", 140 "UpstreamHttpMethod": [ "Post" ], 141 "QoSOptions": { 142 "ExceptionsAllowedBeforeBreaking": 3, 143 "DurationOfBreak": 10, 144 "TimeoutValue": 5000 145 } 146 }, 147 { 148 "DownstreamPathTemplate": "/posts/{postId}", 149 "DownstreamScheme": "http", 150 "DownstreamHostAndPorts": [ 151 { 152 "Host": "jsonplaceholder.typicode.com", 153 "Port": 80 154 } 155 ], 156 "UpstreamPathTemplate": "/posts/{postId}", 157 "UpstreamHttpMethod": [ "Put" ], 158 "QoSOptions": { 159 "ExceptionsAllowedBeforeBreaking": 3, 160 "DurationOfBreak": 10, 161 "TimeoutValue": 5000 162 } 163 }, 164 { 165 "DownstreamPathTemplate": "/posts/{postId}", 166 "DownstreamScheme": "http", 167 "DownstreamHostAndPorts": [ 168 { 169 "Host": "jsonplaceholder.typicode.com", 170 "Port": 80 171 } 172 ], 173 "UpstreamPathTemplate": "/posts/{postId}", 174 "UpstreamHttpMethod": [ "Patch" ], 175 "QoSOptions": { 176 "ExceptionsAllowedBeforeBreaking": 3, 177 "DurationOfBreak": 10, 178 "TimeoutValue": 5000 179 } 180 }, 181 { 182 "DownstreamPathTemplate": "/posts/{postId}", 183 "DownstreamScheme": "http", 184 "DownstreamHostAndPorts": [ 185 { 186 "Host": "jsonplaceholder.typicode.com", 187 "Port": 80 188 } 189 ], 190 "UpstreamPathTemplate": "/posts/{postId}", 191 "UpstreamHttpMethod": [ "Delete" ], 192 "QoSOptions": { 193 "ExceptionsAllowedBeforeBreaking": 3, 194 "DurationOfBreak": 10, 195 "TimeoutValue": 5000 196 } 197 }, 198 { 199 "DownstreamPathTemplate": "/api/products", 200 "DownstreamScheme": "http", 201 "DownstreamHostAndPorts": [ 202 { 203 "Host": "jsonplaceholder.typicode.com", 204 "Port": 80 205 } 206 ], 207 "UpstreamPathTemplate": "/products", 208 "UpstreamHttpMethod": [ "Get" ], 209 "QoSOptions": { 210 "ExceptionsAllowedBeforeBreaking": 3, 211 "DurationOfBreak": 10, 212 "TimeoutValue": 5000 213 }, 214 "FileCacheOptions": { "TtlSeconds": 15 } 215 }, 216 { 217 "DownstreamPathTemplate": "/api/products/{productId}", 218 "DownstreamScheme": "http", 219 "DownstreamHostAndPorts": [ 220 { 221 "Host": "jsonplaceholder.typicode.com", 222 "Port": 80 223 } 224 ], 225 "UpstreamPathTemplate": "/products/{productId}", 226 "UpstreamHttpMethod": [ "Get" ], 227 "FileCacheOptions": { "TtlSeconds": 15 } 228 }, 229 { 230 "DownstreamPathTemplate": "/api/products", 231 "DownstreamScheme": "http", 232 "DownstreamHostAndPorts": [ 233 { 234 "Host": "jsonplaceholder.typicode.com", 235 "Port": 80 236 } 237 ], 238 "UpstreamPathTemplate": "/products", 239 "UpstreamHttpMethod": [ "Post" ], 240 "QoSOptions": { 241 "ExceptionsAllowedBeforeBreaking": 3, 242 "DurationOfBreak": 10, 243 "TimeoutValue": 5000 244 } 245 }, 246 { 247 "DownstreamPathTemplate": "/api/products/{productId}", 248 "DownstreamScheme": "http", 249 "DownstreamHostAndPorts": [ 250 { 251 "Host": "jsonplaceholder.typicode.com", 252 "Port": 80 253 } 254 ], 255 "UpstreamPathTemplate": "/products/{productId}", 256 "UpstreamHttpMethod": [ "Put" ], 257 "QoSOptions": { 258 "ExceptionsAllowedBeforeBreaking": 3, 259 "DurationOfBreak": 10, 260 "TimeoutValue": 5000 261 }, 262 "FileCacheOptions": { "TtlSeconds": 15 } 263 }, 264 { 265 "DownstreamPathTemplate": "/posts", 266 "DownstreamScheme": "http", 267 "DownstreamHostAndPorts": [ 268 { 269 "Host": "jsonplaceholder.typicode.com", 270 "Port": 80 271 } 272 ], 273 "UpstreamPathTemplate": "/posts/", 274 "UpstreamHttpMethod": [ "Get" ], 275 "QoSOptions": { 276 "ExceptionsAllowedBeforeBreaking": 3, 277 "DurationOfBreak": 10, 278 "TimeoutValue": 5000 279 }, 280 "FileCacheOptions": { "TtlSeconds": 15 } 281 }, 282 { 283 "DownstreamPathTemplate": "/", 284 "DownstreamScheme": "http", 285 "DownstreamHostAndPorts": [ 286 { 287 "Host": "www.bbc.co.uk", 288 "Port": 80 289 } 290 ], 291 "UpstreamPathTemplate": "/bbc/", 292 "UpstreamHttpMethod": [ "Get" ] 293 } 294 ], 295 "GlobalConfiguration": { 296 "RequestIdKey": "ot-traceid", 297 "BaseUrl": "http://localhost:8000", 298 "ServiceDiscoveryProvider": { 299 "Host": "localhost", 300 "Port": 8500 301 } 302 } 303 }