From 095644c81139027894ab524c85bc3104876a740a Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Wed, 5 Feb 2025 16:44:25 +0100 Subject: [PATCH 1/3] support independent response time route setting Signed-off-by: Pablo Carle --- .../config/NettyRoutingFilterApiml.java | 27 +++++++----- .../gateway/GatewayRoutingTest.java | 42 ++++++++++++++----- 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/NettyRoutingFilterApiml.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/NettyRoutingFilterApiml.java index 9f10b69e53..a81b9abf89 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/NettyRoutingFilterApiml.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/NettyRoutingFilterApiml.java @@ -30,6 +30,7 @@ import java.util.Optional; import static org.springframework.cloud.gateway.support.RouteMetadataUtils.CONNECT_TIMEOUT_ATTR; +import static org.springframework.cloud.gateway.support.RouteMetadataUtils.RESPONSE_TIMEOUT_ATTR; import static org.zowe.apiml.constants.ApimlConstants.HTTP_CLIENT_USE_CLIENT_CERTIFICATE; @Slf4j @@ -66,23 +67,29 @@ static Integer getInteger(Object connectTimeoutAttr) { @Override protected HttpClient getHttpClient(Route route, ServerWebExchange exchange) { // select proper HttpClient instance by attribute apiml.useClientCert - boolean useClientCert = Optional.ofNullable((Boolean) exchange.getAttribute(HTTP_CLIENT_USE_CLIENT_CERTIFICATE)).orElse(Boolean.FALSE); - HttpClient httpClient = useClientCert ? httpClientClientCert : httpClientNoCert; + var useClientCert = Optional.ofNullable((Boolean) exchange.getAttribute(HTTP_CLIENT_USE_CLIENT_CERTIFICATE)).orElse(Boolean.FALSE); + var httpClient = useClientCert ? httpClientClientCert : httpClientNoCert; log.debug("Using client with keystore {}", useClientCert); - Object connectTimeoutAttr = route.getMetadata().get(CONNECT_TIMEOUT_ATTR); + var connectTimeoutAttr = route.getMetadata().get(CONNECT_TIMEOUT_ATTR); + var responseTimeoutAttr = route.getMetadata().get(RESPONSE_TIMEOUT_ATTR); + + httpClient = httpClient + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, requestTimeout) + .responseTimeout(Duration.ofMillis(requestTimeout)); + + if (responseTimeoutAttr != null) { + httpClient = httpClient.responseTimeout(Duration.parse(String.valueOf(responseTimeoutAttr))); + } + if (connectTimeoutAttr != null) { // if there is configured timeout, respect it Integer connectTimeout = getInteger(connectTimeoutAttr); - return httpClient - .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout) - .responseTimeout(Duration.ofMillis(connectTimeout)); + httpClient = httpClient + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout); } - // otherwise just return selected HttpClient with the default configured timeouts - return httpClient - .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, requestTimeout) - .responseTimeout(Duration.ofMillis(requestTimeout)); + return httpClient; } @Override diff --git a/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/GatewayRoutingTest.java b/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/GatewayRoutingTest.java index 678523a687..e7aee77002 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/GatewayRoutingTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/functional/gateway/GatewayRoutingTest.java @@ -50,7 +50,10 @@ static void setup() { }) void testRoutingWithBasePath(String basePath) throws URISyntaxException { String scgUrl = String.format("%s://%s:%s%s", conf.getScheme(), conf.getHost(), conf.getPort(), basePath); - given().get(new URI(scgUrl)).then().statusCode(200); + given() + .get(new URI(scgUrl)) + .then() + .statusCode(200); } @ParameterizedTest(name = "When header X-Forward-To is set to {0} should return 200") @@ -60,8 +63,11 @@ void testRoutingWithBasePath(String basePath) throws URISyntaxException { }) void testRoutingWithHeader(String forwardTo) throws URISyntaxException { String scgUrl = String.format("%s://%s:%s%s", conf.getScheme(), conf.getHost(), conf.getPort(), DISCOVERABLE_GREET); - given().header(HEADER_X_FORWARD_TO, forwardTo) - .get(new URI(scgUrl)).then().statusCode(200); + given() + .header(HEADER_X_FORWARD_TO, forwardTo) + .get(new URI(scgUrl)) + .then() + .statusCode(200); } @ParameterizedTest(name = "When base path is {0} should return 404") @@ -71,7 +77,10 @@ void testRoutingWithHeader(String forwardTo) throws URISyntaxException { }) void testRoutingWithIncorrectServiceInBasePath(String basePath) throws URISyntaxException { String scgUrl = String.format("%s://%s:%s%s", conf.getScheme(), conf.getHost(), conf.getPort(), basePath); - given().get(new URI(scgUrl)).then().statusCode(404); + given() + .get(new URI(scgUrl)) + .then() + .statusCode(404); } @ParameterizedTest(name = "When header X-Forward-To is set to {0} should return 404") @@ -81,8 +90,11 @@ void testRoutingWithIncorrectServiceInBasePath(String basePath) throws URISyntax }) void testRoutingWithIncorrectServiceInHeader(String forwardTo) throws URISyntaxException { String scgUrl = String.format("%s://%s:%s%s", conf.getScheme(), conf.getHost(), conf.getPort(), NON_EXISTING_SERVICE_ENDPOINT); - given().header(HEADER_X_FORWARD_TO, forwardTo) - .get(new URI(scgUrl)).then().statusCode(404); + given() + .header(HEADER_X_FORWARD_TO, forwardTo) + .get(new URI(scgUrl)) + .then() + .statusCode(404); } @ParameterizedTest(name = "When header X-Forward-To is set to {0} and base path is {1} should return 200 - loopback") @@ -91,8 +103,11 @@ void testRoutingWithIncorrectServiceInHeader(String forwardTo) throws URISyntaxE }) void testWrongRoutingWithHeader(String forwardTo, String endpoint) throws URISyntaxException { String scgUrl = String.format("%s://%s:%s%s", conf.getScheme(), conf.getHost(), conf.getPort(), endpoint); - given().header(HEADER_X_FORWARD_TO, forwardTo) - .get(new URI(scgUrl)).then().statusCode(200); + given() + .header(HEADER_X_FORWARD_TO, forwardTo) + .get(new URI(scgUrl)) + .then() + .statusCode(200); } @ParameterizedTest(name = "When base path is {0} should return 404") @@ -102,12 +117,19 @@ void testWrongRoutingWithHeader(String forwardTo, String endpoint) throws URISyn }) void testWrongRoutingWithBasePath(String basePath) throws URISyntaxException { String scgUrl = String.format("%s://%s:%s%s", conf.getScheme(), conf.getHost(), conf.getPort(), basePath); - given().get(new URI(scgUrl)).then().statusCode(404); + given() + .get(new URI(scgUrl)) + .then() + .statusCode(404); } @Test void givenEndpointDoesNotExistOnRegisteredService() throws URISyntaxException { String scgUrl = String.format("%s://%s:%s%s", conf.getScheme(), conf.getHost(), conf.getPort(), "/dcpassticket/api/v1/unknown"); - given().get(new URI(scgUrl)).then().statusCode(404); + given() + .get(new URI(scgUrl)) + .then() + .statusCode(404); } + } From 529b10e9c3c2d6c142e13050a3e1f792f481a683 Mon Sep 17 00:00:00 2001 From: ac892247 Date: Tue, 17 Jun 2025 15:07:09 +0200 Subject: [PATCH 2/3] use property from zowe documentation Signed-off-by: ac892247 --- .../src/main/resources/application.yml | 3 +++ .../gateway/config/NettyRoutingFilterApiml.java | 13 ++++--------- .../config/NettyRoutingFilterApimlTest.java | 17 ++++++++++++++--- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/discoverable-client/src/main/resources/application.yml b/discoverable-client/src/main/resources/application.yml index fcc2d4bbe0..f9b13442da 100644 --- a/discoverable-client/src/main/resources/application.yml +++ b/discoverable-client/src/main/resources/application.yml @@ -129,7 +129,10 @@ apiml: trustStore: ${server.ssl.trustStore} trustStorePassword: ${server.ssl.trustStorePassword} customMetadata: + apiml: + responseTimeout: 10000 + connectTimeout: 6500 enableUrlEncodedCharacters: true gatewayPort: 10010 gatewayAuthEndpoint: /gateway/api/v1/auth diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/NettyRoutingFilterApiml.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/NettyRoutingFilterApiml.java index a81b9abf89..9c403c40f1 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/NettyRoutingFilterApiml.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/NettyRoutingFilterApiml.java @@ -29,8 +29,6 @@ import java.util.List; import java.util.Optional; -import static org.springframework.cloud.gateway.support.RouteMetadataUtils.CONNECT_TIMEOUT_ATTR; -import static org.springframework.cloud.gateway.support.RouteMetadataUtils.RESPONSE_TIMEOUT_ATTR; import static org.zowe.apiml.constants.ApimlConstants.HTTP_CLIENT_USE_CLIENT_CERTIFICATE; @Slf4j @@ -71,16 +69,13 @@ protected HttpClient getHttpClient(Route route, ServerWebExchange exchange) { var httpClient = useClientCert ? httpClientClientCert : httpClientNoCert; log.debug("Using client with keystore {}", useClientCert); - var connectTimeoutAttr = route.getMetadata().get(CONNECT_TIMEOUT_ATTR); - var responseTimeoutAttr = route.getMetadata().get(RESPONSE_TIMEOUT_ATTR); + var connectTimeoutAttr = route.getMetadata().get("apiml.connectTimeout"); + var responseTimeoutAttr = route.getMetadata().get("apiml.responseTimeout"); + var responseTimeoutResult = responseTimeoutAttr != null ? Long.parseLong(String.valueOf(responseTimeoutAttr)) : requestTimeout; httpClient = httpClient .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, requestTimeout) - .responseTimeout(Duration.ofMillis(requestTimeout)); - - if (responseTimeoutAttr != null) { - httpClient = httpClient.responseTimeout(Duration.parse(String.valueOf(responseTimeoutAttr))); - } + .responseTimeout(Duration.ofMillis(responseTimeoutResult)); if (connectTimeoutAttr != null) { // if there is configured timeout, respect it diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/config/NettyRoutingFilterApimlTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/config/NettyRoutingFilterApimlTest.java index 7cc215ae9e..dcd50f4602 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/config/NettyRoutingFilterApimlTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/config/NettyRoutingFilterApimlTest.java @@ -28,13 +28,13 @@ import reactor.netty.http.client.HttpClient; import javax.net.ssl.SSLException; +import java.time.Duration; import static io.restassured.RestAssured.given; import static org.apache.http.HttpStatus.SC_OK; import static org.apache.http.HttpStatus.SC_SERVICE_UNAVAILABLE; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; -import static org.springframework.cloud.gateway.support.RouteMetadataUtils.CONNECT_TIMEOUT_ATTR; import static org.zowe.apiml.constants.ApimlConstants.HTTP_CLIENT_USE_CLIENT_CERTIFICATE; class NettyRoutingFilterApimlTest { @@ -92,10 +92,13 @@ class GetHttpClient { NettyRoutingFilterApiml nettyRoutingFilterApiml; private final Route ROUTE_NO_TIMEOUT = Route.async() - .id("1").uri("http://localhost/").predicate(__ -> true) + .id("1").uri("http://localhost/").predicate(__ -> true) .build(); private final Route ROUTE_TIMEOUT = Route.async() - .id("2").uri("http://localhost/").predicate(__ -> true).metadata(CONNECT_TIMEOUT_ATTR, "100") + .id("2").uri("http://localhost/").predicate(__ -> true).metadata("apiml.connectTimeout", "100") + .build(); + private final Route ROUTE_RESPONSE_TIMEOUT = Route.async() + .id("3").uri("http://localhost/").predicate(__ -> true).metadata("apiml.responseTimeout", "23") .build(); MockServerWebExchange serverWebExchange; @@ -143,6 +146,14 @@ void givenTimeoutAndRequirementsForClientCert_whenGetHttpClient_thenCallWithoutC verify(httpClientWithCert).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 100); } + @Test + void givenTimeoutAndRequirementsForClientCert_whenGetHttpClient_thenCallWithoutClientCertWithCorrectTimeout() { + setUpClient(httpClientWithCert); + serverWebExchange.getAttributes().put(HTTP_CLIENT_USE_CLIENT_CERTIFICATE, Boolean.TRUE); + nettyRoutingFilterApiml.getHttpClient(ROUTE_RESPONSE_TIMEOUT, serverWebExchange); + verify(httpClientWithCert).responseTimeout(Duration.ofMillis(23)); + } + } } From 99db43eff711c08d4ad774cee03ed9060a65fcd9 Mon Sep 17 00:00:00 2001 From: ac892247 Date: Tue, 17 Jun 2025 15:16:01 +0200 Subject: [PATCH 3/3] simplify connection timeout Signed-off-by: ac892247 --- .../apiml/gateway/config/NettyRoutingFilterApiml.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/NettyRoutingFilterApiml.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/NettyRoutingFilterApiml.java index 9c403c40f1..c2b40060d7 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/config/NettyRoutingFilterApiml.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/config/NettyRoutingFilterApiml.java @@ -73,17 +73,11 @@ protected HttpClient getHttpClient(Route route, ServerWebExchange exchange) { var responseTimeoutAttr = route.getMetadata().get("apiml.responseTimeout"); var responseTimeoutResult = responseTimeoutAttr != null ? Long.parseLong(String.valueOf(responseTimeoutAttr)) : requestTimeout; + var connectTimeoutResult = connectTimeoutAttr != null ? getInteger(connectTimeoutAttr) : requestTimeout; httpClient = httpClient - .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, requestTimeout) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeoutResult) .responseTimeout(Duration.ofMillis(responseTimeoutResult)); - if (connectTimeoutAttr != null) { - // if there is configured timeout, respect it - Integer connectTimeout = getInteger(connectTimeoutAttr); - httpClient = httpClient - .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout); - } - return httpClient; }