spring cloud zuul 1.x 开启gzip压缩

zuul1.x本身是不支持gzip压缩的。而且zuul已经不再维护了,这样一些还在使用spring cloud老生态的程序员头疼。我在整个互联网上搜了一圈,都没有完整的,开箱即用的关于zuul开启gzip的资料。

但是零碎的技术问答还是有的,于是我综合了网上的一些讨论,基于spring cloud zull 1.x写了一个gzip的拦截器。这个拦截器应该算是比较完整了,对于各种情况都有所考虑。

gzip拦截器代码

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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144

/**
* @author https://www.jdkdownload.com
*/
@Slf4j
public class GZipResponseFilter extends ZuulFilter {
private static final String GZIP_CONTENT_ENCODING = "gzip";
private static final String HEADER_CONTENT_ENCODING = "content-encoding";
private static final String HEADER_CONTENT_LENGTH = "content-length";
private static final String CHARSET = "utf8";
private static final int MIN_GZIP_SIZE = 860;
private static DynamicStringSetProperty GZIPPABLE_CONTENT_TYPES = new DynamicStringSetProperty("zuul.gzip.contenttypes",
"text/html,application/x-javascript,text/css,application/javascript,text/javascript,text/plain,text/xml," +
"application/json,application/vnd.ms-fontobject,application/x-font-opentype,application/x-font-truetype," +
"application/x-font-ttf,application/xml,font/eot,font/opentype,font/otf,image/svg+xml,image/vnd.microsoft.icon",
",");

@Override
public String filterType() {
return "post";
}

@Override
public int filterOrder() {
return 999;
}

private boolean isRightSizeForGzip(RequestContext context) {
Long contentLength=context.getOriginContentLength();
if(!Objects.isNull(contentLength)&&contentLength>0){
return contentLength > MIN_GZIP_SIZE;
}
try {
String responseBody = StreamUtils.copyToString(context.getResponseDataStream(), Charset.forName(CHARSET));
if (StringUtils.isEmpty(responseBody)) {
return false;
}
if (responseBody.getBytes(Charset.forName(CHARSET)).length > MIN_GZIP_SIZE) {
return true;
}
} catch (IOException e) {
log.error("gzip filter error",e);
}
return false;
}

private boolean isGzippableContentType(String contentType) {
if (contentType != null) {
int charsetIndex = contentType.indexOf(';');
if (charsetIndex > 0) {
contentType = contentType.substring(0, charsetIndex);
}
return GZIPPABLE_CONTENT_TYPES.get().contains(contentType.toLowerCase());
}
return false;
}

private String findResponseHeader(RequestContext context,String name){
List<Pair<String, String>> headers = context.getOriginResponseHeaders();
if(headers!=null&&headers.size()>0){
Pair<String, String> pair= headers.stream()
.filter(p->p.first().toLowerCase().equals(name))
.findFirst()
.orElse(null);
return Objects.isNull(pair)?null:pair.second();
}
return null;
}

//判断是否需要gzip,如果返回true则表示需要,否则不需要
@Override
public boolean shouldFilter() {
RequestContext context = getCurrentContext();
HttpServletResponse response = context.getResponse();
HttpServletRequest request = context.getRequest();
String responseBody = context.getResponseBody();

if (!StringUtils.isEmpty(responseBody)) {
return false;
}

String acceptEncoding = request.getHeader("Accept-Encoding");

boolean isGzipRequested = false;
if (!StringUtils.isEmpty(acceptEncoding) && acceptEncoding.toLowerCase().indexOf(GZIP_CONTENT_ENCODING) != -1) {
isGzipRequested = true;
}

boolean isResponseGzipped = false;
String contentEncoding = findResponseHeader(context,"content-encoding");
if (!StringUtils.isEmpty(contentEncoding) && contentEncoding.toLowerCase().indexOf(GZIP_CONTENT_ENCODING) != -1) {
isResponseGzipped = true;
}

String contentType = findResponseHeader(context,"content-type");
final boolean shouldGzip = isGzippableContentType(contentType) && isGzipRequested && !isResponseGzipped && isRightSizeForGzip(context);

return shouldGzip;
}


@Override
public Object run() {
GZIPOutputStream gzip = null;
InputStream responseDataStream =null;
try {
RequestContext context = getCurrentContext();
responseDataStream = context.getResponseDataStream();
// 将原始的响应体解析出来
String body = StreamUtils.copyToString(responseDataStream, Charset.forName(CHARSET));
// gzip压缩响应体
ByteArrayOutputStream bos = new ByteArrayOutputStream(body.length());
gzip = new GZIPOutputStream(bos);
gzip.write(body.getBytes(CHARSET));
gzip.finish();
gzip.flush();
byte[] compressed = bos.toByteArray();
context.setResponseDataStream(new ByteArrayInputStream(compressed));
//设置gzip头
resetHeader(context.getResponse());
} catch (IOException e) {
rethrowRuntimeException(e);
} finally {
try {
if (Objects.nonNull(gzip)) {
gzip.close();
gzip = null;
}
if (Objects.nonNull(responseDataStream)) {
responseDataStream.close();
responseDataStream = null;
}
} catch (IOException e) {
rethrowRuntimeException(e);
}
}
return null;
}

public void resetHeader(HttpServletResponse response) {
response.addHeader(HEADER_CONTENT_ENCODING, GZIP_CONTENT_ENCODING);
response.setHeader(HEADER_CONTENT_LENGTH,null);
}
}

