From 05623feed391866f7a548881c98a54bc2630c3d2 Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Tue, 21 Oct 2025 15:25:23 +0200 Subject: [PATCH 1/8] fix: decodierung of body content for if, call, setHeader. Make jsonpath accept arrays as input. --- .../extractors/ApiKeyExpressionExtractor.java | 3 ++- .../balancer/PolyglotSessionIdExtractor.java | 4 +++- .../interceptor/flow/CallInterceptor.java | 2 +- .../core/interceptor/flow/ForInterceptor.java | 3 ++- .../core/interceptor/flow/IfInterceptor.java | 3 ++- .../core/interceptor/flow/choice/Case.java | 3 ++- .../idempotency/IdempotencyInterceptor.java | 3 ++- .../ratelimit/RateLimitInterceptor.java | 3 ++- .../core/lang/ExchangeExpression.java | 2 +- .../membrane/core/lang/ScriptingUtils.java | 1 + .../core/lang/TemplateExchangeExpression.java | 3 ++- .../jsonpath/JsonpathExchangeExpression.java | 2 +- .../core/lang/spel/spelable/SpELBody.java | 5 +++++ .../core/openapi/serviceproxy/APIProxy.java | 3 ++- .../flow/ChooseInterceptorTest.java | 6 +++--- .../flow/invocation/FlowTestInterceptors.java | 2 +- .../lang/SetPropertyInterceptorSpELTest.java | 14 +++++++++++++- .../lang/AbstractExchangeExpressionTest.java | 10 ++++------ .../JsonpathExchangeExpressionTest.java | 19 ++++++++++++++++--- .../APIProxyKeyComplexMatchTest.java | 7 ++++--- 20 files changed, 69 insertions(+), 29 deletions(-) diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/apikey/extractors/ApiKeyExpressionExtractor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/apikey/extractors/ApiKeyExpressionExtractor.java index 80e4ab7fbb..9303c850df 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/apikey/extractors/ApiKeyExpressionExtractor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/apikey/extractors/ApiKeyExpressionExtractor.java @@ -25,6 +25,7 @@ import static com.predic8.membrane.core.interceptor.Interceptor.Flow.REQUEST; import static com.predic8.membrane.core.lang.ExchangeExpression.Language.SPEL; +import static com.predic8.membrane.core.lang.ExchangeExpression.expression; import static com.predic8.membrane.core.security.ApiKeySecurityScheme.In.EXPRESSION; /** @@ -53,7 +54,7 @@ public class ApiKeyExpressionExtractor implements ApiKeyExtractor, Polyglot { @Override public void init(Router router) { - exchangeExpression = ExchangeExpression.newInstance(router, language, expression); + exchangeExpression = expression(router, language, expression); } @Override diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/balancer/PolyglotSessionIdExtractor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/balancer/PolyglotSessionIdExtractor.java index f3ec2b41c1..9bd3c7de3d 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/balancer/PolyglotSessionIdExtractor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/balancer/PolyglotSessionIdExtractor.java @@ -24,6 +24,8 @@ import com.predic8.membrane.core.lang.ExchangeExpression; import com.predic8.membrane.core.lang.ExchangeExpression.Language; +import static com.predic8.membrane.core.lang.ExchangeExpression.expression; + @MCElement(name = "sessionIdExtractor") public class PolyglotSessionIdExtractor extends AbstractXmlElement implements SessionIdExtractor, Polyglot { @@ -33,7 +35,7 @@ public class PolyglotSessionIdExtractor extends AbstractXmlElement implements Se public void init(Router router) { if (sessionSource != null && !sessionSource.isEmpty()) { - exchangeExpression = ExchangeExpression.newInstance(router, language, sessionSource); + exchangeExpression = expression(router, language, sessionSource); } } diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/CallInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/CallInterceptor.java index a8ed8b9d0d..f4ef0d5df5 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/CallInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/CallInterceptor.java @@ -87,7 +87,7 @@ private Outcome handleInternal(Exchange exc) { } try { - exc.getRequest().setBodyContent(newExc.getResponse().getBody().getContent()); + exc.getRequest().setBodyContent(newExc.getResponse().getBodyAsStringDecoded().getBytes()); copyHeadersFromResponseToRequest(newExc, exc); return CONTINUE; } catch (Exception e) { diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/ForInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/ForInterceptor.java index 49ad0385bf..f958aa8f3e 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/ForInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/ForInterceptor.java @@ -29,6 +29,7 @@ import static com.predic8.membrane.core.interceptor.Outcome.ABORT; import static com.predic8.membrane.core.interceptor.Outcome.CONTINUE; import static com.predic8.membrane.core.lang.ExchangeExpression.Language.SPEL; +import static com.predic8.membrane.core.lang.ExchangeExpression.expression; /** * @description Iterates over a collection extracted from the Exchange and applies @@ -56,7 +57,7 @@ public class ForInterceptor extends AbstractFlowWithChildrenInterceptor { public void init() { super.init(); try { - exchangeExpression = ExchangeExpression.newInstance(router, language, in); + exchangeExpression = expression(router, language, in); } catch (ConfigurationException ce) { throw new ConfigurationException(ce.getMessage() + """ diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/IfInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/IfInterceptor.java index 881bf33231..280cc1e0db 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/IfInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/IfInterceptor.java @@ -26,6 +26,7 @@ import static com.predic8.membrane.core.interceptor.Outcome.ABORT; import static com.predic8.membrane.core.interceptor.Outcome.*; import static com.predic8.membrane.core.lang.ExchangeExpression.Language.*; +import static com.predic8.membrane.core.lang.ExchangeExpression.expression; /** * @description

@@ -52,7 +53,7 @@ public IfInterceptor() { @Override public void init() { super.init(); - exchangeExpression = ExchangeExpression.newInstance(router, language, test); + exchangeExpression = expression(router, language, test); } @Override diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/choice/Case.java b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/choice/Case.java index b71b80dfe6..572610b384 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/choice/Case.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/choice/Case.java @@ -25,6 +25,7 @@ import org.slf4j.LoggerFactory; import static com.predic8.membrane.core.lang.ExchangeExpression.Language.SPEL; +import static com.predic8.membrane.core.lang.ExchangeExpression.expression; @MCElement(name = "case", topLevel = false) public class Case extends InterceptorContainer { @@ -36,7 +37,7 @@ public class Case extends InterceptorContainer { private ExchangeExpression exchangeExpression; public void init(Router router) { - exchangeExpression = ExchangeExpression.newInstance(router, language, test); + exchangeExpression = expression(router, language, test); } boolean evaluate(Exchange exc, Flow flow) { diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/idempotency/IdempotencyInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/idempotency/IdempotencyInterceptor.java index 6fe142e3fe..4d4d8ad1fc 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/idempotency/IdempotencyInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/idempotency/IdempotencyInterceptor.java @@ -32,6 +32,7 @@ import static com.predic8.membrane.core.interceptor.Outcome.ABORT; import static com.predic8.membrane.core.interceptor.Outcome.CONTINUE; import static com.predic8.membrane.core.lang.ExchangeExpression.Language.SPEL; +import static com.predic8.membrane.core.lang.ExchangeExpression.expression; /** * @description

Prevents duplicate request processing based on a dynamic idempotency key.

@@ -54,7 +55,7 @@ public class IdempotencyInterceptor extends AbstractInterceptor { @Override public void init() { super.init(); - exchangeExpression = ExchangeExpression.newInstance(router, language, key); + exchangeExpression = expression(router, language, key); processedKeys = CacheBuilder.newBuilder() .maximumSize(10000) .expireAfterWrite(expiration, TimeUnit.SECONDS) diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/ratelimit/RateLimitInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/ratelimit/RateLimitInterceptor.java index c89b52315c..71977c7fef 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/ratelimit/RateLimitInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/ratelimit/RateLimitInterceptor.java @@ -30,6 +30,7 @@ import static com.predic8.membrane.core.interceptor.Interceptor.Flow.REQUEST; import static com.predic8.membrane.core.interceptor.Interceptor.Flow.Set.*; import static com.predic8.membrane.core.interceptor.Outcome.*; +import static com.predic8.membrane.core.lang.ExchangeExpression.expression; import static com.predic8.membrane.core.util.HttpUtil.*; import static java.lang.String.*; @@ -93,7 +94,7 @@ protected ExchangeExpression getExchangeExpression() { // If there is no expression use the client IP if (expression.isEmpty()) return null; - return ExchangeExpression.newInstance(router, language, expression); + return expression(router, language, expression); } @Override diff --git a/core/src/main/java/com/predic8/membrane/core/lang/ExchangeExpression.java b/core/src/main/java/com/predic8/membrane/core/lang/ExchangeExpression.java index e6f1651c62..f52f3175c2 100644 --- a/core/src/main/java/com/predic8/membrane/core/lang/ExchangeExpression.java +++ b/core/src/main/java/com/predic8/membrane/core/lang/ExchangeExpression.java @@ -48,7 +48,7 @@ enum Language {GROOVY, SPEL, XPATH, JSONPATH} */ T evaluate(Exchange exchange, Interceptor.Flow flow, Class type) throws ExchangeExpressionException; - static ExchangeExpression newInstance(Router router, Language language, String expression) { + static ExchangeExpression expression(Router router, Language language, String expression) { return switch (language) { case GROOVY -> new GroovyExchangeExpression(router, expression); case SPEL -> new SpELExchangeExpression(expression,null); diff --git a/core/src/main/java/com/predic8/membrane/core/lang/ScriptingUtils.java b/core/src/main/java/com/predic8/membrane/core/lang/ScriptingUtils.java index 9932e0eb36..f0dd269392 100644 --- a/core/src/main/java/com/predic8/membrane/core/lang/ScriptingUtils.java +++ b/core/src/main/java/com/predic8/membrane/core/lang/ScriptingUtils.java @@ -71,6 +71,7 @@ public static HashMap createParameterBindings(Router router, Exc if (msg != null) { params.put("message", msg); + params.put("body", msg.getBodyAsStringDecoded()); params.put("header", msg.getHeader()); params.put("headers", msg.getHeader()); if (includeJsonObject) { diff --git a/core/src/main/java/com/predic8/membrane/core/lang/TemplateExchangeExpression.java b/core/src/main/java/com/predic8/membrane/core/lang/TemplateExchangeExpression.java index 87d5d521d2..1240c5840c 100644 --- a/core/src/main/java/com/predic8/membrane/core/lang/TemplateExchangeExpression.java +++ b/core/src/main/java/com/predic8/membrane/core/lang/TemplateExchangeExpression.java @@ -24,6 +24,7 @@ import java.util.regex.*; import static com.predic8.membrane.core.lang.ExchangeExpression.Language.*; +import static com.predic8.membrane.core.lang.ExchangeExpression.expression; public class TemplateExchangeExpression extends AbstractExchangeExpression { @@ -95,7 +96,7 @@ protected static List parseTokens(Router router, Language language, Strin } String expr = m.group(3); if (expr != null) { - tokens.add(new Expression(ExchangeExpression.newInstance(router, language, expr))); + tokens.add(new Expression(expression(router, language, expr))); } } log.debug("Tokens: {}", tokens); diff --git a/core/src/main/java/com/predic8/membrane/core/lang/jsonpath/JsonpathExchangeExpression.java b/core/src/main/java/com/predic8/membrane/core/lang/jsonpath/JsonpathExchangeExpression.java index c77fe98bb2..a3b82117c3 100644 --- a/core/src/main/java/com/predic8/membrane/core/lang/jsonpath/JsonpathExchangeExpression.java +++ b/core/src/main/java/com/predic8/membrane/core/lang/jsonpath/JsonpathExchangeExpression.java @@ -140,6 +140,6 @@ private boolean convertToBoolean(Object o) { } private Object execute(Exchange exchange, Flow flow) throws IOException { - return JsonPath.read(om.readValue(exchange.getMessage(flow).getBodyAsStream(), Map.class), expression); + return JsonPath.read(om.readValue(exchange.getMessage(flow).getBodyAsStreamDecoded(), Object.class), expression); } } diff --git a/core/src/main/java/com/predic8/membrane/core/lang/spel/spelable/SpELBody.java b/core/src/main/java/com/predic8/membrane/core/lang/spel/spelable/SpELBody.java index 96343fa9b2..ffc9669a3c 100644 --- a/core/src/main/java/com/predic8/membrane/core/lang/spel/spelable/SpELBody.java +++ b/core/src/main/java/com/predic8/membrane/core/lang/spel/spelable/SpELBody.java @@ -29,4 +29,9 @@ public SpELBody(Message msg) { public Message getMessage() { return message; } + + @Override + public String toString() { + return message.getBodyAsStringDecoded(); + } } diff --git a/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/APIProxy.java b/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/APIProxy.java index c357baf6a2..3e6a743508 100644 --- a/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/APIProxy.java +++ b/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/APIProxy.java @@ -35,6 +35,7 @@ import java.util.*; import static com.predic8.membrane.core.lang.ExchangeExpression.Language.SPEL; +import static com.predic8.membrane.core.lang.ExchangeExpression.expression; /** * @description The api proxy extends the serviceProxy with API related functions like OpenAPI support and path parameters. @@ -82,7 +83,7 @@ public void setSpecs(List specs) { public void init() { super.init(); if (test != null && !test.isEmpty()) { - exchangeExpression = ExchangeExpression.newInstance(router, language, test); + exchangeExpression = expression(router, language, test); } key = new APIProxyKey(key, exchangeExpression, !specs.isEmpty()); initOpenAPI(); diff --git a/core/src/test/java/com/predic8/membrane/core/interceptor/flow/ChooseInterceptorTest.java b/core/src/test/java/com/predic8/membrane/core/interceptor/flow/ChooseInterceptorTest.java index de34e9ffb4..a85af62f5f 100644 --- a/core/src/test/java/com/predic8/membrane/core/interceptor/flow/ChooseInterceptorTest.java +++ b/core/src/test/java/com/predic8/membrane/core/interceptor/flow/ChooseInterceptorTest.java @@ -26,7 +26,7 @@ public class ChooseInterceptorTest extends AbstractInterceptorFlowTest { void caseA() throws Exception { assertFlow(">a>ba>ca>d T evaluateWithEmptyBodyFor(Class type) throws URISyntaxException { - return ExchangeExpression.newInstance(router, JSONPATH, "$") + return expression(router, JSONPATH, "$") .evaluate(get("/foo").buildExchange(), REQUEST, type); } } \ No newline at end of file diff --git a/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/APIProxyKeyComplexMatchTest.java b/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/APIProxyKeyComplexMatchTest.java index bf86f2b3ad..97bd364673 100644 --- a/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/APIProxyKeyComplexMatchTest.java +++ b/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/APIProxyKeyComplexMatchTest.java @@ -29,6 +29,7 @@ import java.util.stream.Stream; import static com.predic8.membrane.core.lang.ExchangeExpression.Language.SPEL; +import static com.predic8.membrane.core.lang.ExchangeExpression.expression; import static com.predic8.membrane.test.TestUtil.assembleExchange; import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.params.provider.Arguments.of; @@ -54,7 +55,7 @@ void complexMatchExpressionTrueTest() throws URISyntaxException { @Test void complexMatchExpressionFalse() throws URISyntaxException { var key = new APIProxyKey("", "", 80, null,"*", - ExchangeExpression.newInstance(null, SPEL,"1 == 2"), false); + expression(null, SPEL,"1 == 2"), false); assertFalse(key.complexMatch(new Builder().get("").buildExchange())); } @@ -68,11 +69,11 @@ void complexMatchExpressionHeader() throws URISyntaxException { @Test void complexMatchExpressionQueryParam() throws URISyntaxException { var key = new APIProxyKey("", "", 80, null,"*", - ExchangeExpression.newInstance(null, SPEL,"param.foo == 'bar'"),false); + expression(null, SPEL,"param.foo == 'bar'"),false); assertTrue(key.complexMatch(new Builder().get("/baz?foo=bar").buildExchange())); key = new APIProxyKey("", "", 80, null,"*", - ExchangeExpression.newInstance(null, SPEL,"param.foo == 'wrong'"),false); + expression(null, SPEL,"param.foo == 'wrong'"),false); assertFalse(key.complexMatch(new Builder().get("/baz?foo=bar").buildExchange())); } From f9e1e60e5f628b0193d21ccc2426b781ab356c4d Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Tue, 21 Oct 2025 15:26:54 +0200 Subject: [PATCH 2/8] fix: decodierung of body content for if, call, setHeader. Make jsonpath accept arrays as input. --- .../membrane/core/lang/ScriptingUtils.java | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/com/predic8/membrane/core/lang/ScriptingUtils.java b/core/src/main/java/com/predic8/membrane/core/lang/ScriptingUtils.java index f0dd269392..6e25e9722b 100644 --- a/core/src/main/java/com/predic8/membrane/core/lang/ScriptingUtils.java +++ b/core/src/main/java/com/predic8/membrane/core/lang/ScriptingUtils.java @@ -77,7 +77,7 @@ public static HashMap createParameterBindings(Router router, Exc if (includeJsonObject) { try { log.info("Parsing body as JSON for scripting plugins"); - params.put("json",om.readValue(readInputStream(msg.getBodyAsStream()),Map.class)); + params.put("json", om.readValue(readInputStream(msg.getBodyAsStream()), Map.class)); } catch (Exception e) { log.warn("Can't parse body as JSON", e); } @@ -85,15 +85,12 @@ public static HashMap createParameterBindings(Router router, Exc } /** - properties does not work in Groovy based Template Interceptor! - - Reason: properties is a special built-in property in Groovy. Every Groovy object has a properties property - that returns a Map of all the object's properties. - - But it can be accessed in Groovy Interceptor - - Decision: Keep it but change examples to use property, even for spel, .. - */ + * properties does not work in Groovy based Template Interceptor! + * Reason: properties is a special built-in property in Groovy. Every Groovy object has a properties property + * that returns a Map of all the object's properties. + * But it can be accessed in Groovy Interceptor + * Decision: Keep it but change examples to use property, even for spel, .. + */ params.put("properties", exc.getProperties()); // Also expose properties unter props and property From 6f8324e7e67c221e5a87c049bc3545cf3f8d697a Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Tue, 21 Oct 2025 16:33:02 +0200 Subject: [PATCH 3/8] fix: CallInterceptor Keep transfer- and content-encoding --- .../core/interceptor/flow/CallInterceptor.java | 8 ++++++-- .../core/interceptor/flow/CallInterceptorTest.java | 11 +++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/CallInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/CallInterceptor.java index f4ef0d5df5..13236edc8e 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/CallInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/CallInterceptor.java @@ -53,7 +53,7 @@ public class CallInterceptor extends AbstractExchangeExpressionInterceptor { * and are not added to the current message. */ private static final List REMOVE_HEADERS = List.of( - SERVER, TRANSFER_ENCODING, CONTENT_ENCODING + SERVER ); private String method = GET; @@ -87,7 +87,11 @@ private Outcome handleInternal(Exchange exc) { } try { - exc.getRequest().setBodyContent(newExc.getResponse().getBodyAsStringDecoded().getBytes()); + /** + * The content is copied from the response without decoding. The response headers like transfer-encoding are also copied. So + * when the content is later accessed using Stream or StringDecoded the decoding is done. + */ + exc.getRequest().setBodyContent(newExc.getResponse().getBody().getContent()); copyHeadersFromResponseToRequest(newExc, exc); return CONTINUE; } catch (Exception e) { diff --git a/core/src/test/java/com/predic8/membrane/core/interceptor/flow/CallInterceptorTest.java b/core/src/test/java/com/predic8/membrane/core/interceptor/flow/CallInterceptorTest.java index cd48fb7d74..0e8a1fb063 100644 --- a/core/src/test/java/com/predic8/membrane/core/interceptor/flow/CallInterceptorTest.java +++ b/core/src/test/java/com/predic8/membrane/core/interceptor/flow/CallInterceptorTest.java @@ -38,16 +38,19 @@ static void beforeAll() throws URISyntaxException { @Test void filterHeaders() { exc.setResponse(Response.ok() - .header(TRANSFER_ENCODING, "dummy") - .header(CONTENT_ENCODING, "dummy") + .header(TRANSFER_ENCODING, "foo") + .header(CONTENT_ENCODING, "bar") .header(SERVER, "dummy") .header("X-FOO", "42").build()); copyHeadersFromResponseToRequest(exc, exc); + // preserve assertEquals("42",exc.getRequest().getHeader().getFirstValue("X-FOO")); - assertNull(exc.getRequest().getHeader().getFirstValue(TRANSFER_ENCODING)); - assertNull(exc.getRequest().getHeader().getFirstValue(CONTENT_ENCODING)); + assertEquals("foo",exc.getRequest().getHeader().getFirstValue(TRANSFER_ENCODING)); + assertEquals( "bar",exc.getRequest().getHeader().getFirstValue(CONTENT_ENCODING)); + + // take out assertNull(exc.getRequest().getHeader().getFirstValue(SERVER)); } From 26961b883f646ff39ca5744af099d46947262746 Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Tue, 21 Oct 2025 17:08:12 +0200 Subject: [PATCH 4/8] refactor: CallInterceptor copy just bytes and let setBodyContent handle transfer encoding --- .../membrane/core/interceptor/flow/CallInterceptor.java | 6 +++--- .../membrane/core/interceptor/flow/CallInterceptorTest.java | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/CallInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/CallInterceptor.java index 13236edc8e..c1726b0c70 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/CallInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/CallInterceptor.java @@ -88,10 +88,10 @@ private Outcome handleInternal(Exchange exc) { try { /** - * The content is copied from the response without decoding. The response headers like transfer-encoding are also copied. So - * when the content is later accessed using Stream or StringDecoded the decoding is done. + * The content is copied from the response with decoding. The response headers transfer-encoding + * and content-encoding are removed by the setBodyContent method. */ - exc.getRequest().setBodyContent(newExc.getResponse().getBody().getContent()); + exc.getRequest().setBodyContent(newExc.getResponse().getBodyAsStreamDecoded().readAllBytes()); copyHeadersFromResponseToRequest(newExc, exc); return CONTINUE; } catch (Exception e) { diff --git a/core/src/test/java/com/predic8/membrane/core/interceptor/flow/CallInterceptorTest.java b/core/src/test/java/com/predic8/membrane/core/interceptor/flow/CallInterceptorTest.java index 0e8a1fb063..a800789b8f 100644 --- a/core/src/test/java/com/predic8/membrane/core/interceptor/flow/CallInterceptorTest.java +++ b/core/src/test/java/com/predic8/membrane/core/interceptor/flow/CallInterceptorTest.java @@ -47,8 +47,6 @@ void filterHeaders() { // preserve assertEquals("42",exc.getRequest().getHeader().getFirstValue("X-FOO")); - assertEquals("foo",exc.getRequest().getHeader().getFirstValue(TRANSFER_ENCODING)); - assertEquals( "bar",exc.getRequest().getHeader().getFirstValue(CONTENT_ENCODING)); // take out assertNull(exc.getRequest().getHeader().getFirstValue(SERVER)); From 0d8d6bca433d1a94307ebc6a3f21d791ecb38bed Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Tue, 21 Oct 2025 17:43:21 +0200 Subject: [PATCH 5/8] refactor: minor --- .../interceptor/flow/CallInterceptor.java | 2 +- .../membrane/core/util/MessageUtil.java | 32 +++++++++---------- .../interceptor/flow/CallInterceptorTest.java | 2 ++ 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/CallInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/CallInterceptor.java index c1726b0c70..36a6aac2b7 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/CallInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/CallInterceptor.java @@ -53,7 +53,7 @@ public class CallInterceptor extends AbstractExchangeExpressionInterceptor { * and are not added to the current message. */ private static final List REMOVE_HEADERS = List.of( - SERVER + SERVER, CONTENT_ENCODING, TRANSFER_ENCODING ); private String method = GET; diff --git a/core/src/main/java/com/predic8/membrane/core/util/MessageUtil.java b/core/src/main/java/com/predic8/membrane/core/util/MessageUtil.java index 562a936952..56926ffca4 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/MessageUtil.java +++ b/core/src/main/java/com/predic8/membrane/core/util/MessageUtil.java @@ -36,36 +36,36 @@ public class MessageUtil { saxParserFactory.setValidating(false); } - public static InputStream getContentAsStream(Message res) throws IOException { - if (res.isGzip()) { - return new GZIPInputStream(res.getBodyAsStream()); + public static InputStream getContentAsStream(Message msg) throws IOException { + if (msg.isGzip()) { + return new GZIPInputStream(msg.getBodyAsStream()); } - if (res.isDeflate()) { - return new ByteArrayInputStream(getDecompressedData(res.getBody().getContent())); + if (msg.isDeflate()) { + return new ByteArrayInputStream(getDecompressedData(msg.getBody().getContent())); } - if (res.isBrotli()) { - return new BrotliInputStream(res.getBodyAsStream()); + if (msg.isBrotli()) { + return new BrotliInputStream(msg.getBodyAsStream()); } - return res.getBodyAsStream(); + return msg.getBodyAsStream(); } - public static byte[] getContent(Message res) throws Exception { - if (res.isGzip()) { - try (InputStream lInputStream = res.getBodyAsStream(); + public static byte[] getContent(Message msg) throws Exception { + if (msg.isGzip()) { + try (InputStream lInputStream = msg.getBodyAsStream(); GZIPInputStream lGZIPInputStream = new GZIPInputStream(lInputStream)) { return lGZIPInputStream.readAllBytes(); } } - if (res.isDeflate()) { - return getDecompressedData(res.getBody().getContent()); + if (msg.isDeflate()) { + return getDecompressedData(msg.getBody().getContent()); } - if (res.isBrotli()) { - try (InputStream lInputStream = res.getBodyAsStream(); + if (msg.isBrotli()) { + try (InputStream lInputStream = msg.getBodyAsStream(); BrotliInputStream lBrotliInputStream = new BrotliInputStream(lInputStream)) { return lBrotliInputStream.readAllBytes(); } } - return res.getBody().getContent(); + return msg.getBody().getContent(); } public static Source getSOAPBody(InputStream stream) { diff --git a/core/src/test/java/com/predic8/membrane/core/interceptor/flow/CallInterceptorTest.java b/core/src/test/java/com/predic8/membrane/core/interceptor/flow/CallInterceptorTest.java index a800789b8f..5104549c68 100644 --- a/core/src/test/java/com/predic8/membrane/core/interceptor/flow/CallInterceptorTest.java +++ b/core/src/test/java/com/predic8/membrane/core/interceptor/flow/CallInterceptorTest.java @@ -50,6 +50,8 @@ void filterHeaders() { // take out assertNull(exc.getRequest().getHeader().getFirstValue(SERVER)); + assertNull(exc.getRequest().getHeader().getFirstValue(TRANSFER_ENCODING)); + assertNull(exc.getRequest().getHeader().getFirstValue(CONTENT_ENCODING)); } } \ No newline at end of file From f54b9f2e1a614b58fe4e15d763fec072d7a60858 Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Mon, 27 Oct 2025 11:57:22 +0100 Subject: [PATCH 6/8] refactor: minor --- .../spelable/SpELBody.java => LazyBody.java} | 15 ++++++++++++--- .../membrane/core/lang/ScriptingUtils.java | 2 +- .../lang/spel/SpELExchangeEvaluationContext.java | 7 ++++--- .../SpELBodyToStringTypeConverter.java | 6 +++--- 4 files changed, 20 insertions(+), 10 deletions(-) rename core/src/main/java/com/predic8/membrane/core/lang/{spel/spelable/SpELBody.java => LazyBody.java} (73%) diff --git a/core/src/main/java/com/predic8/membrane/core/lang/spel/spelable/SpELBody.java b/core/src/main/java/com/predic8/membrane/core/lang/LazyBody.java similarity index 73% rename from core/src/main/java/com/predic8/membrane/core/lang/spel/spelable/SpELBody.java rename to core/src/main/java/com/predic8/membrane/core/lang/LazyBody.java index ffc9669a3c..97e5cd6986 100644 --- a/core/src/main/java/com/predic8/membrane/core/lang/spel/spelable/SpELBody.java +++ b/core/src/main/java/com/predic8/membrane/core/lang/LazyBody.java @@ -11,18 +11,23 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -package com.predic8.membrane.core.lang.spel.spelable; +package com.predic8.membrane.core.lang; import com.predic8.membrane.core.http.*; -public class SpELBody { +import java.io.*; + +/** + * Enables the use of ${body} in scripting environments without reading the body from InputStream when it is not needed. + */ +public class LazyBody { /** * Store message instead of body to be able to extract even zipped bodies */ final Message message; - public SpELBody(Message msg) { + public LazyBody(Message msg) { message = msg; } @@ -30,6 +35,10 @@ public Message getMessage() { return message; } + /** + * This method is called in an expression like "Body ${body}". + * @return + */ @Override public String toString() { return message.getBodyAsStringDecoded(); diff --git a/core/src/main/java/com/predic8/membrane/core/lang/ScriptingUtils.java b/core/src/main/java/com/predic8/membrane/core/lang/ScriptingUtils.java index 6e25e9722b..b2c734b639 100644 --- a/core/src/main/java/com/predic8/membrane/core/lang/ScriptingUtils.java +++ b/core/src/main/java/com/predic8/membrane/core/lang/ScriptingUtils.java @@ -71,7 +71,7 @@ public static HashMap createParameterBindings(Router router, Exc if (msg != null) { params.put("message", msg); - params.put("body", msg.getBodyAsStringDecoded()); + params.put("body", new LazyBody(msg)); params.put("header", msg.getHeader()); params.put("headers", msg.getHeader()); if (includeJsonObject) { diff --git a/core/src/main/java/com/predic8/membrane/core/lang/spel/SpELExchangeEvaluationContext.java b/core/src/main/java/com/predic8/membrane/core/lang/spel/SpELExchangeEvaluationContext.java index a36ff921f3..41b22e2cb1 100644 --- a/core/src/main/java/com/predic8/membrane/core/lang/spel/SpELExchangeEvaluationContext.java +++ b/core/src/main/java/com/predic8/membrane/core/lang/spel/SpELExchangeEvaluationContext.java @@ -18,6 +18,7 @@ import com.predic8.membrane.core.exchange.*; import com.predic8.membrane.core.http.*; import com.predic8.membrane.core.interceptor.Interceptor.*; +import com.predic8.membrane.core.lang.*; import com.predic8.membrane.core.lang.spel.functions.*; import com.predic8.membrane.core.lang.spel.spelable.*; import com.predic8.membrane.core.lang.spel.typeconverters.*; @@ -39,7 +40,7 @@ public class SpELExchangeEvaluationContext extends StandardEvaluationContext { private final Exchange exchange; private final Message message; - private final SpELBody body; // Is used by SpEL in scripts + private final LazyBody body; // Is used by SpEL in scripts // Avoid the common plural error private final SpELLablePropertyAware headers; @@ -70,7 +71,7 @@ public SpELExchangeEvaluationContext(Exchange exchange, Flow flow) { this.exchange = exchange; this.flow = flow; this.message = exchange.getMessage(flow); - this.body = new SpELBody(message); + this.body = new LazyBody(message); pathParam = new SpELPathParameters(exchange); properties = new SpELProperties(exchange.getProperties()); @@ -174,7 +175,7 @@ public Message getMessage() { return message; } - public SpELBody getBody() { return body; } + public LazyBody getBody() { return body; } public String getPath() { return path; diff --git a/core/src/main/java/com/predic8/membrane/core/lang/spel/typeconverters/SpELBodyToStringTypeConverter.java b/core/src/main/java/com/predic8/membrane/core/lang/spel/typeconverters/SpELBodyToStringTypeConverter.java index fd47a060b6..419bf38a4f 100644 --- a/core/src/main/java/com/predic8/membrane/core/lang/spel/typeconverters/SpELBodyToStringTypeConverter.java +++ b/core/src/main/java/com/predic8/membrane/core/lang/spel/typeconverters/SpELBodyToStringTypeConverter.java @@ -13,17 +13,17 @@ limitations under the License. */ package com.predic8.membrane.core.lang.spel.typeconverters; -import com.predic8.membrane.core.lang.spel.spelable.*; +import com.predic8.membrane.core.lang.*; import com.predic8.membrane.core.util.*; import org.slf4j.*; import org.springframework.core.convert.converter.*; -public class SpELBodyToStringTypeConverter implements Converter { +public class SpELBodyToStringTypeConverter implements Converter { private static final Logger log = LoggerFactory.getLogger(SpELBodyToStringTypeConverter.class.getName()); @Override - public String convert(SpELBody body) { + public String convert(LazyBody body) { try { return new String(MessageUtil.getContent(body.getMessage())); } catch (Exception e) { From 86e4b226174c7dfa393bf1268f4ce28e997d6cba Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Mon, 27 Oct 2025 11:59:00 +0100 Subject: [PATCH 7/8] refactor: minor --- .../com/predic8/membrane/core/lang/LazyBody.java | 1 - .../predic8/membrane/core/lang/ScriptingUtils.java | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/com/predic8/membrane/core/lang/LazyBody.java b/core/src/main/java/com/predic8/membrane/core/lang/LazyBody.java index 97e5cd6986..bbc5948d69 100644 --- a/core/src/main/java/com/predic8/membrane/core/lang/LazyBody.java +++ b/core/src/main/java/com/predic8/membrane/core/lang/LazyBody.java @@ -37,7 +37,6 @@ public Message getMessage() { /** * This method is called in an expression like "Body ${body}". - * @return */ @Override public String toString() { diff --git a/core/src/main/java/com/predic8/membrane/core/lang/ScriptingUtils.java b/core/src/main/java/com/predic8/membrane/core/lang/ScriptingUtils.java index b2c734b639..c42cba3701 100644 --- a/core/src/main/java/com/predic8/membrane/core/lang/ScriptingUtils.java +++ b/core/src/main/java/com/predic8/membrane/core/lang/ScriptingUtils.java @@ -84,12 +84,12 @@ public static HashMap createParameterBindings(Router router, Exc } } - /** - * properties does not work in Groovy based Template Interceptor! - * Reason: properties is a special built-in property in Groovy. Every Groovy object has a properties property - * that returns a Map of all the object's properties. - * But it can be accessed in Groovy Interceptor - * Decision: Keep it but change examples to use property, even for spel, .. + /* + properties does not work in Groovy based Template Interceptor! + Reason: properties is a special built-in property in Groovy. Every Groovy object has a properties property + that returns a Map of all the object's properties. + But it can be accessed in Groovy Interceptor + Decision: Keep it but change examples to use property, even for spel, .. */ params.put("properties", exc.getProperties()); From e0ae7d375d6487b5e301b81d329413e2cf517ba7 Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Mon, 27 Oct 2025 12:53:25 +0100 Subject: [PATCH 8/8] refactor: minor --- core/src/main/java/com/predic8/membrane/core/lang/LazyBody.java | 2 -- .../java/com/predic8/membrane/core/lang/ScriptingUtils.java | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/core/src/main/java/com/predic8/membrane/core/lang/LazyBody.java b/core/src/main/java/com/predic8/membrane/core/lang/LazyBody.java index bbc5948d69..ea7565d8a8 100644 --- a/core/src/main/java/com/predic8/membrane/core/lang/LazyBody.java +++ b/core/src/main/java/com/predic8/membrane/core/lang/LazyBody.java @@ -15,8 +15,6 @@ import com.predic8.membrane.core.http.*; -import java.io.*; - /** * Enables the use of ${body} in scripting environments without reading the body from InputStream when it is not needed. */ diff --git a/core/src/main/java/com/predic8/membrane/core/lang/ScriptingUtils.java b/core/src/main/java/com/predic8/membrane/core/lang/ScriptingUtils.java index c42cba3701..03233bf35c 100644 --- a/core/src/main/java/com/predic8/membrane/core/lang/ScriptingUtils.java +++ b/core/src/main/java/com/predic8/membrane/core/lang/ScriptingUtils.java @@ -77,7 +77,7 @@ public static HashMap createParameterBindings(Router router, Exc if (includeJsonObject) { try { log.info("Parsing body as JSON for scripting plugins"); - params.put("json", om.readValue(readInputStream(msg.getBodyAsStream()), Map.class)); + params.put("json", om.readValue(readInputStream(msg.getBodyAsStreamDecoded()), Map.class)); } catch (Exception e) { log.warn("Can't parse body as JSON", e); }