diff --git a/README.md b/README.md index 08e61a0..e50feaa 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ dgate的主要特性: - 支持JWT - 支持CORS - 灵活的login配置 +- 支持断路器 详细的用户指南请访问[这里](./docs/user_guide.md)。 diff --git a/docs/user_guide.md b/docs/user_guide.md index 7e9e38a..6a03b18 100644 --- a/docs/user_guide.md +++ b/docs/user_guide.md @@ -400,3 +400,43 @@ dgate会给每个发往后端服务的请求参数中添加若干参数,通过 - token,已解码的jwt token,其类型是一个Map,内容依赖于在产生token时设置的值。如:在产生时包含[sub, name, role]这几个键值,则此处就获得这3个键值。若在产生时为[sub, name, role, other],则此处就可以会有这4个键值。 注意:除了必需的几个属性,JWT Token中token本身是可以附加其他属性进来的。相当于将token本身作为信息的载体。 + +## 断路器设置 + +dgate缺省会为每个上游服务设置一个断路器,缺省的配置如下: +- 最大失败次数(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]] + ] +} +…… +~~~ \ No newline at end of file diff --git a/src/main/groovy/top/dteam/dgate/config/ApiGatewayRepository.groovy b/src/main/groovy/top/dteam/dgate/config/ApiGatewayRepository.groovy index 73ea782..02cf670 100644 --- a/src/main/groovy/top/dteam/dgate/config/ApiGatewayRepository.groovy +++ b/src/main/groovy/top/dteam/dgate/config/ApiGatewayRepository.groovy @@ -1,5 +1,6 @@ package top.dteam.dgate.config +import io.vertx.circuitbreaker.CircuitBreakerOptions import io.vertx.core.http.HttpMethod class ApiGatewayRepository { @@ -39,9 +40,10 @@ class ApiGatewayRepository { String host = body.host ?: '0.0.0.0' LoginConfig login = body.login ? buildLogin(body.login) : null CorsConfig cors = buildCors(body.cors) + CircuitBreakerOptions defaultCBOptions = buildCircuitBreaker(body.circuitBreaker) List urlConfigs = new ArrayList<>() body.urls.keySet().each { url -> - urlConfigs << buildUrl(url, body.urls[url]) + urlConfigs << buildUrl(url, body.urls[url], defaultCBOptions) } new ApiGatewayConfig( @@ -66,15 +68,25 @@ class ApiGatewayRepository { } } - private static UrlConfig buildUrl(def key, def body) { + private static CircuitBreakerOptions buildCircuitBreaker(Map circuitBreaker) { + new CircuitBreakerOptions() + .setMaxFailures(circuitBreaker?.maxFailures ?: 3) + .setTimeout(circuitBreaker?.timeout ?: 5000) + .setResetTimeout(circuitBreaker?.resetTimeout ?: 10000) + } + + private static UrlConfig buildUrl(def key, def body, CircuitBreakerOptions cbOptions) { String url = key Object required = body.required ?: null List methods = body.methods ?: [] Map expected = body.expected List upstreamURLs = new ArrayList<>() body.upstreamURLs.each { upstreamURL -> + CircuitBreakerOptions cbOptionsForUpstreamURL = + upstreamURL.circuitBreaker? buildCircuitBreaker(upstreamURL.circuitBreaker) : cbOptions + upstreamURLs << new UpstreamURL(host: upstreamURL.host, port: upstreamURL.port, url: upstreamURL.url, - before: upstreamURL.before, after: upstreamURL.after) + before: upstreamURL.before, after: upstreamURL.after, cbOptions: cbOptionsForUpstreamURL) } new UrlConfig(url: url, required: required, methods: methods, expected: expected, upstreamURLs: upstreamURLs) diff --git a/src/main/groovy/top/dteam/dgate/config/UpstreamURL.groovy b/src/main/groovy/top/dteam/dgate/config/UpstreamURL.groovy index d5d8df6..110c694 100644 --- a/src/main/groovy/top/dteam/dgate/config/UpstreamURL.groovy +++ b/src/main/groovy/top/dteam/dgate/config/UpstreamURL.groovy @@ -2,16 +2,18 @@ package top.dteam.dgate.config import groovy.transform.CompileStatic import groovy.transform.EqualsAndHashCode +import io.vertx.circuitbreaker.CircuitBreakerOptions import io.vertx.core.json.JsonObject import top.dteam.dgate.gateway.SimpleResponse -@EqualsAndHashCode(excludes = ['before', 'after']) +@EqualsAndHashCode(excludes = ['before', 'after', 'cbOptions']) @CompileStatic class UpstreamURL { String host int port String url + CircuitBreakerOptions cbOptions Closure before Closure after diff --git a/src/main/java/top/dteam/dgate/handler/LoginHandler.java b/src/main/java/top/dteam/dgate/handler/LoginHandler.java index 3eb5bc3..00053ad 100644 --- a/src/main/java/top/dteam/dgate/handler/LoginHandler.java +++ b/src/main/java/top/dteam/dgate/handler/LoginHandler.java @@ -1,10 +1,9 @@ package top.dteam.dgate.handler; -import top.dteam.dgate.config.UrlConfig; -import top.dteam.dgate.utils.JWTTokenGenerator; -import io.vertx.circuitbreaker.CircuitBreakerOptions; import io.vertx.core.Vertx; import io.vertx.ext.auth.jwt.JWTAuth; +import top.dteam.dgate.config.UrlConfig; +import top.dteam.dgate.utils.JWTTokenGenerator; import java.util.HashMap; import java.util.Map; @@ -14,8 +13,8 @@ public class LoginHandler extends ProxyHandler { private JWTTokenGenerator tokenGenerator; - public LoginHandler(Vertx vertx, UrlConfig urlConfig, CircuitBreakerOptions circuitBreakerOptions, JWTAuth jwtAuth) { - super(vertx, urlConfig, circuitBreakerOptions); + public LoginHandler(Vertx vertx, UrlConfig urlConfig, JWTAuth jwtAuth) { + super(vertx, urlConfig); this.tokenGenerator = new JWTTokenGenerator(jwtAuth); } diff --git a/src/main/java/top/dteam/dgate/handler/ProxyHandler.java b/src/main/java/top/dteam/dgate/handler/ProxyHandler.java index e39ed16..a2eac43 100644 --- a/src/main/java/top/dteam/dgate/handler/ProxyHandler.java +++ b/src/main/java/top/dteam/dgate/handler/ProxyHandler.java @@ -1,13 +1,7 @@ package top.dteam.dgate.handler; -import top.dteam.dgate.config.UpstreamURL; -import top.dteam.dgate.config.UrlConfig; -import top.dteam.dgate.gateway.SimpleResponse; -import top.dteam.dgate.utils.RequestUtils; -import top.dteam.dgate.utils.Utils; import groovy.lang.Closure; import io.vertx.circuitbreaker.CircuitBreaker; -import io.vertx.circuitbreaker.CircuitBreakerOptions; import io.vertx.core.Vertx; import io.vertx.core.http.HttpMethod; import io.vertx.core.http.HttpServerRequest; @@ -15,8 +9,16 @@ import io.vertx.core.json.JsonObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import top.dteam.dgate.config.UpstreamURL; +import top.dteam.dgate.config.UrlConfig; +import top.dteam.dgate.gateway.SimpleResponse; +import top.dteam.dgate.utils.RequestUtils; +import top.dteam.dgate.utils.Utils; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicInteger; @@ -28,7 +30,7 @@ public class ProxyHandler extends RequestHandler { private Map circuitBreakers; private RequestUtils requestUtils; - public ProxyHandler(Vertx vertx, UrlConfig urlConfig, CircuitBreakerOptions circuitBreakerOptions) { + public ProxyHandler(Vertx vertx, UrlConfig urlConfig) { super(vertx, urlConfig); upstreamURLs = urlConfig.getUpstreamURLs(); @@ -38,7 +40,7 @@ public ProxyHandler(Vertx vertx, UrlConfig urlConfig, CircuitBreakerOptions circ upstreamURLs.forEach(upStreamURL -> circuitBreakers.put(upStreamURL.toString(), CircuitBreaker.create(String.format("cb-%s-%s", urlConfig.getUrl(), - upStreamURL.toString()), vertx, circuitBreakerOptions)) + upStreamURL.toString()), vertx, upStreamURL.getCbOptions())) ); } diff --git a/src/main/java/top/dteam/dgate/handler/RequestHandler.java b/src/main/java/top/dteam/dgate/handler/RequestHandler.java index 75eab39..0eb589e 100644 --- a/src/main/java/top/dteam/dgate/handler/RequestHandler.java +++ b/src/main/java/top/dteam/dgate/handler/RequestHandler.java @@ -1,10 +1,5 @@ package top.dteam.dgate.handler; -import io.vertx.ext.auth.jwt.JWTAuth; -import top.dteam.dgate.config.InvalidConfiguriationException; -import top.dteam.dgate.utils.Utils; -import top.dteam.dgate.config.UrlConfig; -import io.vertx.circuitbreaker.CircuitBreakerOptions; import io.vertx.core.Handler; import io.vertx.core.MultiMap; import io.vertx.core.Vertx; @@ -13,11 +8,17 @@ import io.vertx.core.http.HttpServerRequest; import io.vertx.core.http.HttpServerResponse; import io.vertx.core.json.JsonObject; +import io.vertx.ext.auth.jwt.JWTAuth; import io.vertx.ext.web.RoutingContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import top.dteam.dgate.config.InvalidConfiguriationException; +import top.dteam.dgate.config.UrlConfig; +import top.dteam.dgate.utils.Utils; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public abstract class RequestHandler implements Handler { @@ -31,12 +32,9 @@ public static RequestHandler create(Vertx vertx, UrlConfig urlConfig, JWTAuth jw int requestHandlerType = urlConfig.requestHandlerType(); if (requestHandlerType == UrlConfig.PROXY) { if (jwtAuth == null) { - return new ProxyHandler(vertx, urlConfig, - new CircuitBreakerOptions().setMaxFailures(3).setTimeout(5000).setResetTimeout(10000)); + return new ProxyHandler(vertx, urlConfig); } else { - return new LoginHandler(vertx, urlConfig, - new CircuitBreakerOptions().setMaxFailures(3).setTimeout(5000).setResetTimeout(10000), - jwtAuth); + return new LoginHandler(vertx, urlConfig, jwtAuth); } } else if (requestHandlerType == UrlConfig.MOCK) { return new MockHandler(vertx, urlConfig); diff --git a/src/test/groovy/top/dteam/dgate/config/ApiGatewayRepositorySpec.groovy b/src/test/groovy/top/dteam/dgate/config/ApiGatewayRepositorySpec.groovy index f6640a9..a4ed92e 100644 --- a/src/test/groovy/top/dteam/dgate/config/ApiGatewayRepositorySpec.groovy +++ b/src/test/groovy/top/dteam/dgate/config/ApiGatewayRepositorySpec.groovy @@ -68,6 +68,11 @@ class ApiGatewayRepositorySpec extends Specification { apiGateway2 { port = 7001 host = 'localhost' + circuitBreaker { + maxFailures = 5 + timeout = 10000 + resetTimeout = 30000 + } urls { "/mock" { expected { @@ -85,6 +90,17 @@ class ApiGatewayRepositorySpec extends Specification { } } } + "/proxy1" { + upstreamURLs = [ + [host: 'localhost', port: 8080, url: '/test1'] + ] + } + "/proxy2" { + upstreamURLs = [ + [host: 'localhost', port: 8080, url: '/test1', + circuitBreaker: [maxFailures: 2, timeout: 3000, resetTimeout: 3000]] + ] + } } } apiGateway3 { @@ -131,6 +147,7 @@ class ApiGatewayRepositorySpec extends Specification { } urlConfigs.size() == 4 with(urlConfigs[0]) { + url == '/login' required == ["sub", "password"] methods == [HttpMethod.GET, HttpMethod.POST] upstreamURLs.size == 1 @@ -139,9 +156,13 @@ class ApiGatewayRepositorySpec extends Specification { upstreamURLs[0].url == '/login' !upstreamURLs[0].before upstreamURLs[0].after + upstreamURLs[0].cbOptions.maxFailures == 3 + upstreamURLs[0].cbOptions.timeout == 5000 + upstreamURLs[0].cbOptions.resetTimeout == 10000 } urlConfigs[1].expected == [statusCode: 200, payload: [test: true]] with(urlConfigs[2]) { + url == '/forward' required == [get: ['param1'], post: ['param2'], delete: ['param3']] methods == [HttpMethod.GET, HttpMethod.POST, HttpMethod.DELETE] upstreamURLs.size == 1 @@ -153,6 +174,7 @@ class ApiGatewayRepositorySpec extends Specification { upstreamURLs[0].after upstreamURLs[0].after(simpleResponse) == simpleResponse !expected + upstreamURLs[0].cbOptions } with(urlConfigs[3]) { required == ['param1', 'param2'] @@ -163,9 +185,11 @@ class ApiGatewayRepositorySpec extends Specification { new UpstreamURL(host: 'localhost', port: 8080, url: '/test2') ] !upstreamURLs[0].before + !upstreamURLs[0].after + upstreamURLs[0].cbOptions + !upstreamURLs[1].before !upstreamURLs[1].after - !upstreamURLs[0].before - !upstreamURLs[1].after + upstreamURLs[1].cbOptions !expected } } @@ -175,10 +199,16 @@ class ApiGatewayRepositorySpec extends Specification { name == 'apiGateway2' !login !cors - urlConfigs.size() == 1 + urlConfigs.size() == 3 urlConfigs[0].expected == [get : [statusCode: 200, payload: [method: 'get']], post : [statusCode: 200, payload: [method: 'post']], delete: [statusCode: 200, payload: [method: 'delete']]] + urlConfigs[1].upstreamURLs[0].cbOptions.maxFailures == 5 + urlConfigs[1].upstreamURLs[0].cbOptions.timeout == 10000 + urlConfigs[1].upstreamURLs[0].cbOptions.resetTimeout == 30000 + urlConfigs[2].upstreamURLs[0].cbOptions.maxFailures == 2 + urlConfigs[2].upstreamURLs[0].cbOptions.timeout == 3000 + urlConfigs[2].upstreamURLs[0].cbOptions.resetTimeout == 3000 } with(repository[2]) { port == 7002 diff --git a/src/test/groovy/top/dteam/dgate/handler/CircuitBreakerSpec.groovy b/src/test/groovy/top/dteam/dgate/handler/CircuitBreakerSpec.groovy new file mode 100644 index 0000000..027e30b --- /dev/null +++ b/src/test/groovy/top/dteam/dgate/handler/CircuitBreakerSpec.groovy @@ -0,0 +1,148 @@ +package top.dteam.dgate.handler + +import io.vertx.core.Vertx +import io.vertx.core.http.HttpServer +import io.vertx.core.json.JsonObject +import io.vertx.ext.web.Router +import spock.lang.Specification +import top.dteam.dgate.config.ApiGatewayRepository +import top.dteam.dgate.gateway.ApiGateway +import top.dteam.dgate.gateway.SimpleResponse +import top.dteam.dgate.utils.RequestUtils +import top.dteam.dgate.utils.TestUtils +import top.dteam.dgate.utils.Utils + +class CircuitBreakerSpec extends Specification { + + private static final long DEFAULT_OP_TIMEOUT = 5000 + + String config = """ + apiGateway1 { + port = 7000 + urls { + "/test" { + upstreamURLs = [ + [ host: 'localhost', port: 8080, url: '/test-5s'] + ] + } + } + } + apiGateway2 { + port = 7001 + circuitBreaker { + maxFailures = 2 + timeout = 2000 + resetTimeout = 5000 + } + urls { + "/test" { + upstreamURLs = [ + [host: 'localhost', port: 8080, url: '/test-2s'] + ] + } + } + } + apiGateway3 { + port = 7002 + circuitBreaker { + maxFailures = 2 + timeout = 5000 + resetTimeout = 2000 + } + urls { + "/test" { + upstreamURLs = [ + [host: 'localhost', port: 8080, url: '/test-3s', + circuitBreaker: [maxFailures: 2, timeout: 3000, resetTimeout: 3000]] + ] + } + } + } + """ + + Vertx vertx + HttpServer dest + RequestUtils requestUtils + + void setup() { + vertx = Vertx.vertx() + deployGate() + dest = createDest() + requestUtils = new RequestUtils(vertx) + } + + void cleanup() { + dest.close() + vertx.close() + } + + def "CircuitBreak Default Options should work"() { + setup: + SimpleResponse result + + when: + sleep(100) + requestUtils.post("localhost", 7000, "/test", new JsonObject()) { simpleResponse -> + result = simpleResponse + } + TestUtils.waitResult(result, DEFAULT_OP_TIMEOUT + 500) + + then: + result.statusCode == 500 + result.payload.map.error == "operation timeout" + } + + def "Global CircuitBreak Options should override the default Circuit Break Options"() { + setup: + SimpleResponse result + + when: + sleep(100) + requestUtils.post("localhost", 7001, "/test", new JsonObject()) { simpleResponse -> + result = simpleResponse + } + TestUtils.waitResult(result, 2000 + 500) + + then: + result.statusCode == 500 + result.payload.map.error == "operation timeout" + } + + def "Circuit Break Options for specific URL should be used first"() { + setup: + SimpleResponse result + + when: + sleep(100) + requestUtils.post("localhost", 7002, "/test", new JsonObject()) { simpleResponse -> + result = simpleResponse + } + TestUtils.waitResult(result, 3000 + 500) + + then: + result.statusCode == 500 + result.payload.map.error == "operation timeout" + } + + private HttpServer createDest() { + HttpServer httpServer = vertx.createHttpServer() + Router router = Router.router(vertx) + httpServer.requestHandler(router.&accept).listen(8080) + + router.route('/test').handler { routingContext -> + sleep(DEFAULT_OP_TIMEOUT + 200) + routingContext.request().bodyHandler { totalBuffer -> + Utils.fireJsonResponse(routingContext.response(), 200, [test: true]) + } + } + + httpServer + } + + private void deployGate() { + vertx.deployVerticle(new ApiGateway(ApiGatewayRepository.build(config)[0])) + vertx.deployVerticle(new ApiGateway(ApiGatewayRepository.build(config)[1])) + vertx.deployVerticle(new ApiGateway(ApiGatewayRepository.build(config)[2])) + } + +} diff --git a/src/test/groovy/top/dteam/dgate/handler/CompositeRequestSpec.groovy b/src/test/groovy/top/dteam/dgate/handler/CompositeRequestSpec.groovy index b437abb..31846d9 100644 --- a/src/test/groovy/top/dteam/dgate/handler/CompositeRequestSpec.groovy +++ b/src/test/groovy/top/dteam/dgate/handler/CompositeRequestSpec.groovy @@ -117,41 +117,53 @@ class CompositeRequestSpec extends Specification { router.route("/allSuccess").handler(new ProxyHandler(vertx, new UrlConfig( upstreamURLs: Arrays.asList( - new UpstreamURL(host: "localhost", port: 8082, url: "/success1"), - new UpstreamURL(host: "localhost", port: 8082, url: "/success2") + new UpstreamURL(host: "localhost", port: 8082, url: "/success1", + cbOptions: new CircuitBreakerOptions().setMaxFailures(3) + .setTimeout(OP_TIMEOUT).setResetTimeout(RESET_TIMEOUT)), + new UpstreamURL(host: "localhost", port: 8082, url: "/success2", + cbOptions: new CircuitBreakerOptions().setMaxFailures(3) + .setTimeout(OP_TIMEOUT).setResetTimeout(RESET_TIMEOUT)) ) - ), - new CircuitBreakerOptions().setMaxFailures(3).setTimeout(OP_TIMEOUT).setResetTimeout(RESET_TIMEOUT))) + ))) router.route("/allFailure").handler(new ProxyHandler(vertx, new UrlConfig( upstreamURLs: Arrays.asList( - new UpstreamURL(host: "localhost", port: 8082, url: "/failure1"), - new UpstreamURL(host: "localhost", port: 8082, url: "/failure2") + new UpstreamURL(host: "localhost", port: 8082, url: "/failure1", + cbOptions: new CircuitBreakerOptions().setMaxFailures(3) + .setTimeout(OP_TIMEOUT).setResetTimeout(RESET_TIMEOUT)), + new UpstreamURL(host: "localhost", port: 8082, url: "/failure2", + cbOptions: new CircuitBreakerOptions().setMaxFailures(3) + .setTimeout(OP_TIMEOUT).setResetTimeout(RESET_TIMEOUT)) ) - ), - new CircuitBreakerOptions().setMaxFailures(3).setTimeout(OP_TIMEOUT).setResetTimeout(RESET_TIMEOUT))) + ))) router.route("/partialSuccess").handler(new ProxyHandler(vertx, new UrlConfig( upstreamURLs: Arrays.asList( - new UpstreamURL(host: "localhost", port: 8082, url: "/failure1"), - new UpstreamURL(host: "localhost", port: 8082, url: "/success2") + new UpstreamURL(host: "localhost", port: 8082, url: "/failure1", + cbOptions: new CircuitBreakerOptions().setMaxFailures(3) + .setTimeout(OP_TIMEOUT).setResetTimeout(RESET_TIMEOUT)), + new UpstreamURL(host: "localhost", port: 8082, url: "/success2", + cbOptions: new CircuitBreakerOptions().setMaxFailures(3) + .setTimeout(OP_TIMEOUT).setResetTimeout(RESET_TIMEOUT)) ) - ), - new CircuitBreakerOptions().setMaxFailures(3).setTimeout(OP_TIMEOUT).setResetTimeout(RESET_TIMEOUT))) + ))) router.route("/checkHandlerContext").handler(new SubProxyHandler(vertx, new UrlConfig( upstreamURLs: Arrays.asList( new UpstreamURL(host: "localhost", port: 8082, url: "/success1", before: { params -> params.put("before", paramForBefore) params - }), + }, + cbOptions: new CircuitBreakerOptions().setMaxFailures(3) + .setTimeout(OP_TIMEOUT).setResetTimeout(RESET_TIMEOUT)), new UpstreamURL(host: "localhost", port: 8082, url: "/success2", after: { simpleResponse -> simpleResponse.payload.put("after", paramForAfter) simpleResponse - }) + }, + cbOptions: new CircuitBreakerOptions().setMaxFailures(3) + .setTimeout(OP_TIMEOUT).setResetTimeout(RESET_TIMEOUT)) ) - ), - new CircuitBreakerOptions().setMaxFailures(3).setTimeout(OP_TIMEOUT).setResetTimeout(RESET_TIMEOUT))) + ))) httpServer } @@ -200,8 +212,8 @@ class CompositeRequestSpec extends Specification { class SubProxyHandler extends ProxyHandler { - SubProxyHandler(Vertx vertx, UrlConfig urlConfig, CircuitBreakerOptions circuitBreakerOptions) { - super(vertx, urlConfig, circuitBreakerOptions) + SubProxyHandler(Vertx vertx, UrlConfig urlConfig) { + super(vertx, urlConfig) } @Override diff --git a/src/test/groovy/top/dteam/dgate/handler/ForwardRequestSpec.groovy b/src/test/groovy/top/dteam/dgate/handler/ForwardRequestSpec.groovy index 48144ca..894ec66 100644 --- a/src/test/groovy/top/dteam/dgate/handler/ForwardRequestSpec.groovy +++ b/src/test/groovy/top/dteam/dgate/handler/ForwardRequestSpec.groovy @@ -198,13 +198,15 @@ class ForwardRequestSpec extends Specification { httpServer.requestHandler(router.&accept).listen(8081) router.route("/withoutHandler").handler(new ProxyHandler(vertx, - new UrlConfig(upstreamURLs: [new UpstreamURL(host: "localhost", port: 8082, url: "/normal")], - methods: [HttpMethod.GET, HttpMethod.POST, HttpMethod.DELETE]), - new CircuitBreakerOptions().setMaxFailures(3).setTimeout(OP_TIMEOUT).setResetTimeout(RESET_TIMEOUT))) + new UrlConfig(upstreamURLs: [new UpstreamURL(host: "localhost", port: 8082, url: "/normal", + cbOptions: new CircuitBreakerOptions().setMaxFailures(3) + .setTimeout(OP_TIMEOUT).setResetTimeout(RESET_TIMEOUT))], + methods: [HttpMethod.GET, HttpMethod.POST, HttpMethod.DELETE]))) router.route("/timeout").handler(new ProxyHandler(vertx, - new UrlConfig(upstreamURLs: [new UpstreamURL(host: "localhost", port: 8082, url: "/timeout")], - methods: [HttpMethod.GET, HttpMethod.POST, HttpMethod.DELETE]), - new CircuitBreakerOptions().setMaxFailures(3).setTimeout(OP_TIMEOUT).setResetTimeout(RESET_TIMEOUT))) + new UrlConfig(upstreamURLs: [new UpstreamURL(host: "localhost", port: 8082, url: "/timeout", + cbOptions: new CircuitBreakerOptions().setMaxFailures(3) + .setTimeout(OP_TIMEOUT).setResetTimeout(RESET_TIMEOUT))], + methods: [HttpMethod.GET, HttpMethod.POST, HttpMethod.DELETE]))) router.route("/withHandler").handler(new ProxyHandler(vertx, new UrlConfig(upstreamURLs: [ new UpstreamURL(host: "localhost", port: 8082, url: "/normal", @@ -215,20 +217,24 @@ class ForwardRequestSpec extends Specification { after: { simpleResponse -> simpleResponse.payload.put('addedByAfter', 'addedByAfter') simpleResponse - })], - methods: [HttpMethod.GET, HttpMethod.POST, HttpMethod.DELETE]), - new CircuitBreakerOptions().setMaxFailures(3).setTimeout(OP_TIMEOUT).setResetTimeout(RESET_TIMEOUT))) + }, + cbOptions: new CircuitBreakerOptions().setMaxFailures(3) + .setTimeout(OP_TIMEOUT).setResetTimeout(RESET_TIMEOUT))], + methods: [HttpMethod.GET, HttpMethod.POST, HttpMethod.DELETE]))) router.route("/unknown").handler(new ProxyHandler(vertx, - new UrlConfig(upstreamURLs: [new UpstreamURL(host: "localhost", port: 8082, url: "/unknown")], - methods: [HttpMethod.GET, HttpMethod.POST, HttpMethod.DELETE]), - new CircuitBreakerOptions().setMaxFailures(3).setTimeout(OP_TIMEOUT).setResetTimeout(RESET_TIMEOUT))) + new UrlConfig(upstreamURLs: [new UpstreamURL(host: "localhost", port: 8082, url: "/unknown", + cbOptions: new CircuitBreakerOptions().setMaxFailures(3) + .setTimeout(OP_TIMEOUT).setResetTimeout(RESET_TIMEOUT))], + methods: [HttpMethod.GET, HttpMethod.POST, HttpMethod.DELETE]))) router.route("/url-template").handler(new ProxyHandler(vertx, - new UrlConfig(upstreamURLs: [new UpstreamURL(host: "localhost", port: 8082, url: "/url-template/:x/:y?/:z?")], - methods: [HttpMethod.GET, HttpMethod.POST, HttpMethod.DELETE]), - new CircuitBreakerOptions().setMaxFailures(3).setTimeout(OP_TIMEOUT).setResetTimeout(RESET_TIMEOUT))) + new UrlConfig(upstreamURLs: [new UpstreamURL(host: "localhost", port: 8082, url: "/url-template/:x/:y?/:z?", + cbOptions: new CircuitBreakerOptions().setMaxFailures(3) + .setTimeout(OP_TIMEOUT).setResetTimeout(RESET_TIMEOUT))], + methods: [HttpMethod.GET, HttpMethod.POST, HttpMethod.DELETE]))) router.route("/path-params/:id").handler(new ProxyHandler(vertx, - new UrlConfig(upstreamURLs: [new UpstreamURL(host: "localhost", port: 8082, url: "/normal")]), - new CircuitBreakerOptions().setMaxFailures(3).setTimeout(OP_TIMEOUT).setResetTimeout(RESET_TIMEOUT))) + new UrlConfig(upstreamURLs: [new UpstreamURL(host: "localhost", port: 8082, url: "/normal", + cbOptions: new CircuitBreakerOptions().setMaxFailures(3) + .setTimeout(OP_TIMEOUT).setResetTimeout(RESET_TIMEOUT))]))) httpServer }