0%

feign开启压缩配置(okhttp3&&httpclient)

教训:nacos配置一定要验证可用性之后再去验证性能

feign+okhttp 与 feign+httpclient开启压缩的方式是不一样的!

服务端返回值开启压缩

服务端添加配置

1
2
3
4
5
6
7
8
9
10
server:
compression:
enabled: true
min-response-size: 100
mime-type:
- text/html
- text/xml
- application/xml
- application/octet-stream
- application/json

客户端接收服务端返回信息进行自动解压缩

okhttp自带自动解压缩,代码在okhttp3.internal.http.BridgeInterceptor

httpclient 未知

测试验证

1
2
3
4
5
6
7
8
http POST http://127.0.0.1:8080/ping
HTTP/1.1 200
Connection: close
Content-Encoding: gzip
Content-Type: application/json
Date: Fri, 03 Feb 2023 04:56:39 GMT
Transfer-Encoding: chunked
Vary: accept-encoding

客户端发送压缩请求

方案1: feign_okhttp_undertow

此方案可以利用undertow的自动解压缩能力,方便快捷,但是风险点是会影响现有配置及监控(我们有大量的tomcat配置及监控)。

服务端添加如下依赖配置

1
2
3
4
5
6
7
8
9
10
11
12
13
server:
undertow:
io-threads: 64
worker-threads: 500
compression:
enabled: true
min-response-size: 100
mime-type:
- text/html
- text/xml
- application/xml
- application/octet-stream
- application/json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class WebServerConfig {

@Bean
public UndertowServletWebServerFactory undertowServletWebServerFactory() {
UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
factory.addDeploymentInfoCustomizers((deploymentInfo ->
deploymentInfo.addInitialHandlerChainWrapper(httpHandler ->
new RequestEncodingHandler(httpHandler).addEncoding("gzip", GzipStreamSourceConduit.WRAPPER)
)
));
return factory;
}
}

客户端添加如下配置

