Skip to content

Commit cc612e5

Browse files
authored
feat: Provide compression for specific paths only. (#1755)
* Limit to Accept-Encoding containing gzip Limit to specific routes. Signed-off-by: Jakub Balhar <jakub@balhar.net> * Test new and old paths alike. Test noncompressed paths Signed-off-by: Jakub Balhar <jakub@balhar.net> * Refactor the Metadata preparation. Signed-off-by: Jakub Balhar <jakub@balhar.net> * Add Acceptance Tests for the Compression per path Signed-off-by: Jakub Balhar <jakub@balhar.net> * Use the provided fulent API Signed-off-by: Jakub Balhar <jakub@balhar.net> * Correctly handle relative paths Signed-off-by: Jakub Balhar <jakub@balhar.net> * Show debug information from the Gradle. Signed-off-by: Jakub Balhar <jakub@balhar.net> * Try just building Signed-off-by: Jakub Balhar <jakub@balhar.net> * Info instead of debug Signed-off-by: Jakub Balhar <jakub@balhar.net> * Get the info about tests Signed-off-by: Jakub Balhar <jakub@balhar.net> * Remove the cached packages. Signed-off-by: Jakub Balhar <jakub@balhar.net> * Limit the amount of time taken Signed-off-by: Jakub Balhar <jakub@balhar.net> * Provide scan after the build Signed-off-by: Jakub Balhar <jakub@balhar.net> * Verify the build of gateway service Signed-off-by: Jakub Balhar <jakub@balhar.net> * Try removing the functional tests Signed-off-by: Jakub Balhar <jakub@balhar.net> * Return one functional test Signed-off-by: Jakub Balhar <jakub@balhar.net> * Return the tests Signed-off-by: Jakub Balhar <jakub@balhar.net> * Limit the tests to the default configuration Signed-off-by: Jakub Balhar <jakub@balhar.net> * Return the previous setup Signed-off-by: Jakub Balhar <jakub@balhar.net> * Test response isn't compressed when client doesn't accept gzip encoding Signed-off-by: Jakub Balhar <jakub@balhar.net>
1 parent e2d95df commit cc612e5

File tree

13 files changed

+462
-211
lines changed

13 files changed

+462
-211
lines changed

.github/workflows/ci-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ jobs:
4040
${{ runner.OS }}-node001-
4141
- name: Build with Gradle
4242
run: >
43-
./gradlew build runStartUpCheck -Partifactory_user=${{ secrets.ARTIFACTORY_USERNAME }} -Partifactory_password=${{ secrets.ARTIFACTORY_PASSWORD }}
43+
./gradlew build runStartUpCheck --info -Partifactory_user=${{ secrets.ARTIFACTORY_USERNAME }} -Partifactory_password=${{ secrets.ARTIFACTORY_PASSWORD }}
4444
- name: Store results
4545
uses: actions/upload-artifact@v2
4646
if: always()

gateway-service/src/main/java/org/zowe/apiml/gateway/filters/pre/PerServiceGZipFilter.java

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.springframework.cloud.client.ServiceInstance;
1414
import org.springframework.cloud.client.discovery.DiscoveryClient;
1515
import org.springframework.stereotype.Component;
16+
import org.springframework.util.AntPathMatcher;
1617
import org.springframework.web.filter.OncePerRequestFilter;
1718
import org.zowe.apiml.gzip.GZipResponseUtils;
1819
import org.zowe.apiml.gzip.GZipResponseWrapper;
@@ -24,6 +25,8 @@
2425
import java.io.ByteArrayOutputStream;
2526
import java.io.IOException;
2627
import java.util.List;
28+
import java.util.Map;
29+
import java.util.Optional;
2730
import java.util.zip.GZIPOutputStream;
2831

2932
/**
@@ -81,26 +84,78 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
8184

8285
}
8386

87+
/**
88+
* The compression is requested when the Client specifies the Accept-Encoding header and there is valid Instance for
89+
* the service and the Instance specifies that it is interested in compression and either specify no pattern for the
90+
* and matcher or the URL matches the path.
91+
*
92+
* @param request The request to verify
93+
*/
94+
boolean requiresCompression(HttpServletRequest request) {
95+
String requestUri = request.getRequestURI();
96+
boolean acceptsCompression = requestAcceptsCompression(request);
97+
Optional<ServiceInstance> validInstance = getInstanceInfoForUri(requestUri);
98+
if (!validInstance.isPresent()) {
99+
return false;
100+
}
101+
boolean serviceRequestsCompression = serviceOnRouteRequestsCompression(validInstance.get(), requestUri);
84102

85-
private boolean requiresCompression(HttpServletRequest request) {
86-
String[] uriParts = request.getRequestURI().split("/");
103+
return acceptsCompression && serviceRequestsCompression;
104+
}
105+
106+
// Verify non versioned APIs
107+
Optional<ServiceInstance> getInstanceInfoForUri(String requestUri) {
108+
// Compress only if there is valid instance with relevant metadata.
109+
String[] uriParts = requestUri.split("/");
87110
List<ServiceInstance> instances;
88111
if (uriParts.length < 2) {
89-
return false;
112+
return Optional.empty();
90113
}
91114
if ("api".equals(uriParts[1]) || "ui".equals(uriParts[1])) {
92115
if (uriParts.length < 4) {
93-
return false;
116+
return Optional.empty();
94117
}
95118
instances = discoveryClient.getInstances(uriParts[3]);
96119
} else {
97120
instances = discoveryClient.getInstances(uriParts[1]);
98121
}
99122
if (instances == null || instances.isEmpty()) {
100-
return false;
123+
return Optional.empty();
101124
}
102-
return "true".equals(instances.get(0).getMetadata().get("apiml.response.compress"));
125+
126+
return Optional.of(instances.get(0));
103127
}
104128

129+
boolean requestAcceptsCompression(HttpServletRequest request) {
130+
String encodingHeader = request.getHeader("Accept-Encoding");
131+
if (encodingHeader == null) {
132+
return false;
133+
} else return encodingHeader.contains("gzip");
134+
}
105135

136+
boolean serviceOnRouteRequestsCompression(ServiceInstance instance, String requestUri) {
137+
Map<String, String> metadata = instance.getMetadata();
138+
boolean allowCompressionForService = "true".equals(metadata.get("apiml.response.compress"));
139+
if (!allowCompressionForService) {
140+
return false;
141+
}
142+
143+
String routesToCompress = metadata.get("apiml.response.compressRoutes");
144+
if (routesToCompress == null) {
145+
return true;
146+
}
147+
148+
String[] setOfRoutesToMatch = routesToCompress.split(",");
149+
AntPathMatcher matcher = new AntPathMatcher();
150+
for (String pattern: setOfRoutesToMatch) {
151+
if (!pattern.startsWith("/")) {
152+
pattern = "/" + pattern;
153+
}
154+
if (matcher.match(pattern, requestUri)) {
155+
return true;
156+
}
157+
}
158+
159+
return false;
160+
}
106161
}

gateway-service/src/main/resources/application.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ server:
101101
keyStoreType: ${server.ssl.keyStoreType:PKCS12}
102102
keyStore: ${server.ssl.keyStore:keystore}
103103
trustStoreType: ${server.ssl.trustStoreType:PKCS12}
104-
trustStore: ${server.ssl.trustStore:trustore}
104+
trustStore: ${server.ssl.trustStore:truststore}
105105
keyStorePassword: ${server.ssl.keyStorePassword:password}
106106
trustStorePassword: ${server.ssl.trustStorePassword:password}
107107
clientAuth: ${server.ssl.clientAuth}

gateway-service/src/test/java/org/zowe/apiml/acceptance/CompressPerServiceTest.java

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@
1010
package org.zowe.apiml.acceptance;
1111

1212
import org.hamcrest.Matchers;
13+
import org.junit.jupiter.api.BeforeEach;
14+
import org.junit.jupiter.api.Nested;
1315
import org.junit.jupiter.api.Test;
1416
import org.zowe.apiml.acceptance.common.AcceptanceTest;
1517
import org.zowe.apiml.acceptance.common.AcceptanceTestWithTwoServices;
18+
import org.zowe.apiml.acceptance.netflix.MetadataBuilder;
1619

1720
import java.io.IOException;
1821

@@ -22,18 +25,65 @@
2225

2326
@AcceptanceTest
2427
public class CompressPerServiceTest extends AcceptanceTestWithTwoServices {
28+
@Nested
29+
class GivenServiceAcceptsCompression {
30+
@Nested
31+
class ForAllPaths {
32+
@Test
33+
void thenResponseIsCompressed() throws IOException {
34+
mockValid200HttpResponse();
35+
applicationRegistry.setCurrentApplication(serviceWithCustomConfiguration.getId());
36+
discoveryClient.createRefreshCacheEvent();
37+
given()
38+
.when()
39+
.get(basePath + serviceWithCustomConfiguration.getPath())
40+
.then()
41+
.statusCode(is(SC_OK))
42+
.header("Content-Encoding", is("gzip"));
43+
}
44+
}
2545

26-
@Test
27-
void givenServiceAcceptsCompression_thenResponseIsCompressed() throws IOException {
28-
mockValid200HttpResponse();
29-
applicationRegistry.setCurrentApplication(serviceWithCustomConfiguration.getId());
30-
discoveryClient.createRefreshCacheEvent();
31-
given()
32-
.when()
33-
.get(basePath + serviceWithCustomConfiguration.getPath())
34-
.then()
35-
.statusCode(is(SC_OK))
36-
.header("Content-Encoding", is("gzip"));
46+
@Nested
47+
class ForSomePaths {
48+
@BeforeEach
49+
void prepareServices() {
50+
// Make sure customConfiguration service has custom metadata for this set.
51+
applicationRegistry.clearApplications();
52+
MetadataBuilder customBuilder = MetadataBuilder.customInstance()
53+
.withCompressionPath("/**/test")
54+
.withCompression(true);
55+
56+
applicationRegistry.addApplication(serviceWithDefaultConfiguration, MetadataBuilder.defaultInstance(), false);
57+
applicationRegistry.addApplication(serviceWithCustomConfiguration, customBuilder, true);
58+
applicationRegistry.setCurrentApplication(serviceWithDefaultConfiguration.getId());
59+
}
60+
61+
@Test
62+
void forValidPath_thenResponseIsCompressed() throws IOException {
63+
mockValid200HttpResponse();
64+
applicationRegistry.setCurrentApplication(serviceWithCustomConfiguration.getId());
65+
discoveryClient.createRefreshCacheEvent();
66+
given()
67+
.when()
68+
.get(basePath + serviceWithCustomConfiguration.getPath())
69+
.then()
70+
.statusCode(is(SC_OK))
71+
.header("Content-Encoding", is("gzip"));
72+
}
73+
74+
@Test
75+
void forInvalidPath_thenResponseIsNotCompressed() throws IOException {
76+
mockValid200HttpResponse();
77+
applicationRegistry.setCurrentApplication(serviceWithDefaultConfiguration.getId());
78+
discoveryClient.createRefreshCacheEvent();
79+
given()
80+
.when()
81+
.get(basePath + serviceWithCustomConfiguration.getPath() + "/noncompressed")
82+
.then()
83+
.statusCode(is(SC_OK))
84+
.header("Content-Encoding", Matchers.nullValue());
85+
}
86+
}
3787
}
3888

3989
@Test

gateway-service/src/test/java/org/zowe/apiml/acceptance/DeterministicUserBasedRoutingTest.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.springframework.beans.factory.annotation.Autowired;
2424
import org.zowe.apiml.acceptance.common.AcceptanceTest;
2525
import org.zowe.apiml.acceptance.common.AcceptanceTestWithTwoServices;
26+
import org.zowe.apiml.acceptance.netflix.MetadataBuilder;
2627
import org.zowe.apiml.gateway.cache.LoadBalancerCache;
2728

2829
import java.io.IOException;
@@ -45,8 +46,13 @@ class DeterministicUserBasedRoutingTest extends AcceptanceTestWithTwoServices {
4546
public void prepareApplications() {
4647
cache.getLocalCache().clear();
4748
applicationRegistry.clearApplications();
48-
applicationRegistry.addApplication(serviceWithDefaultConfiguration, false, true, false, "authentication", false, false);
49-
applicationRegistry.addApplication(serviceWithCustomConfiguration, true, false, true, "authentication", false, false);
49+
MetadataBuilder defaultBuilder = MetadataBuilder.defaultInstance();
50+
defaultBuilder.withLoadBalancerStrategy("authentication");
51+
MetadataBuilder customBuilder = MetadataBuilder.customInstance();
52+
customBuilder.withLoadBalancerStrategy("authentication");
53+
54+
applicationRegistry.addApplication(serviceWithDefaultConfiguration, defaultBuilder, false);
55+
applicationRegistry.addApplication(serviceWithCustomConfiguration, customBuilder, true);
5056
}
5157

5258
@Nested

0 commit comments

Comments
 (0)