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

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

简介

项目中,请求时发送超大 json 数据外;响应时也有可能返回超大 json数据。上一篇实现了请求数据的 gzip 压缩。本篇通过 filter 实现对响应 json 数据的压缩。

先了解一下以下两个概念:

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

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.*;
@Slf4j
public class GzipResponseWrapper extends HttpServletResponseWrapper {
/**
     * 字节数组缓冲流,用来保存截获到的输出数据
     */
private ByteArrayOutputStream buffer;
/**
     * 重新定义servlet输出流,改变输出目的地将响应内容输出到给定的字节数组缓冲流中
     */
private GzipResponseWrapper.CustomServletOutputStream servletOutputStream;
/**
     * 同上
     */
private PrintWriter writer;
public GzipResponseWrapper(HttpServletResponse response) {
super(response);
//original HttpServletResponse object
        buffer = new ByteArrayOutputStream();
        servletOutputStream = new GzipResponseWrapper.CustomServletOutputStream(buffer);
try {
            writer = new PrintWriter(new OutputStreamWriter(buffer, response.getCharacterEncoding()), true);
        } catch (UnsupportedEncodingException e) {
            log.error("GZipHttpServletResponse", e);
        }
    }
@Override
public ServletOutputStream getOutputStream() throws IOException {
return servletOutputStream;
    }
@Override
public PrintWriter getWriter() throws IOException {
return writer;
    }
@Override
public 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;
        }
@Override
public boolean isReady() {
return true;
        }
@Override
public void setWriteListener(WriteListener listener) {
        }
/**
         * 重写输出流相关的方法
         * 将输出数据写出到给定的ByteArrayOutputStream缓冲流中保存起来
         * @param b 输出的数据
         * @throws IOException
         */
@Override
public 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对象(响应头)告知浏览器响应的数据类型
 */
@Slf4j
public 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 header
if( acceptEncoding != null && acceptEncoding.indexOf(GZIP) >= 0){
GzipResponseWrapper gzipResponseWrapper = new GzipResponseWrapper(response);
//pass the customized response object to controller to capture the output data
            chain.doFilter(request, gzipResponseWrapper);
//get captured data
byte[] data = gzipResponseWrapper.getOutputData();
            log.info("截获到数据:" + data.length + " bytes");
//get gzip data
ByteArrayOutputStream gzipBuffer = new ByteArrayOutputStream();
GZIPOutputStream gzipOut = new GZIPOutputStream(gzipBuffer);
            gzipOut.write(data);
            gzipOut.flush();
            gzipOut.close();
byte[] gzipData = gzipBuffer.toByteArray();
            log.info("压缩后数据:" + gzipData.length + " bytes");
//set response header and output
            response.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");
    }
}

注册 GzipFilter 拦截器

package com.olive.config;
import com.olive.filter.GzipFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * 注册filter
 */
@Configuration
public class FilterRegistration {
@Bean
public FilterRegistrationBean<GzipFilter> gzipFilterRegistrationBean() {
        FilterRegistrationBean<GzipFilter> registration = new FilterRegistrationBean<>();
//Filter可以new,也可以使用依赖注入Bean
        registration.setFilter(new GzipFilter());
//过滤器名称
        registration.setName("gzipFilter");
//拦截路径
        registration.addUrlPatterns("/*");
//设置顺序
        registration.setOrder(1);
return registration;
    }
}

定义 Controller

该 Controller 非常简单,主要读取一个大文本文件,作为输出的内容。

package com.olive.controller;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import com.olive.vo.ArticleRequestVO;
import org.apache.commons.io.FileUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
  @RequestMapping("/getArticle")
  public Map<String, Object> getArticle(){
    Map<String, Object> result = new HashMap<>();
    result.put("code", 200);
    result.put("msg", "success");
    byte[] bytes = null;
    try {
      bytes = FileUtils.readFileToByteArray(new File("C:\\Users\\2230\\Desktop\\凯平项目资料\\改装车项目\\CXSSBOOT_DB_DDL-1.0.9.sql"));
    }catch (Exception e){
    }
    String content = new String(bytes);
    ArticleRequestVO vo = new ArticleRequestVO();
    vo.setId(1L);
    vo.setTitle("BUG弄潮儿");
    vo.setContent(content);
    result.put("body", vo);
    return result;
  }
}

Controller 返回数据的 VO