1
2
3
4
5
6
feign:
client:
okhttp:
enabled: true
httpclient:
enabled: false
1
2
3
4
5
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>${version.feign.okhttp}</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
@Slf4j
public class OkHttpConfig {
@Bean
public OkHttpClient okHttpClient() {
return new OkHttpClient.Builder()
.addInterceptor(new OkGzipCompressionInterceptor())
.connectTimeout(2, TimeUnit.SECONDS)
.connectionPool(new ConnectionPool(500, 5L, TimeUnit.MINUTES))
.build();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Slf4j
public class OkGzipCompressionInterceptor implements Interceptor {
@NotNull
@Override
public Response intercept(@NotNull Chain chain) throws IOException {
Request originalRequest = chain.request();
if (originalRequest.body() == null || !StringUtils.equalsIgnoreCase(originalRequest.header("Content-Encoding"), "gzip")) {
return chain.proceed(originalRequest);
}

Request compressedRequest = originalRequest.newBuilder()
.header("Content-Encoding", "gzip")
.method(originalRequest.method(), compress(originalRequest.body()))
.build();
return chain.proceed(compressedRequest);
}

private RequestBody compress(RequestBody body) {
return new RequestBody() {
@Nullable
@Override
public MediaType contentType() {
return body.contentType();
}

@Override
public void writeTo(@NotNull BufferedSink bufferedSink) throws IOException {
BufferedSink gzipSink = Okio.buffer(new GzipSink(bufferedSink));
body.writeTo(gzipSink);
gzipSink.getBuffer().size();
gzipSink.close();
}
};
}
}

方案2: feign_okhttp_tomcat

服务端配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class GZIPUtils {

public static byte[] uncompress(byte[] src) throws IOException {
if (src == null || src.length == 0) {
return null;
}

ByteArrayInputStream in = new ByteArrayInputStream(src);
return uncompress(in);
}

public static byte[] uncompress(InputStream srcStream) throws IOException {
if (srcStream == null || srcStream.available() == 0) {
return null;
}

ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPInputStream ungzip = new GZIPInputStream(srcStream);
byte[] buffer = new byte[256];
int n;
while ((n = ungzip.read(buffer)) >= 0) {
out.write(buffer, 0, n);
}
return out.toByteArray();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/**
* 读取request 参数数据并包装为新的request
*/
@Slf4j
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {

/**
* requestBody数据
*/
private final byte[] body;

/**
* 所有参数的集合
*/
private Map<String, String[]> parameterMap;


public BodyReaderHttpServletRequestWrapper(HttpServletRequest request, boolean isCompress) throws IOException {
super(request);
if (isCompress) {
InputStream in = request.getInputStream();
if (in != null) {
body = readGzipBytes(in);
} else {
body = new byte[0];
}
} else {
BufferedReader reader = request.getReader();
if (reader != null) {
body = readBytes(reader);
} else {
body = new byte[0];
}
}
parameterMap = request.getParameterMap();
}

private byte[] readGzipBytes(InputStream srcStream) throws IOException {
return GZIPUtils.uncompress(srcStream);
}


@Override
public BufferedReader getReader() throws IOException {

ServletInputStream inputStream = getInputStream();

if (null == inputStream) {
return null;
}

return new BufferedReader(new InputStreamReader(inputStream));
}

@Override
public Enumeration<String> getParameterNames() {
Vector<String> vector = new Vector<>(parameterMap.keySet());
return vector.elements();
}

@Override
public ServletInputStream getInputStream() throws IOException {

if (body == null) {
return null;
}

final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {

@Override
public boolean isFinished() {
return false;
}

@Override
public boolean isReady() {
return false;
}

@Override
public void setReadListener(ReadListener listener) {

}

@Override
public int read() throws IOException {
return bais.read();
}
};
}

/**
* 通过BufferedReader和字符编码集转换成byte数组
*
* @param br
* @return
* @throws IOException
*/
private byte[] readBytes(BufferedReader br) throws IOException {
String str;
StringBuilder retStr = new StringBuilder();
while ((str = br.readLine()) != null) {
retStr.append(str);
}
if (StringUtils.isNotBlank(retStr.toString())) {
return retStr.toString().getBytes(StandardCharsets.UTF_8);
}
return new byte[0];
}

public String inputStream2String(InputStream inputStream) {
if (Objects.isNull(inputStream)) {
return "";
}

StringBuilder sb = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
log.error("Fail to populate request body", e);
}
return sb.toString();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Component
@WebFilter(urlPatterns = "/*")
@Slf4j
public class RequestContentFilterHaha implements Filter {

private final static String CONTENT_ENCODING = "Content-Encoding";
private final static String GZIP_TYPE = "gzip";

@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("Initializing request content filter ...");
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
if (servletRequest instanceof HttpServletRequest) {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
try {
String encodeType = httpServletRequest.getHeader(CONTENT_ENCODING);
BodyReaderHttpServletRequestWrapper requestWrapper = new BodyReaderHttpServletRequestWrapper(httpServletRequest, GZIP_TYPE.equals(encodeType));
filterChain.doFilter(requestWrapper, servletResponse);
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.warn("read request request body error: {}, url: {}, method: {}", e.getMessage(), httpServletRequest.getRequestURL(), httpServletRequest.getMethod());
}
}
} else {
filterChain.doFilter(servletRequest, servletResponse);
}
}

@Override
public void destroy() {

}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server:
compression:
enabled: true
min-response-size: 200
mimeTypes:
- text/html
- text/xml
- application/xml
- application/octet-stream
- application/json
feign:
okhttp:
enabled: true
httpclient:
enabled: false
max-connections: 1000
max-connections-per-route: 50
ribbon:
http:
client:
enabled: false
okhttp:
enabled: true

客户端配置

1
2
3
4
5
6
7
8
9
10
11
12
13
feign:
okhttp:
enabled: true
httpclient:
enabled: false
max-connections: 1000
max-connections-per-route: 50
ribbon:
http:
client:
enabled: false
okhttp:
enabled: true
1
2
3
4
5
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>${version.feign.okhttp}</version>
</dependency>