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