From 6a1d293b1f763ea7ce89ee26e514f9754c116d67 Mon Sep 17 00:00:00 2001 From: Matt Nichols Date: Tue, 15 Aug 2023 08:34:26 -0600 Subject: [PATCH 1/2] feat: add basic compression to session puts Rewrote in rc branch to remove very large file, huge.txt. --- build.gradle | 2 +- .../compression/CompressionService.java | 9 ++++ .../CompressionServiceException.java | 10 ++++ .../com/mx/path/core/context/Session.java | 52 ++++++++++++++++--- .../mx/path/core/context/SessionTest.groovy | 43 ++++++++++++++- 5 files changed, 106 insertions(+), 10 deletions(-) create mode 100644 common/src/main/java/com/mx/path/core/common/compression/CompressionService.java create mode 100644 common/src/main/java/com/mx/path/core/common/compression/CompressionServiceException.java diff --git a/build.gradle b/build.gradle index 6aed8a24..8395c56d 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { id "io.github.gradle-nexus.publish-plugin" version "1.1.0" } -version "3.6.0" // x-release-please-version +version "3.7.0-SNAPSHOT" // x-release-please-version def platformProject = "platform" def publishedProjects = [ diff --git a/common/src/main/java/com/mx/path/core/common/compression/CompressionService.java b/common/src/main/java/com/mx/path/core/common/compression/CompressionService.java new file mode 100644 index 00000000..f08e12da --- /dev/null +++ b/common/src/main/java/com/mx/path/core/common/compression/CompressionService.java @@ -0,0 +1,9 @@ +package com.mx.path.core.common.compression; + +public interface CompressionService { + boolean isCompressed(String data); + + String compress(String data); + + String decompress(String data); +} diff --git a/common/src/main/java/com/mx/path/core/common/compression/CompressionServiceException.java b/common/src/main/java/com/mx/path/core/common/compression/CompressionServiceException.java new file mode 100644 index 00000000..3551d3be --- /dev/null +++ b/common/src/main/java/com/mx/path/core/common/compression/CompressionServiceException.java @@ -0,0 +1,10 @@ +package com.mx.path.core.common.compression; + +import com.mx.path.core.common.exception.PathSystemException; + +public class CompressionServiceException extends PathSystemException { + + public CompressionServiceException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/context/src/main/java/com/mx/path/core/context/Session.java b/context/src/main/java/com/mx/path/core/context/Session.java index a3cf95d5..07f9574f 100644 --- a/context/src/main/java/com/mx/path/core/context/Session.java +++ b/context/src/main/java/com/mx/path/core/context/Session.java @@ -15,6 +15,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.mx.path.core.common.compression.CompressionService; import com.mx.path.core.common.lang.Strings; import com.mx.path.core.common.security.EncryptionService; import com.mx.path.core.common.serialization.LocalDateDeserializer; @@ -39,6 +40,8 @@ public class Session implements SessionInfo { private static Supplier encryptionServiceSupplier = new EncryptionServiceSupplier(); + private static Supplier compressionServiceSupplier = null; + private static final int DEFAULT_SESSION_EXPIRATION_MINUTES = 30; private static Duration defaultSessionExpiration = Duration.ofMinutes(DEFAULT_SESSION_EXPIRATION_MINUTES); @@ -66,6 +69,22 @@ public static EncryptionService getEncryptionService() { return encryptionServiceSupplier.get(); } + public static CompressionService getCompressionService() { + if (compressionServiceSupplier == null) { + return null; + } + + return compressionServiceSupplier.get(); + } + + public static void setCompressionServiceSupplier(Supplier supplier) { + compressionServiceSupplier = supplier; + } + + static void resetCompressionService() { + compressionServiceSupplier = null; + } + public static void setDefaultSessionExpiration(Duration sessionExpiry) { defaultSessionExpiration = sessionExpiry; } @@ -522,7 +541,7 @@ public final void delete(ScopeKeyGenerator scope, String key) { @Deprecated public final String get(String key) { String value = getRepository().getValue(this, key); - return decryptValue(value); + return decompressValue(decryptValue(value)); } /** @@ -533,7 +552,7 @@ public final String get(String key) { */ public final String get(ScopeKeyGenerator scope, String key) { String value = getRepository().getValue(this, buildScopeKey(scope, key)); - return decryptValue(value); + return decompressValue(decryptValue(value)); } /** @@ -564,7 +583,7 @@ public final T getObj(ScopeKeyGenerator scope, String key, Class klass) { */ @Deprecated public final void put(String key, String value) { - getRepository().saveValue(this, key, value); + getRepository().saveValue(this, key, compressValue(value)); } /** @@ -572,10 +591,9 @@ public final void put(String key, String value) { * @param scope * @param key * @param value - * */ public final void put(ScopeKeyGenerator scope, String key, String value) { - getRepository().saveValue(this, buildScopeKey(scope, key), value); + getRepository().saveValue(this, buildScopeKey(scope, key), compressValue(value)); } /** @@ -585,7 +603,7 @@ public final void put(ScopeKeyGenerator scope, String key, String value) { * @param obj */ public final void putObj(ScopeKeyGenerator scope, String key, Object obj) { - put(scope, key, gson.toJson(obj)); + getRepository().saveValue(this, buildScopeKey(scope, key), compressValue(gson.toJson(obj))); } /** @@ -598,7 +616,7 @@ public final void putObj(ScopeKeyGenerator scope, String key, Object obj) { * @param value */ public final void sput(ScopeKeyGenerator scope, String key, String value) { - put(scope, key, encryptValue(value)); + getRepository().saveValue(this, buildScopeKey(scope, key), encryptValue(compressValue(value))); } /** @@ -611,7 +629,7 @@ public final void sput(ScopeKeyGenerator scope, String key, String value) { * @param value */ public final void sputObj(ScopeKeyGenerator scope, String key, Object value) { - sput(scope, key, gson.toJson(value)); + getRepository().saveValue(this, buildScopeKey(scope, key), encryptValue(compressValue(gson.toJson(value)))); } /** @@ -629,6 +647,24 @@ private String buildScopeKey(ScopeKeyGenerator scope, String key) { return scope.generate() + "." + key; } + private String compressValue(String value) { + CompressionService compressionService = getCompressionService(); + if (compressionService == null) { + return value; + } + + return compressionService.compress(value); + } + + private String decompressValue(String value) { + CompressionService compressionService = getCompressionService(); + if (compressionService == null) { + return value; + } + + return compressionService.decompress(value); + } + private String decryptValue(String value) { if (Objects.isNull(value) || Strings.isBlank(value)) { return value; diff --git a/context/src/test/groovy/com/mx/path/core/context/SessionTest.groovy b/context/src/test/groovy/com/mx/path/core/context/SessionTest.groovy index aa5f68df..f4f9c4f0 100644 --- a/context/src/test/groovy/com/mx/path/core/context/SessionTest.groovy +++ b/context/src/test/groovy/com/mx/path/core/context/SessionTest.groovy @@ -1,9 +1,12 @@ package com.mx.path.core.context +import static org.mockito.Mockito.mock import static org.mockito.Mockito.spy import java.time.Duration +import java.util.function.Supplier +import com.mx.path.core.common.compression.CompressionService import com.mx.path.core.common.security.EncryptionService import com.mx.path.core.context.store.SessionRepository import com.mx.testing.HashSessionRepository @@ -29,6 +32,7 @@ class SessionTest extends Specification { def cleanup() { Session.setRepositorySupplier(null) Session.setEncryptionServiceSupplier(null) + Session.resetCompressionService() Session.clearSession() Session.setDefaultSessionExpiration(Duration.ofMinutes(30)) } @@ -128,6 +132,25 @@ class SessionTest extends Specification { 1 * repository.saveValue(subject, "scope.key1", "value1") } + def "putStoresInRepository with compressionService"() { + given: + def compressionService = Stub(CompressionService) + + def Supplier supplier = { -> compressionService } + Session.setCompressionServiceSupplier(supplier) + + and: + compressionService.compress("value1") >> "compressed:value1" + + when: + subject.put("key1", "value1") + subject.put(TestScope.Key, "key1", "value1") + + then: + 1 * repository.saveValue(subject, "key1", "compressed:value1") + 1 * repository.saveValue(subject, "scope.key1", "compressed:value1") + } + def "deleteRemovesFromRepository"() { when: subject.delete("key1") @@ -173,7 +196,6 @@ class SessionTest extends Specification { 1 * listener2.beforeSave(session) } - def "clearSessionRemovesListeners"() { given: SessionEventListener listener = Mock() @@ -336,6 +358,25 @@ class SessionTest extends Specification { 1 * encryptionService.decrypt("encrypted:v1:(!ph3%") >> "plaintext" } + def "setCompressionServiceSupplier"() { + given: + def compressionService = mock(CompressionService) + + def Supplier supplier = new Supplier() { + @Override + CompressionService get() { + return compressionService + } + } + + when: + Session.setCompressionServiceSupplier(supplier) + def result = Session.getCompressionService() + + then: + result == compressionService + } + def "encryptsEmail"() { given: subject = spy(new Session()) From 8f88af1a4b8c3c35077cc061429e6ce7d25048af Mon Sep 17 00:00:00 2001 From: Matt Nichols Date: Tue, 15 Aug 2023 08:45:36 -0600 Subject: [PATCH 2/2] feat: add arbitrary rrequest context header forwarding --- .../connect/filter/HeaderForwarderFilter.java | 30 ++++++++++++ .../filter/HeaderForwarderFilterTest.groovy | 49 +++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 gateway/src/main/java/com/mx/path/gateway/connect/filter/HeaderForwarderFilter.java create mode 100644 gateway/src/test/groovy/com/mx/path/gateway/connect/filter/HeaderForwarderFilterTest.groovy diff --git a/gateway/src/main/java/com/mx/path/gateway/connect/filter/HeaderForwarderFilter.java b/gateway/src/main/java/com/mx/path/gateway/connect/filter/HeaderForwarderFilter.java new file mode 100644 index 00000000..bb69bfe2 --- /dev/null +++ b/gateway/src/main/java/com/mx/path/gateway/connect/filter/HeaderForwarderFilter.java @@ -0,0 +1,30 @@ +package com.mx.path.gateway.connect.filter; + +import com.mx.path.core.common.connect.Request; +import com.mx.path.core.common.connect.RequestFilterBase; +import com.mx.path.core.common.connect.Response; +import com.mx.path.core.context.RequestContext; +import com.mx.path.core.context.UpstreamRequestConfiguration; + +/** + * Request Filter that adds forwarded headers to request + */ +public class HeaderForwarderFilter extends RequestFilterBase { + + @Override + public final void execute(Request request, Response response) { + UpstreamRequestConfiguration upstreamRequestConfiguration = null; + + if (RequestContext.current() != null) { + upstreamRequestConfiguration = RequestContext.current().getUpstreamRequestConfiguration(); + } + + if (upstreamRequestConfiguration != null && upstreamRequestConfiguration.getForwardedRequestHeaders() != null) { + upstreamRequestConfiguration.getForwardedRequestHeaders().forEach((key, value) -> { + request.withHeader(key, value.toString()); + }); + } + + next(request, response); + } +} diff --git a/gateway/src/test/groovy/com/mx/path/gateway/connect/filter/HeaderForwarderFilterTest.groovy b/gateway/src/test/groovy/com/mx/path/gateway/connect/filter/HeaderForwarderFilterTest.groovy new file mode 100644 index 00000000..0f748bdd --- /dev/null +++ b/gateway/src/test/groovy/com/mx/path/gateway/connect/filter/HeaderForwarderFilterTest.groovy @@ -0,0 +1,49 @@ +package com.mx.path.gateway.connect.filter + +import static org.mockito.Mockito.mock + +import com.mx.path.core.common.connect.Request +import com.mx.path.core.common.connect.RequestFilter +import com.mx.path.core.common.connect.Response +import com.mx.path.core.context.RequestContext +import com.mx.path.core.context.UpstreamRequestConfiguration + +import spock.lang.Specification + +class HeaderForwarderFilterTest extends Specification { + class TestRequest extends Request { + TestRequest(RequestFilter filterChain) { + super(filterChain) + } + + @Override + TestResponse execute() { + return null + } + } + + class TestResponse extends Response { + } + + def "forwards all headers in current RequestContext forwardedHeader"() { + given: + def requestFilter = mock(RequestFilter) + def subject = new HeaderForwarderFilter() + def request = new TestRequest(requestFilter) + def response = new TestResponse() + RequestContext.builder() + .upstreamRequestConfiguration(UpstreamRequestConfiguration.builder() + .forwardedHeader("mx_forwarded_important_header", "12345") + .forwardedHeader("mx-forwarded-important-header", "12345") + .build()) + .build() + .register() + + when: + subject.execute(request, response) + + then: + request.headers.containsKey("mx_forwarded_important_header") + request.headers.containsKey("mx-forwarded-important-header") + } +}