资讯专栏INFORMATION COLUMN

dgate:an API Gateway based on Vert.x

dingding199389 / 1788人阅读

摘要:请注意闭包的返回值必需是。开发者可以利用相关方法来自定义其内容,会将闭包的返回值作为最终结果与其他后端服务的响应合并,然后返回给访问层。注意不要忘记本身是一个闭包否则,无法模拟过期后重新生成另一个的情况。

dgate:an API Gateway based on Vert.x

dgate是基于Vertx的API Gateway。运行dgate的命令如下:

java -jar dgate-version-fat.jar -Dconf=conf

其中的conf属性用来指定运行所需的配置文件。

conf的文件格式

在说明格式之前,先看一个例子:

apiGateway1 {
    port = 7000
    login = "/login"
    urls {
        "/login" {
            required = ["sub", "password"]
            methods = [HttpMethod.GET, HttpMethod.POST]
            upstreamURLs = [
                [
                    host: "localhost", port: 8080, url: "/login",
                    after: { simpleResponse ->
                        Map payload = [
                            sub: simpleResponse.payload.getString("sub"),
                            name: simpleResponse.payload.getString("name"),
                            role: simpleResponse.payload.getString("role")
                        ]
                        simpleResponse.payload.put("token", delegate.tokenGenerator.token(payload, 5))
                        simpleResponse
                    }
                ]
            ]
        }
        "/summary" {
            expected {
                statusCode = 200
                payload {
                    eqLocations = []
                    opRateInLast30Days = []
                    myOrgs = [
                        ["name": "org1", "admin": false]
                    ]
                }
            }
        }
        "/forward" {
            required = ["param1", "param2"]
            methods = ["GET", "POST"]
            upstreamURLs = [
                [host: "localhost", port: 8080, url: "/test"]
            ]
        }
        "/composite" {
            required = ["param1", "param2"]
            methods = ["GET", "POST"]
            upstreamURLs = [
                [host: "localhost", port: 8080, url: "/test1"],
                [host: "localhost", port: 8080, url: "/test2"]
            ]
        }
    }
}
apiGateway2 {
    port = 7001
    host = "localhost"
    urls {
        "/mock" {
            expected {
                statusCode = 200
                payload = [test: true]
            }
        }
    }
}

dgate采用ConfigSlurper解析conf文件,因此其文件的语法实际上是Groovy语法。

conf文件由多个Api Gateway的定义组成,对于每个Gateway定义如下:

apiGatewayName {
    port //端口
    host //绑定的ip或主机名,默认0.0.0.0
    login //后端login服务的配置
    urls { URL配置(UrlConfig)列表 } //dgate暴露的url列表
}

对于每个URL配置,其结构如下:

url {
    required //必需参数列表
    methods  //支持的HTTP Method
    upstreamURLs { 上游URL列表(UpStreamURL) }
    expected //期望返回值
}

其中:

required支持两种格式:List和Map。前者适用于不区分HttpMethod时的参数验证,后者则可以针对不同的HttpMethod分别进行设置。

List,如:required = ["sub", "password"]

Map,如:required = [get: ["param1"], post: ["param2"], delete: ["param3"]]

expected和upStreamURLs两个属性不能并存

expected主要用于mock模式,其目的是为了便于依赖dgate的访问层可以自行mock所需的响应,使得它们的开发进度受dgate开发进度的影响最小化。

对于expected的内容:

statusCode和payload至少有一个

或针对具体的HTTP METHOD的返回值

对于每个上游URL,有3个属性:host、port和url,其中url必需以"/"开始

不同类型的API Gateway请求

API Gateway的目的是作为后端Service的一个统一集中点,简化访问层与后端Service的交互。说得更简单点,其作用非常类似Facade。

因此,API Gateway的主要作用就是:

接受访问层的请求

将请求转发到对应的后端服务

收集后端响应,统一组装成response,然后返回给访问层

从大的方面讲,发给API Gateway的请求分成两类:

转发给一个上游URL,即上面的forward

转发给多个上游URL,即上面的Composite

这便是为何upStreamURLs是一个列表的主要原因。对于每种请求:

forward

访问层的request parameter直接转发给后端service

后端service的response直接转发给访问层

composite

访问层的request parameter会直接(并发)转发给每个后端服务

后端服务的response会merge在一起后再发给访问层

对于每个发往后端的请求,dgate会采用Circuit Breaker来防护,防止出现由于某个后端service的失效导致整个系统雪崩。同时,为了简单起见,dgate和后端service之间只通过JSON进行交互:

对于发往dgate的请求,dgate会将:request parameters、form变量、request body合并为一体,统一作为request body发往后端

