介绍
简单的来说ocelot是一堆的asp core middleware组成的一个管道。当它拿到请求之后会用一个request builder来构造一个httprequestmessage发到下游的真实服务器,等下游的服务返回response之后再由一个middleware将它返回的httpresponsemessage映射到httpresponse上。
api网关—— 它是系统的暴露在外部的一个访问入口。这个有点像代理访问的家伙,就像一个公司的门卫承担着寻址、限制进入、安全检查、位置引导、等等功能。
ocelot的基本使用
用一台web service来host ocelot,在这里有一个json配置文件,里面设置了所有对当前这个网关的配置。它会接收所有的客户端请求,并路由到对应的下游服务器进行处理,再将请求结果返回。而这个上下游请求的对应关系也被称之为路由。
集成identity server
当我们涉及到认证和鉴权的时候,我们可以跟identity server进行结合。当网关需要请求认证信息的时候会与identity server服务器进行交互来完成。
网关集群
只有一个网关是很危险的,也就是我们通常所讲的单点,只要它挂了,所有的服务全挂。这显然无法达到高可用,所以我们也可以部署多台网关。当然这个时候在多台网关前,你还需要一台负载均衡器。
consul 服务发现
在ocelot已经支持简单的负载功能,也就是当下游服务存在多个结点的时候,ocelot能够承担起负载均衡的作用。但是它不提供健康检查,服务的注册也只能通过手动在配置文件里面添加完成。这不够灵活并且在一定程度下会有风险。这个时候我们就可以用consul来做服务发现,它能与ocelot完美结合。
集成网关
在asp core 2.0里通过nuget即可完成集成,或者命令行dotnet add package ocelot以及通过vs2017 ui添加ocelot nuget引用都可以。
install-package ocelot
配置
我们需要添加一个.json的文件用来添加ocelot的配置,以下是最基本的配置信息。
要特别注意一下baseurl是我们外部暴露的url,比如我们的ocelot运行在http://123.111.1.1的一个地址上,但是前面有一个 nginx绑定了域名http://api.jessetalk,那这里我们的baseurl就是 http://api.jessetalk。
将配置文件加入asp.net core configuration
我们需要通过webhostbuilder将我们添加的json文件添加进asp core的配置
配置依赖注入与中间件
在startup.cs中我们首先需要引用两个命名空间
using ocelot.dependencyinjection;
using ocelot.middleware;
接下来就是添加依赖注入和中间件
ocelot功能介绍
通过配置文件可以完成对ocelot的功能配置:路由、服务聚合、服务发现、认证、鉴权、限流、熔断、缓存、header头传递等。在配置文件中包含两个根节点:reroutes和globalconfiguration。reroutes是一个数组,其中的每一个元素代表了一个路由,我们可以针对每一个路由进行以上功能配置。下面是一个完整的路由配置
downstream是下游服务配置
upstream是上游服务配置
aggregates 服务聚合配置
servicename, loadbalancer, useservicediscovery 配置服务发现
authenticationoptions 配置服务认证
routeclaimsrequirement 配置claims鉴权
ratelimitoptions为限流配置
filecacheoptions 缓存配置
qosoptions 服务质量与熔断
downstreamheadertransform头信息转发
我们接下来将对这些功能一一进行介绍和配置
路由
路由是api网关最基本也是最核心的功能、reroutes下就是由多个路由节点组成。
而每一个路由由以下几个基本信息组成:
下面这个配置信息就是将用户的请求 /post/1 转发到 localhost/api/post/1
downstreampathtemplate:下游戏
downstreamscheme:下游服务http schema
downstreamhostandports:下游服务的地址,如果使用loadbalancer的话这里可以填多项
upstreampathtemplate: 上游也就是用户输入的请求url模板
upstreamhttpmethod: 上游请求http方法,可使用数组
万能模板
万能模板即所有请求全部转发,upstreampathtemplate 与downstreampathtemplate 设置为 “/{url}”
万能模板的优先级最低,只要有其它的路由模板,其它的路由模板则会优先生效。
上游host
上游host也是路由用来判断的条件之一,由客户端访问时的host来进行区别。比如当a.jesetalk/users/{userid}和b.jessetalk/users/{userid}两个请求的时候可以进行区别对待。
prioirty优先级
对多个产生冲突的路由设置优化级
比如你有同样两个路由,当请求/goods/delete的时候,则下面那个会生效。也就是说prority是大的会被优先选择。
路由负载均衡
当下游服务有多个结点的时候,我们可以在downstreamhostandports中进行配置。
loadbalancer将决定负载均衡的算法
leastconnection – 将请求发往最空闲的那个服务器
roundrobin – 轮流发送
noloadbalance – 总是发往第一个请求或者是服务发现
在负载均衡这里,我们还可以和consul结合来使用服务发现,我们将在后面的小节中进行详述。
请求聚合
即将多个api请求结果合并为一个返回。要实现请求聚合我们需要给其它参与的路由起一个key。
当我们请求/的时候,会将/tom和/laura两个结果合并到一个response返回
{tom:{age: 19},laura:{age: 25}}
需要注意的是:
聚合服务目前只支持返回json
目前只支持get方式请求下游服务
任何下游的response header并会被丢弃
如果下游服务返回404,聚合服务只是这个key的value为空,它不会返回404
有一些其它的功能会在将来实现
下游服务很慢的处理
做一些像 graphql的处理对下游服务返回结果进行处理
404的处理
限流
对请求进行限流可以防止下游服务器因为访问过载而崩溃,这个功能就是我们的张善友张队进添加进去的。非常优雅的实现,我们只需要在路由下加一些简单的配置即可以完成。
clientwihtelist 白名单
enableratelimiting 是否启用限流
period 统计时间段:1s, 5m, 1h, 1d
peroidtimespan 多少秒之后客户端可以重试
limit 在统计时间段内允许的最大请求数量
在 globalconfiguration下我们还可以进行以下配置
http头 x-rate-limit 和 retry-after 是否禁用
quotaexceedmessage 当请求过载被截断时返回的消息
httpstatuscode 当请求过载被截断时返回的http status
clientidheader 用来识别客户端的请求头,默认是 clientid
服务质量与熔断
熔断的意思是停止将请求转发到下游服务。当下游服务已经出现故障的时候再请求也是功而返,并且增加下游服务器和api网关的负担。这个功能是用的pollly来实现的,我们只需要为路由做一些简单配置即可
exceptionsallowedbeforebreaking 允许多少个异常请求
durationofbreak 熔断的时间,单位为秒
timeoutvalue 如果下游请求的处理时间超过多少则自如将请求设置为超时
缓存
ocelot可以对下游请求结果进行缓存 ,目前缓存的功能还不是很强大。它主要是依赖于cachemanager 来实现的,我们只需要在路由下添加以下配置即可
filecacheoptions: { ttlseconds: 15, region: somename }
region是对缓存进行的一个分区,我们可以调用ocelot的 administration api来移除某个区下面的缓存 。
认证
如果我们需要对下游api进行认证以及鉴权服务的,则首先ocelot 网关这里需要添加认证服务。这和我们给一个单独的api或者asp.net core mvc添加认证服务没有什么区别。
然后在reroutes的路由模板中的authenticationoptions进行配置,只需要我们的authenticationproviderkey一致即可。
jwt tokens
要让网关支持jwt 的认证其实和让api支持jwt token的认证是一样的
identity server bearer tokens
添加identity server的认证也是一样
allowed scopes
这里的scopes将从当前 token 中的 claims中来获取,我们的鉴权服务将依靠于它来实现 。当前路由的下游api需要某个权限时,我们需要在这里声明 。和oauth2中的 scope意义一致。
鉴权
我们通过认证中的allowedscopes 拿到claims之后,如果要进行权限的鉴别需要添加以下配置
当前请求上下文的token中所带的claims如果没有 name=”usertype” 并且 value=”registered” 的话将无法访问下游服务。
请求头转化
请求头转发分两种:转化之后传给下游和从下游接收转化之后传给客户端。在ocelot的配置里面叫做pre downstream request和post downstream request。目前的转化只支持查找和替换。我们用到的配置主要是 upstreamheadertransform 和 downstreamheadertransform
pre downstream request
test: http://bbc.co.uk/, http://ocelot/
比如我们将客户端传过来的header中的 test 值改为 http://ocelot/之后再传给下游
upstreamheadertransform: {
test: http://bbc.co.uk/, http://ocelot/
},
post downstream request
而我们同样可以将下游header中的test再转为 http://bbc.co.uk/之后再转给客户端。
downstreamheadertransform: {
test: http://bbc.co.uk/, http://ocelot/
},
变量
在请求头转化这里ocelot为我们提供了两个变量:baseurl和downstreambaseurl。baseurl就是我们在globalconfiguration里面配置的baseurl,后者是下游服务的url。这里用301跳转做一个示例如何使用这两个变量。
默认的301跳转,我们会返回一个location的头,于是我们希望将http://bbc.co.uk 替换为 http://ocelot,后者者网关对外的域名。
downstreamheadertransform: {
location: http://bbc.co.uk/, http://ocelot/
},
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。
作者:jesse 出处: http://jesse2013blogs/