Springboot 之 Filter 实现超大响应 JSON 数据压缩

简介项目中,请求时发送超大 json 数据外;响应时也有可能返回超大 json数据 。上一篇实现了请求数据的 gzip 压缩 。本篇通过 filter 实现对响应 json 数据的压缩 。先了解一下以下两个概念:

  • 请求头:Accept-Encoding : gzip告诉服务器,该浏览器支持 gzip 压缩
  • 响应头:Content-Encoding : gzip告诉浏览器,输出信息使用了 gzip 进行压缩

Springboot 之 Filter 实现超大响应 JSON 数据压缩

文章插图
pom.xml 引入依赖<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.olive</groupId> <artifactId>response-compression</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>response-compression</name> <url>http://maven.apache.org</url> <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.14</version><relativePath /> <!-- lookup parent from repository --> </parent> <properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target> </properties> <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.14</version></dependency><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.9.0</version></dependency> </dependencies></project>对Response进行包装GzipResponseWrapper 类重新定义了输出流,拦截需要输出的数据,直接缓存到 ByteArrayOutputStream 中 。
package com.olive.filter;import lombok.extern.slf4j.Slf4j;import javax.servlet.ServletOutputStream;import javax.servlet.WriteListener;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpServletResponseWrapper;import java.io.*;@Slf4jpublic class GzipResponseWrapper extends HttpServletResponseWrapper {/*** 字节数组缓冲流,用来保存截获到的输出数据*/private ByteArrayOutputStream buffer;/*** 重新定义servlet输出流,改变输出目的地将响应内容输出到给定的字节数组缓冲流中*/private GzipResponseWrapper.CustomServletOutputStream servletOutputStream;/*** 同上*/private PrintWriter writer;public GzipResponseWrapper(HttpServletResponse response) {super(response);//original HttpServletResponse objectbuffer = new ByteArrayOutputStream();servletOutputStream = new GzipResponseWrapper.CustomServletOutputStream(buffer);try {writer = new PrintWriter(new OutputStreamWriter(buffer, response.getCharacterEncoding()), true);} catch (UnsupportedEncodingException e) {log.error("GZipHttpServletResponse", e);}}@Overridepublic ServletOutputStream getOutputStream() throws IOException {return servletOutputStream;}@Overridepublic PrintWriter getWriter() throws IOException {return writer;}@Overridepublic void flushBuffer() throws IOException {if (servletOutputStream != null) {servletOutputStream.flush();}if (writer != null) {writer.flush();}}/*** 向外部提供一个获取截获数据的方法* @return 从response输出流中截获的响应数据*/public byte[] getOutputData() throws IOException {flushBuffer();return buffer.toByteArray();}private static class CustomServletOutputStream extends ServletOutputStream {/*** 字节数组缓冲流,用来保存截获到的输出数据*/private ByteArrayOutputStream buffer;public CustomServletOutputStream(ByteArrayOutputStream buffer) {this.buffer = buffer;}@Overridepublic boolean isReady() {return true;}@Overridepublic void setWriteListener(WriteListener listener) {}/*** 重写输出流相关的方法* 将输出数据写出到给定的ByteArrayOutputStream缓冲流中保存起来* @param b 输出的数据* @throws IOException*/@Overridepublic void write(int b) throws IOException {buffer.write(b);}}}定义GzipFilter对输出进行拦截GzipFilter 拦截器获取缓存的需要输出的数据,进行压缩,在输出数据之前先设置响应头Content-Encoding : gzip
package com.olive.filter;import lombok.extern.slf4j.Slf4j;import org.springframework.http.HttpHeaders;import javax.servlet.*;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.*;import java.util.zip.GZIPOutputStream;/** * 压缩过滤器 * * 功能:对于返回给客户端的数据进行gzip压缩,提高响应速度 * 实现说明: *要对response对象的输出数据进行gzip压缩,首先得拿到后面servlet(controller)进行业务处理后往response对象里写入的数据 *可以通过重写response对象,修改该对象内部的输出流,使该流写出数据时写出到给定的字节数组缓冲流当中, *并在重写后的response对象内部提供一个获取该字节数组缓冲流的方法,这样就可以截获响应数据 *然后就可以对截获的响应数据通过Gzip输出流进行压缩输出即可; *因为响应数据是gzip压缩格式,不是普通的文本格式所以需要通过response对象(响应头)告知浏览器响应的数据类型 */@Slf4jpublic class GzipFilter implements Filter {private final String GZIP = "gzip";public void destroy() {log.info("GzipFilter destroy");}public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {log.info("GzipFilter start");HttpServletRequest request = (HttpServletRequest) req;HttpServletResponse response = (HttpServletResponse) resp;String acceptEncoding = request.getHeader(HttpHeaders.ACCEPT_ENCODING);//searching for 'gzip' in ACCEPT_ENCODING headerif( acceptEncoding != null && acceptEncoding.indexOf(GZIP) >= 0){GzipResponseWrapper gzipResponseWrapper = new GzipResponseWrapper(response);//pass the customized response object to controller to capture the output datachain.doFilter(request, gzipResponseWrapper);//get captured databyte[] data = https://www.huyubaike.com/biancheng/gzipResponseWrapper.getOutputData();log.info("截获到数据:" + data.length + " bytes");//get gzip dataByteArrayOutputStream gzipBuffer = new ByteArrayOutputStream();GZIPOutputStream gzipOut = new GZIPOutputStream(gzipBuffer);gzipOut.write(data);gzipOut.flush();gzipOut.close();byte[] gzipData = https://www.huyubaike.com/biancheng/gzipBuffer.toByteArray();log.info("压缩后数据:" + gzipData.length + " bytes");//set response header and outputresponse.setHeader(HttpHeaders.CONTENT_ENCODING, GZIP);response.getOutputStream().write(gzipData);response.getOutputStream().flush();}else{chain.doFilter(req, resp);}}public void init(FilterConfig config) throws ServletException {log.info("GzipFilter init");}}

推荐阅读