使用方法也非常简单,将上面的类复制到你项目,然后在你的Application类中加入这个拦截器的初始化即可使用。

1
2
3
4
@Bean
public GZipResponseFilter accessFilter() {
return new GZipResponseFilter();
}

zuul开启Gzip的条件

  • 客户端(浏览器)发起的请求中,必须包含Accept-Encoding:gzip,说明客户端可以接授gzip请求。
  • 当zuul转发给其他微服务时,得到响应请求中,不包含content-encoding:gzip。这个判断是为了防止重复gzip。
  • 同样的,转发得到的响应中content-type限定在了几个类型里(比如:text/htmlapplication/x-javascript)。因为不是所有的请求都可以gzip加密。
  • 响应体的大小必须超过860byte,因为如果小于这个响应体的体积大小的话,压缩导致的开销会大于解压和传输的开销。这样的话,相当于捡了芝麻丢了西瓜。

如何查看是否成功开启了gzip压缩

1.通过浏览器来查看

  • 进入网页之后,按F12可以打开开发者工具,再将你的视图切换到network这个选项卡。

  • 按F5重新加载网络资源,然后选中你想要查看的资源。

  • 如果响应头中(response header)存在content-type:gzip的话,说明开启成功。

    通过第三方工具进行检测

  • 进入https://www.giftofspeed.com/gzip-test/

  • 填写自己的网址

  • 得到结果

image-20201123203622849

http中Gzip的原理

gzip毫无疑问是一个压缩协议,除开这个压缩协议其实还有其他的协议,比如当你发起请求的时候,你就可以看到deflatebr

image-20201123203915903

这意味着服务端和客户端必须同时拥有相同的压缩解压协议。但是客户端并不清楚服务端具体的能力。那么这里就是一个gzip协商的核心过程了。

  • 客户端发起请求给服务端,同时在请求头中携带accept-encoding头,该http头说明浏览器端可以支持哪些解压协议。浏览器端会将自己支持的所有解压协议都带上(因为不知道服务器端到底支持哪个)。

  • 服务端收到请求后,解析accept-encoding头,然后知道了客户端支持的解压协议,于是选第一款自己支持的协议将响应体进行压缩。

  • 服务端开始返回响应,此时在http头中包含了``content-type`头,这个http头告诉了浏览器我的响应体是由什么压缩协议制造的,于是浏览器就会选择相应的解压协议进行解压。

spring boot 开启gzip

在配置文件中设置:

1
2
3
4
5
6
#是否开启压缩
server.compression.enabled=true
#可以压缩的类型
server.compression.mime-types=application/json,application/xml,text/html,text/xml,text/plain
#最小压缩大小(byte),小于这个值就不压缩了。
server.compression.min-response-size=2048

原文地址:https://www.jdkdownload.com/zuul_enable_gzip.html