package com.olive.vo;
import lombok.Data;
import java.io.Serializable;
@Data
public class ArticleRequestVO implements Serializable {
private Long id;
private String title;
private String content;
}

定义 Springboot 引导类

package com.olive;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
    }
}

测试

测试的curl

curl -X POST http://127.0.0.1:8080/getArticle
相关文章
|
21天前
|
Java API Spring
在 Spring 配置文件中配置 Filter 的步骤
【10月更文挑战第21天】在 Spring 配置文件中配置 Filter 是实现请求过滤的重要手段。通过合理的配置,可以灵活地对请求进行处理,满足各种应用需求。还可以根据具体的项目要求和实际情况,进一步深入研究和优化 Filter 的配置,以提高应用的性能和安全性。
|
21天前
|
搜索推荐 Java Spring
Spring Filter深度解析
【10月更文挑战第21天】Spring Filter 是 Spring 框架中非常重要的一部分,它为请求处理提供了灵活的控制和扩展机制。通过合理配置和使用 Filter,可以实现各种个性化的功能,提升应用的安全性、可靠性和性能。还可以结合具体的代码示例和实际应用案例,进一步深入探讨 Spring Filter 的具体应用和优化技巧,使对它的理解更加全面和深入。
|
24天前
|
数据采集 JSON 数据处理
抓取和分析JSON数据:使用Python构建数据处理管道
在大数据时代,电商网站如亚马逊、京东等成为数据采集的重要来源。本文介绍如何使用Python结合代理IP、多线程等技术,高效、隐秘地抓取并处理电商网站的JSON数据。通过爬虫代理服务,模拟真实用户行为,提升抓取效率和稳定性。示例代码展示了如何抓取亚马逊商品信息并进行解析。
抓取和分析JSON数据:使用Python构建数据处理管道
|
10天前
|
SQL 前端开发 关系型数据库
SpringBoot使用mysql查询昨天、今天、过去一周、过去半年、过去一年数据
SpringBoot使用mysql查询昨天、今天、过去一周、过去半年、过去一年数据
39 9
|
10天前
|
JSON 数据格式 索引
Python中序列化/反序列化JSON格式的数据
【11月更文挑战第4天】本文介绍了 Python 中使用 `json` 模块进行序列化和反序列化的操作。序列化是指将 Python 对象(如字典、列表)转换为 JSON 字符串,主要使用 `json.dumps` 方法。示例包括基本的字典和列表序列化,以及自定义类的序列化。反序列化则是将 JSON 字符串转换回 Python 对象,使用 `json.loads` 方法。文中还提供了具体的代码示例,展示了如何处理不同类型的 Python 对象。
|
14天前
|
JSON 缓存 前端开发
PHP如何高效地处理JSON数据:从编码到解码
在现代Web开发中,JSON已成为数据交换的标准格式。本文探讨了PHP如何高效处理JSON数据,包括编码和解码的过程。通过简化数据结构、使用优化选项、缓存机制及合理设置解码参数等方法,可以显著提升JSON处理的性能,确保系统快速稳定运行。
|
15天前
|
存储 easyexcel Java
SpringBoot+EasyExcel轻松实现300万数据快速导出!
本文介绍了在项目开发中使用Apache POI进行数据导入导出的常见问题及解决方案。首先比较了HSSFWorkbook、XSSFWorkbook和SXSSFWorkbook三种传统POI版本的优缺点,然后根据数据量大小推荐了合适的使用场景。接着重点介绍了如何使用EasyExcel处理超百万数据的导入导出,包括分批查询、分批写入Excel、分批插入数据库等技术细节。通过测试,300万数据的导出用时约2分15秒,导入用时约91秒,展示了高效的数据处理能力。最后总结了公司现有做法的不足,并提出了改进方向。
|
7天前
|
JSON API 数据安全/隐私保护
拍立淘按图搜索API接口返回数据的JSON格式示例
拍立淘按图搜索API接口允许用户通过上传图片来搜索相似的商品,该接口返回的通常是一个JSON格式的响应,其中包含了与上传图片相似的商品信息。以下是一个基于淘宝平台的拍立淘按图搜索API接口返回数据的JSON格式示例,同时提供对其关键字段的解释
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
143 1
|
17天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,创建并配置 Spring Boot 项目,实现后端 API;然后,使用 Ant Design Pro Vue 创建前端项目,配置动态路由和菜单。通过具体案例,展示了如何快速搭建高效、易维护的项目框架。
95 62