- 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 }