对于后端响应,dgate只接受json格式

UpStreamURL的扩展点

如上节所说,API Gateway具备两个职责:转发请求和转发响应。由于每个后端服务所需的request参数和产生的response不同,在这两个阶段,都需要对访问层传来的request和后端服务传回的response进行定制:

需要将访问层的request适配成后端service所需的request,这就面临着一些格式转换,如参数改名、增减对应的参数等。

需要对后端服务的response适配成访问层所需的response,采用类似的格式转换的动作。

针对这个需求,每个UpStreamURL都有两个可选属性可以利用。

before闭包
upstreamURLs = [
    [
        host: "localhost", port: 8080, url: "/test",
        before: { params -> ... }
    ]
]

before闭包的参数为访问层所发来的request parameters,其类型是一个JsonObject。开发者可以通过调用相关的方法来对其内容进行增减,dgate会将before闭包的返回值作为参数发给对应的上游URL。故,在此处非常适合将多余的request params过滤掉。

请注意:before闭包的返回值必需是JsonObject。最简单的before闭包如下:

before: { params -> params }
after闭包
upstreamURLs = [
    [
        host: "localhost", port: 8080, url: "/test",
        after: { simpleResponse -> ... }
    ]
]

after闭包的参数为后端服务所发来的response,其类型是SimpleResponse。开发者可以利用相关方法来自定义其内容,dgate会将after闭包的返回值作为最终结果与其他后端服务的响应合并,然后返回给访问层。故,此处适合对响应进行自定义。

请注意:after闭包的返回值必需是SimpleResponse。最简单的after闭包如下:

after: { simpleResponse -> simpleResponse }
给before/after闭包传入工具类

由于Groovy闭包的特性,我们可以给before/after传入工具类实例,供开发者使用。典型的例子如下:

"/login" {
    required = ["sub", "password"]
    methods = [HttpMethod.GET, HttpMethod.POST]
    upstreamURLs = [
        [
            host: "localhost", port: 8080, url: "/login",
            after: { simpleResponse ->
                simpleResponse.put(tokenGenerator.token(["sub": "13572209183", "name": "foxgem",
                                                         "role": "normal"], 2))
                simpleResponse
            }
        ]
    ]
}

其中的tokenGenerator就是利用这个特性完成的,具体实现可以参见LoginHandler。注意,上述代码中的2表示token的超时时间,单位为秒。

url path parameters

UpStreamURL除了支持一般的url格式,还支持url path parameters,格式如下:

"/:x"

"/y/:x?"

"/:x?/test/:y?"

所有的参数以":"开始,以"?"结束的为可选参数,规定如下:

当url中有且仅有一个可选参数时,它必需为最后一个参数

当url中有多个可选参数时,则只能从后向前不出现在url中,即:对于/:x?/:y?/:z?

/x/y/z,合法

/x/y,合法

/x,合法

/x/z,非法,它将解析对应成:/x/y

Mock请求

最简单的mock请求的书写如下:

"/mock" {
    expected {
        statusCode = 200
        payload = [test: true]
    }
}

若statusCode或payload本身是一个闭包,则dgate会将它们的返回值作为结果返回,这在excepted为一个动态值时非常有用,参见下面【Mock JWT Token】的例子。

上面的形式对于任何Http Method不加区分地返回同一个内容。但在Restful URL中,往往会有一个URL要为不同的Http Method服务,执行不同动作(如CRUD),返回不同响应体的情形。在dgate中可以以如下形式书写:

"/mock" {
    expected {
        get {
            statusCode = 200
            payload = [method: "get"]
        }
        post {
            statusCode = 200
            payload = [method: "post"]
        }
        delete {
            statusCode = 200
            payload = [method: "delete"]
        }
    }
}

即对于每一种HTTP method,都有各自响应体,每个响应体的格式都是由statusCode和payload组成。

安全

dgate支持JWT Token来认证每个访问层请求,当配置中出现login时,安全机制即被触发。

Login的配置

若conf中没有login配置,则所有url都是公共可访问的。

若要对所有url都要求先登录,则配置如下:

login = "/login"

若要忽略某些url,则配置如下:

login {
    url = "/login"
    ignore = [ 被忽略的url ]
}

若只有部分url是需要访问控制的,则配置如下:

login {
    url = "/login"
    only = [ 被控制的url ]
}

注意:ignore和only不能同时存在。

JWT Token

dgate使用JWT Token来认证每个访问层请求,故每个请求必需先请求获得jwt token,然后将每个token放入后续每个request的"Authorization"头,这样每个后续的request才会被认为是有效请求,否则将返回401。

