diff --git a/changelog.md b/changelog.md
index 2fb8a76c..551ac68e 100644
--- a/changelog.md
+++ b/changelog.md
@@ -4,6 +4,7 @@
- 可定义业务错误码(见`@Open`注解中的`bizCode`属性)
- 文档参数可指定最大长度(使用`@Length(max = xx)`)
+- 修复返回大文本导致的错误(Exceeded limit on max bytes to buffer : 262144)
## 4.0.2
diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/GatewayModifyResponseGatewayFilter.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/GatewayModifyResponseGatewayFilter.java
index ca72a59c..e6a13901 100644
--- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/GatewayModifyResponseGatewayFilter.java
+++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/GatewayModifyResponseGatewayFilter.java
@@ -5,6 +5,7 @@ import com.gitee.sop.gatewaycommon.bean.SopConstants;
import com.gitee.sop.gatewaycommon.result.ResultExecutor;
import org.apache.commons.lang3.StringUtils;
import org.reactivestreams.Publisher;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
@@ -12,12 +13,16 @@ import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutput
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.cloud.gateway.support.DefaultClientResponse;
import org.springframework.core.Ordered;
+import org.springframework.core.codec.AbstractDataBufferDecoder;
+import org.springframework.core.codec.Decoder;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseCookie;
import org.springframework.http.client.reactive.ClientHttpResponse;
+import org.springframework.http.codec.DecoderHttpMessageReader;
+import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserter;
@@ -35,6 +40,8 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.O
*/
public class GatewayModifyResponseGatewayFilter implements GlobalFilter, Ordered {
+ @Value("${spring.codec.max-in-memory-size:262144}")
+ private int maxInMemorySize;
@Override
@SuppressWarnings("unchecked")
@@ -60,7 +67,7 @@ public class GatewayModifyResponseGatewayFilter implements GlobalFilter, Ordered
//this will prevent exception in case of using non-standard media types like "Content-Type: image"
httpHeaders.add(HttpHeaders.CONTENT_TYPE, originalResponseContentType);
ResponseAdapter responseAdapter = new ResponseAdapter(body, httpHeaders);
- DefaultClientResponse clientResponse = new DefaultClientResponse(responseAdapter, ExchangeStrategies.withDefaults());
+ DefaultClientResponse clientResponse = new DefaultClientResponse(responseAdapter, getExchangeStrategies());
//TODO: flux or mono
Mono modifiedBody = clientResponse.bodyToMono(inClass)
@@ -97,6 +104,22 @@ public class GatewayModifyResponseGatewayFilter implements GlobalFilter, Ordered
return chain.filter(exchange.mutate().response(responseDecorator).build());
}
+ private ExchangeStrategies getExchangeStrategies() {
+ ExchangeStrategies exchangeStrategies = ExchangeStrategies.withDefaults();
+ // 修复返回大文本数据报org.springframework.core.io.buffer.DataBufferLimitException: Exceeded limit on max bytes to buffer : 262144
+ for (HttpMessageReader> messageReader : exchangeStrategies.messageReaders()) {
+ if (messageReader instanceof DecoderHttpMessageReader) {
+ DecoderHttpMessageReader reader = (DecoderHttpMessageReader) messageReader;
+ Decoder decoder = reader.getDecoder();
+ if (decoder instanceof AbstractDataBufferDecoder) {
+ AbstractDataBufferDecoder dataBufferDecoder = (AbstractDataBufferDecoder)decoder;
+ dataBufferDecoder.setMaxInMemorySize(maxInMemorySize);
+ }
+ }
+ }
+ return exchangeStrategies;
+ }
+
@Override
public int getOrder() {
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java
index 4e94e389..71052dbd 100644
--- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java
+++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java
@@ -13,6 +13,7 @@ import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
@@ -25,6 +26,7 @@ import org.springframework.web.servlet.mvc.method.annotation.ServletRequestMetho
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.InputStream;
import java.io.OutputStream;
@@ -117,6 +119,10 @@ public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver {
, NativeWebRequest nativeWebRequest
, WebDataBinderFactory webDataBinderFactory
) throws Exception {
+ nativeWebRequest = new SopServletWebRequest(
+ (HttpServletRequest) nativeWebRequest.getNativeRequest(),
+ (HttpServletResponse) nativeWebRequest.getNativeResponse()
+ );
if (openApiParams.contains(methodParameter)) {
Object paramObj = this.getParamObject(methodParameter, nativeWebRequest);
if (paramObj != null) {
@@ -175,10 +181,13 @@ public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver {
* @param httpServletRequest
*/
protected void bindUploadFile(Object param, HttpServletRequest httpServletRequest) {
+ if (httpServletRequest instanceof MyServletRequestWrapper) {
+ httpServletRequest = (HttpServletRequest)((MyServletRequestWrapper) httpServletRequest).getRequest();
+ }
if (param == null) {
return;
}
- if (this.isMultipartRequest(httpServletRequest)) {
+ if (OpenUtil.isMultipart(httpServletRequest)) {
MultipartHttpServletRequest request = (MultipartHttpServletRequest) httpServletRequest;
Class> bizClass = param.getClass();
ReflectionUtils.doWithFields(bizClass, field -> {
@@ -190,10 +199,6 @@ public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver {
}
}
- protected boolean isMultipartRequest(HttpServletRequest request) {
- return request instanceof MultipartRequest;
- }
-
/**
* 获取其它的参数解析器
*
@@ -221,4 +226,10 @@ public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver {
public void setParamValidator(ParamValidator paramValidator) {
this.paramValidator = paramValidator;
}
+
+ private static final class SopServletWebRequest extends ServletWebRequest {
+ public SopServletWebRequest(HttpServletRequest request, HttpServletResponse response) {
+ super(new MyServletRequestWrapper(request), response);
+ }
+ }
}
\ No newline at end of file
diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ByteArrayStreamWrapper.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ByteArrayStreamWrapper.java
new file mode 100644
index 00000000..64ede5fa
--- /dev/null
+++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ByteArrayStreamWrapper.java
@@ -0,0 +1,53 @@
+package com.gitee.sop.servercommon.param;
+
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+import java.io.IOException;
+
+/**
+ * byte数组流包装
+ *
+ * @author tanghc
+ */
+public class ByteArrayStreamWrapper extends ServletInputStream {
+
+ private byte[] data;
+ private int idx = 0;
+
+ /**
+ * Creates a new ByteArrayStreamWrapper
instance.
+ *
+ * @param data a byte[]
value
+ */
+ public ByteArrayStreamWrapper(byte[] data) {
+ if (data == null) {
+ data = new byte[0];
+ }
+ this.data = data;
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (idx == data.length) {
+ return -1;
+ }
+ // I have to AND the byte with 0xff in order to ensure that it is returned as an unsigned integer
+ // the lack of this was causing a weird bug when manually unzipping gzipped request bodies
+ return data[idx++] & 0xff;
+ }
+
+ @Override
+ public boolean isFinished() {
+ return true;
+ }
+
+ @Override
+ public boolean isReady() {
+ return true;
+ }
+
+ @Override
+ public void setReadListener(ReadListener readListener) {
+
+ }
+}
\ No newline at end of file
diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/MyServletRequestWrapper.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/MyServletRequestWrapper.java
new file mode 100644
index 00000000..0c14c6eb
--- /dev/null
+++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/MyServletRequestWrapper.java
@@ -0,0 +1,106 @@
+package com.gitee.sop.servercommon.param;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.IOUtils;
+
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Map;
+
+/**
+ * 包装HttpServletRequest
+ */
+@Slf4j
+public class MyServletRequestWrapper extends HttpServletRequestWrapper {
+
+ private final byte[] body;
+ private final Map parameterMap;
+
+ public MyServletRequestWrapper(HttpServletRequest request) {
+ super(request);
+ // 先缓存请求参数,不然获取不到,原因:
+ // 调用getInputStream方法时,usingInputStream会变成true
+ // 之后再调用request.getParameterMap(),就一直返回空map
+ // 因此要先调用request.getParameterMap()缓存起来
+ // 详见org.apache.catalina.connector.Request.parseParameters()中
+ // if (usingInputStream || usingReader) {
+ // success = true;
+ // return;
+ // }
+ parameterMap = request.getParameterMap();
+ try {
+ body = IOUtils.toByteArray(super.getInputStream());
+ } catch (IOException e) {
+ log.error("cache body error, url:{}",request.getRequestURI(), e);
+ throw new RuntimeException("cache body error", e);
+ }
+ }
+
+ @Override
+ public Map getParameterMap() {
+ return parameterMap;
+ }
+
+ @Override
+ public BufferedReader getReader() throws IOException {
+ return new BufferedReader(new InputStreamReader(getInputStream()));
+ }
+
+ @Override
+ public ServletInputStream getInputStream() throws IOException {
+ return new ByteArrayStreamWrapper(body);
+ }
+
+ /**
+ * byte数组流包装
+ *
+ * @author tanghc
+ */
+ public static class ByteArrayStreamWrapper extends ServletInputStream {
+
+ private byte[] data;
+ private int idx = 0;
+
+ /**
+ * Creates a new ByteArrayStreamWrapper
instance.
+ *
+ * @param data a byte[]
value
+ */
+ public ByteArrayStreamWrapper(byte[] data) {
+ if (data == null) {
+ data = new byte[0];
+ }
+ this.data = data;
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (idx == data.length) {
+ return -1;
+ }
+ // I have to AND the byte with 0xff in order to ensure that it is returned as an unsigned integer
+ // the lack of this was causing a weird bug when manually unzipping gzipped request bodies
+ return data[idx++] & 0xff;
+ }
+
+ @Override
+ public boolean isFinished() {
+ return true;
+ }
+
+ @Override
+ public boolean isReady() {
+ return true;
+ }
+
+ @Override
+ public void setReadListener(ReadListener readListener) {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/util/OpenUtil.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/util/OpenUtil.java
index b048ea7b..727e5168 100644
--- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/util/OpenUtil.java
+++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/util/OpenUtil.java
@@ -14,6 +14,7 @@ import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -23,6 +24,8 @@ import java.util.Set;
@Slf4j
public class OpenUtil {
+ public static final String MULTIPART = "multipart/";
+
/**
* 将get类型的参数转换成map,
*
@@ -120,4 +123,19 @@ public class OpenUtil {
return serverSign.equals(sign);
}
+ /**
+ * 是否是文件上传请求
+ *
+ * @param request 请求
+ * @return true:是
+ */
+ public static boolean isMultipart(HttpServletRequest request) {
+ String contentType = request.getContentType();
+ // Don't use this filter on GET method
+ if (contentType == null) {
+ return false;
+ }
+ return contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART);
+ }
+
}
diff --git a/sop-example/sop-story/pom.xml b/sop-example/sop-story/pom.xml
index f45edd17..f7cd0ca0 100644
--- a/sop-example/sop-story/pom.xml
+++ b/sop-example/sop-story/pom.xml
@@ -56,6 +56,11 @@
swagger-bootstrap-ui
1.9.5
+
+ io.springfox
+ springfox-swagger-ui
+ 2.9.2
+
org.springframework.boot
diff --git a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1001_BaseController.java b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1001_BaseController.java
index b2eb6e2c..65c18392 100644
--- a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1001_BaseController.java
+++ b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1001_BaseController.java
@@ -1,5 +1,7 @@
package com.gitee.sop.storyweb.controller;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
import com.gitee.sop.servercommon.annotation.BizCode;
import com.gitee.sop.servercommon.annotation.Open;
import com.gitee.sop.storyweb.controller.param.CategoryParam;
@@ -12,13 +14,18 @@ import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* 签名验证通过后,到达这里进行具体的业务处理。
@@ -83,9 +90,10 @@ public class Example1001_BaseController {
// 忽略验证
@ApiOperation(value = "忽略签名验证", notes = "忽略签名验证")
@Open(value = "story.get.ignore", ignoreValidate = true)
- @RequestMapping("/get/ignore/v1")
- public StoryResult getStory21(StoryParam story) {
+ @RequestMapping(value = "/get/ignore/v1", method = {RequestMethod.GET, RequestMethod.POST})
+ public StoryResult getStory21(@RequestBody StoryParam story) {
StoryResult result = new StoryResult();
+ result.setId((long) story.getId());
result.setName(story.getName() + ", ignoreValidate = true");
return result;
}
@@ -160,4 +168,19 @@ public class Example1001_BaseController {
return parent;
}
+ private static String json = "{\"flightDate\":\"2020-09-01\",\"flightNo\":\"HO1705\",\"departureAirport\":\"ZSCN\",\"arrivalAirport\":\"ZPLJ\",\"ycy\":\"11521\",\"lcy\":\"4354\",\"cr\":\"145\",\"et\":\"1\",\"ye\":\"0\",\"td\":\"0\",\"gw\":\"0\",\"ew\":\"146\",\"xl\":\"1018\",\"yj\":\"0\",\"hw\":\"635\"}";
+ // 返回大数据
+ @Open(value = "bigdata.get")
+ @RequestMapping("/bigdata/v1")
+ public Map bigData(StoryParam param) {
+ int len = 2000;
+ List list = new ArrayList<>(len);
+ for (int i = 0; i < len; i++) {
+ list.add(JSON.parseObject(json));
+ }
+ Map map = new HashMap<>();
+ map.put("data", list);
+ return map;
+ }
+
}
diff --git a/sop-gateway/src/main/resources/application-dev.properties b/sop-gateway/src/main/resources/application-dev.properties
index 4d02bb23..3ed92423 100644
--- a/sop-gateway/src/main/resources/application-dev.properties
+++ b/sop-gateway/src/main/resources/application-dev.properties
@@ -8,4 +8,8 @@ mysql.password=root
# nacos注册中心地址
register.url=http://localhost:1111/eureka/
+# POST请求最大缓存,默认是262144(256KB),即:256 * 1024
+# 如果需要传输大文本可以把值设置高一点
+spring.codec.max-in-memory-size=262144
+
logging.level.com.gitee=debug
\ No newline at end of file
diff --git a/sop-test/src/test/java/com/gitee/sop/test/AllInOneTest.java b/sop-test/src/test/java/com/gitee/sop/test/AllInOneTest.java
index 3ac7f602..cae5bd1b 100644
--- a/sop-test/src/test/java/com/gitee/sop/test/AllInOneTest.java
+++ b/sop-test/src/test/java/com/gitee/sop/test/AllInOneTest.java
@@ -275,6 +275,25 @@ public class AllInOneTest extends TestBase {
client.execute(requestBuilder);
}
+ /**
+ * 测试返回大json数据
+ */
+ public void testLargeResponseJson() {
+ Client.RequestBuilder requestBuilder = new Client.RequestBuilder()
+ .method("bigdata.get")
+ .version("1.0")
+ .bizContent(new BizContent().add("id", "1").add("name", "葫芦娃"))
+ .httpMethod(HttpTool.HTTPMethod.GET)
+ .callback((requestInfo, responseData) -> {
+ int size = JSON.parseObject(responseData)
+ .getJSONObject("bigdata_get_response")
+ .getJSONArray("data").size();
+ Assert.assertEquals(size, 2000);
+ });
+
+ client.execute(requestBuilder);
+ }
+
static class BizContent extends HashMap {
public BizContent add(String key, Object value) {
this.put(key, value);