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