如何产生jwt token的职责由开发者来完成,它必需放入login配置部分,上面的例子给出了一个参考实现。

对于发往后端服务的每个请求,dgate会将前端请求Authorization头中的jwt token附到请求的参数内,可以通过token来获取,此时它已经被解码成一个JSON对象。

产生JWT Token的密钥由下面的三个环境变量决定,故一旦配置中包含login,则需要在启动dgate之前先设置这3个环境变量:

dgate_key_store,keystore文件路径

dgate_key_type,keystore文件类型

dgate_key_password,keystore的密钥,至少6位

下面是一个配置的例子

export dgate_key_store=./test1.jceks
export dgate_key_type=jceks
export dgate_key_password=test123

使用keytool来产生文件,例子如下:

keytool -genseckey -keystore test1.jceks -storetype jceks -storepass test123 -keyalg HMacSHA256 -keysize 2048 -alias HS256 -keypass test123

由于dgate利用Vert.x的JWTAuthProvider产生JWT Token,因此其密钥文件为符合其要求的文件格式。关于密钥和如何产生密钥文件的命令,可以参见其文档。

注意:

在产品环境中请安全地保管好这个密钥文件!

在开发环境可以使用dgate自带的测试密钥文件:dgate,密码为“dcloud”。请注意不要将其用于产品环境,否则为伪造证书提供了便利。

Mock JWT Token

对于使用Mock功能来进行开发的访问层,有时会需要mock login来获得mock的jwt token以便开发相应的功能。可以参考下面的代码:

import io.vertx.core.http.HttpMethod
import io.vertx.core.Vertx
import io.vertx.ext.auth.jwt.JWTAuth
import top.dteam.dgate.utils.*

