diff --git a/shenyu-integrated-test/shenyu-integrated-test-http/script/healthcheck.sh b/shenyu-integrated-test/shenyu-integrated-test-http/script/healthcheck.sh index 015ad1cd7f6c..22a76034abee 100644 --- a/shenyu-integrated-test/shenyu-integrated-test-http/script/healthcheck.sh +++ b/shenyu-integrated-test/shenyu-integrated-test-http/script/healthcheck.sh @@ -1,4 +1,4 @@ - s#!/bin/bash +#!/bin/bash # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with diff --git a/shenyu-plugin/shenyu-plugin-modify-response/src/main/java/org/apache/shenyu/plugin/modify/response/ModifyResponsePlugin.java b/shenyu-plugin/shenyu-plugin-modify-response/src/main/java/org/apache/shenyu/plugin/modify/response/ModifyResponsePlugin.java index dd2598e7247a..a32210b9fa36 100644 --- a/shenyu-plugin/shenyu-plugin-modify-response/src/main/java/org/apache/shenyu/plugin/modify/response/ModifyResponsePlugin.java +++ b/shenyu-plugin/shenyu-plugin-modify-response/src/main/java/org/apache/shenyu/plugin/modify/response/ModifyResponsePlugin.java @@ -98,7 +98,7 @@ static class ModifyResponseDecorator extends ServerHttpResponseDecorator { @NonNull public Mono writeWith(@NonNull final Publisher body) { modifyResponseHeadersAndStatus(); - if (!hasBodyModifications() || !isJsonResponse()) { + if (!hasBodyModifications()) { return super.writeWith(body); } final Mono dataBufferMono = DataBufferUtils.join(body); @@ -106,7 +106,14 @@ public Mono writeWith(@NonNull final Publisher body) byte[] bytes = new byte[dataBuffer.readableByteCount()]; dataBuffer.read(bytes); DataBufferUtils.release(dataBuffer); - return WebFluxResultUtils.result(this.exchange, modifyBody(bytes)); + if (isJsonResponse()) { + return WebFluxResultUtils.result(this.exchange, modifyBody(bytes)); + } + byte[] modifiedBody = tryModifyBody(bytes); + if (Objects.isNull(modifiedBody)) { + return super.writeWith(Mono.just(this.getDelegate().bufferFactory().wrap(bytes))); + } + return WebFluxResultUtils.result(this.exchange, modifiedBody); }); } @@ -193,5 +200,16 @@ private String modifyBody(final String jsonValue) { } return context.jsonString(); } + + private byte[] tryModifyBody(final byte[] responseBody) { + try { + String bodyStr = modifyBody(new String(responseBody, StandardCharsets.UTF_8)); + LOG.info("the body string {}", bodyStr); + return bodyStr.getBytes(StandardCharsets.UTF_8); + } catch (Exception e) { + LOG.debug("skip modify response body because response content type is not json", e); + return null; + } + } } } diff --git a/shenyu-plugin/shenyu-plugin-modify-response/src/test/java/org/apache/shenyu/plugin/modify/response/ModifyResponsePluginTest.java b/shenyu-plugin/shenyu-plugin-modify-response/src/test/java/org/apache/shenyu/plugin/modify/response/ModifyResponsePluginTest.java index 7aeeab8c0105..d879a3e6a101 100644 --- a/shenyu-plugin/shenyu-plugin-modify-response/src/test/java/org/apache/shenyu/plugin/modify/response/ModifyResponsePluginTest.java +++ b/shenyu-plugin/shenyu-plugin-modify-response/src/test/java/org/apache/shenyu/plugin/modify/response/ModifyResponsePluginTest.java @@ -21,16 +21,21 @@ import org.apache.shenyu.common.dto.RuleData; import org.apache.shenyu.common.dto.SelectorData; import org.apache.shenyu.common.dto.convert.rule.impl.ModifyResponseRuleHandle; +import org.apache.shenyu.common.dto.convert.rule.impl.ParamMappingRuleHandle; import org.apache.shenyu.common.enums.PluginEnum; import org.apache.shenyu.plugin.api.ShenyuPluginChain; import org.apache.shenyu.plugin.api.context.ShenyuContext; +import org.apache.shenyu.plugin.api.result.ShenyuResult; +import org.apache.shenyu.plugin.api.utils.SpringBeanUtils; import org.apache.shenyu.plugin.modify.response.handler.ModifyResponsePluginDataHandler; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; import org.reactivestreams.Publisher; import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.context.ApplicationContext; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; @@ -87,6 +92,11 @@ public void setUp() { exchange.getAttributes().put(Constants.CONTEXT, shenyuContext); } + @AfterEach + public void tearDown() { + SpringBeanUtils.getInstance().setApplicationContext(null); + } + @Test public void testDoExecute() { when(chain.execute(any())).thenReturn(Mono.empty()); @@ -166,6 +176,28 @@ public void testWriteWithSkipsBodyRulesForNonJsonResponse() { .verifyComplete(); } + @Test + public void testWriteWithModifiesJsonBodyForNonJsonContentType() { + mockShenyuResult(); + final ModifyResponseRuleHandle responseRuleHandle = new ModifyResponseRuleHandle(); + final ParamMappingRuleHandle.ParamMapInfo addBodyKey = new ParamMappingRuleHandle.ParamMapInfo(); + addBodyKey.setPath("$"); + addBodyKey.setKey("added"); + addBodyKey.setValue("true"); + responseRuleHandle.setAddBodyKeys(Collections.singletonList(addBodyKey)); + final ModifyResponsePlugin.ModifyResponseDecorator decorator = + new ModifyResponsePlugin.ModifyResponseDecorator(exchange, responseRuleHandle); + final MockServerHttpResponse response = (MockServerHttpResponse) exchange.getResponse(); + response.getHeaders().setContentType(MediaType.TEXT_PLAIN); + final DataBuffer dataBuffer = response.bufferFactory().wrap("{\"value\":true}".getBytes(StandardCharsets.UTF_8)); + + StepVerifier.create(decorator.writeWith(Mono.just(dataBuffer))).verifyComplete(); + + StepVerifier.create(response.getBodyAsString()) + .expectNext("{\"value\":true,\"added\":\"true\"}") + .verifyComplete(); + } + @Test public void testGetOrder() { assertEquals(modifyResponsePlugin.getOrder(), PluginEnum.MODIFY_RESPONSE.getCode()); @@ -180,4 +212,15 @@ public void testNamed() { public void testSkip() { assertFalse(modifyResponsePlugin.skip(exchange)); } + + private void mockShenyuResult() { + ApplicationContext applicationContext = mock(ApplicationContext.class); + @SuppressWarnings("unchecked") + ShenyuResult shenyuResult = (ShenyuResult) mock(ShenyuResult.class); + when(shenyuResult.format(any(ServerWebExchange.class), any())).thenAnswer(invocation -> invocation.getArgument(1)); + when(shenyuResult.contentType(any(ServerWebExchange.class), any())).thenReturn(MediaType.APPLICATION_JSON); + when(shenyuResult.result(any(ServerWebExchange.class), any())).thenAnswer(invocation -> invocation.getArgument(1)); + when(applicationContext.getBean(ShenyuResult.class)).thenReturn(shenyuResult); + SpringBeanUtils.getInstance().setApplicationContext(applicationContext); + } }