- Downstream是下游服務配置
- UpStream是上游服務配置
- Aggregates 服務聚合配置
- ServiceName, LoadBalancer, UseServiceDiscovery 配置服務發現
- AuthenticationOptions 配置服務認證
- RouteClaimsRequirement 配置Claims鑒權
- RateLimitOptions為限流配置
- FileCacheOptions 緩存配置
- QosOptions 服務質量與熔斷
- DownstreamHeaderTransform頭信息轉發
- DownstreamPathTemplate:下游戲
- DownstreamScheme:下游服務http schema
- DownstreamHostAndPorts:下游服務的地址,如果使用LoadBalancer的話這里可以填多項
- UpstreamPathTemplate: 上游也就是用戶輸入的請求Url模板
- UpstreamHttpMethod: 上游請求http方法,可使用數組
- Prioirty優先級
對多個產生沖突的路由設置優化級 -
路由負載均衡
當下游服務有多個結點的時候,我們可以在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 – 總是發往第一個請求或者是服務發現
限流
對請求進行限流可以防止下游服務器因為訪問過載而崩潰,這個功能就是我們的張善友張隊進添加進去的。非常優雅的實現,我們只需要在路由下加一些簡單的配置即可以完成。
"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 如果下游請求的處理時間超過多少則自如將請求設置為超時
認證
如果我們需要對下游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轉化功能可以將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 }