apiGateway {
    port = 7000
    login = "/login"
    urls {
        "/login" {
            required = ["sub", "password"]
            methods = [HttpMethod.GET, HttpMethod.POST]
            expected {
                statusCode = 200
                payload = {
                    JWTAuth jwtAuth = Utils.createAuthProvider(Vertx.vertx())
                    JWTTokenGenerator tokenGenerator = new JWTTokenGenerator(jwtAuth)
                    [token: tokenGenerator.token(["sub": "13572209183", "name": "foxgem", "role": "normal"], 200)]
                }
            }
        }
    ……
}

既然login指向的是dgate暴露的url,那么当然也就是可以直接mock啦。

注意:不要忘记payload本身是一个闭包!否则,无法模拟JWT Token过期后重新生成另一个Token的情况。

刷新JWT Token

dgate支持JWT Token的刷新,刷新用的url为:/token-refresh。它会从Request Header中取出JWT,解码,然后重新生成新的JWT。即,刷新JWT Token的前提是必须先获得dgate的JWT Token。

刷新JWT Token的配置由login配置块决定。当login是一个url时,则使用缺省的刷新属性。刷新属性(单位:秒)包括:

refreshLimit,刷新时限,即对于一个超时的JWT Token,若距离当前时间小于这个值,则允许刷新。否则返回401。

refreshExpire,新生成的JWT Token的时限,一旦超过,则对应的JWT Token失效。

若要自定义这两个值,则可按照如下定义:

login {
    ……
    refreshLimit = 30 * 60
    refreshExpire = 30 * 60
    ……
}

若不指定,则使用缺省值。这两个值的缺省值都为:30分钟,即:30 * 60(秒)。

CORS支持

对于某些情况下,需要打开CORS支持才能让访问层正常使用。在dgate中需要在apiGateway中添加如下的配置:

apiGateway {
    ……
    cors {
        allowedOriginPattern = "*"
        allowedMethods = [HttpMethod.GET, HttpMethod.POST, HttpMethod.DELETE]
        allowedHeaders = ["content-type"]
        allowCredentials = true
    }
    ……
}

cors配置并非必需的,若没有,dgate不会支持CORS。CORS的配置项目如下:

allowedOriginPattern,字符串,如 “http://localhost:7000”。

allowedHeaders,字符串集合,支持的HTTP请求HEADER集合

allowedMethods,Vertx的HttpMethod集合,支持的HTTP方法集合

exposedHeaders,字符串集合,允许暴露的响应头集合

maxAgeSeconds,整型,可用于缓存preflight的时间,单位(秒)

allowCredentials,是否允许Cookie

其中仅allowedOriginPattern是必填。但,对于提交格式为JSON的ajax请求,需要允许:"content-type"。

此外,在调试过程中,也可以注意浏览器给出的提示,根据相应提示进行配置。

警告:在产品环境中不要使用“*”作为allowedOriginPattern的值。

每个请求的附加参数

dgate会给每个发往后端服务的请求参数中添加若干参数,通过下面的key可以获得:

nameOfApiGateway,Api Gateway的名字,字符串。

token,已解码的jwt token,其类型是一个Map,内容依赖于在产生token时设置的值。如:在产生时包含[sub, name, role]这几个键值,则此处就获得这3个键值。若在产生时为[sub, name, role, other],则此处就可以会有这4个键值。

注意:除了必需的几个属性,JWT Token中token本身是可以附加其他属性进来的。相当于将token本身作为信息的载体。

断路器设置

dgate缺省会为每个上游服务(注:对于Mock服务,断路器设置无效)设置一个断路器,缺省的配置如下:

最大失败次数(maxFailures),3次

请求超时(timeout),5秒

断路器重置时间(resetTimeout),10秒

若缺省的设置不合适,dgate支持两个层次的设置:gateway级别和上游服务级别。并且,在两者都存在时,上游服务的断路器设置会覆盖gateway级别的设置。

gateway级别的设置

这个设置将对gateway内所有的上游服务生效,例子如下:

apiGateway {
    ……
    circuitBreaker {
        maxFailures = 5
        timeout = 10000
        resetTimeout = 30000
    }
    ……
}
上游服务级别的设置

这个设置仅对当前上游服务生效,例子如下:

……
"/url" {
    upstreamURLs = [
        [host: "localhost", port: 8080, url: "/test1",
         circuitBreaker: [maxFailures: 2, timeout: 3000, resetTimeout: 3000]]
    ]
}
……

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/66564.html

相关文章

  • dgate新特性:EventBusBridge Mock

    摘要:最近,因为项目的需要,顺手给它增加了一个新的特性。其中,对应后端主动发起的推送,对于每一个推送地址,必填,单位为毫秒。,对应后端接收前端消息的消费者。当为闭包时,其返回值为结果。相关链接基于的轻量级上的的使用指南 由于简单同时又强大的Mock特性,dgate在我的项目中除了作为简单的API网关,它也承担着面向前端的Mock Server作用,保证前后端开发同步进行。最近,因为项目的需要...

    Clect 评论0 收藏0
  • Vert.x入坑须知(1)

    摘要:轻量级,部署简单。此外,本文也不是入门文档,而是为了预防陷坑而给出的指导意见,故在阅读本文之前还请先仔细阅读的文档。可视作的一个最小部署和运行单元,简单的说,可类比为。,主,负责部署程序中其他的。严格来讲,之后,上述第一点并不完全正确。 一直以来早有将这些年用Vert.x的经验整理一下的想法,奈何天生不是勤快人,直到最近扶墙老师问起,遂成此文。 选择理由 现在想想,我们应该算是国内用V...

    Turbo 评论0 收藏0
  • Vert.x入坑须知(3)

    摘要:对于集成测试,直接模拟实际的环境,再加上合适的,目前看来也还不错。这里给出两个例子集成测试单元测试都是基于写的,各位可以体验其酸爽度。好啦,本期内容就此结束,请保持关注,期待下期继续本系列其他文章入坑须知入坑须知 随着Vert.x进化到3.5.0,本系列也迎来了新篇章。 CORS的新变化 对于CORS,搞Web开发(不论你是前端,还是后端)的同志应该不陌生,尤其是如今微服务盛行的时代,...

    CollinPeng 评论0 收藏0
  • Vert.x入坑须知(2)

    摘要:这一点其实是非常不妥的,有潜在的安全问题。这次,在项目中终于采用了以它为基础的集群方案。相反,使用一个周期,但针对每个生成一个一次性的,模拟随机发送。同时,要记得用完之后立即释放。 当初创建简书账号的时候曾立下宏愿,希望保持周更,无奈现实残酷,整个5月都处于忙忙碌碌的状态,居然令这个本来并不算太宏伟的目标难以为继,最终导致5月份交了白卷!【好吧,我承认,是我意志不够坚定,太懒了,;)】...

    xialong 评论0 收藏0
  • Vert.x Blueprint 系列教程(三) | Micro-Shop 微服务应用实战

    摘要:本教程是蓝图系列的第三篇教程,对应的版本为。提供了一个服务发现模块用于发布和获取服务记录。前端此微服务的前端部分,目前已整合至组件中。监视仪表板用于监视微服务系统的状态以及日志统计数据的查看。而服务则负责发布其它服务如服务或消息源并且部署。 本文章是 Vert.x 蓝图系列 的第三篇教程。全系列: Vert.x Blueprint 系列教程(一) | 待办事项服务开发教程 Vert....

    QiShare 评论0 收藏0

发表评论

0条评论

dingding199389

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<