Springboot 中的 Filter 实现超大响应 JSON 数据压缩的方法

目录
  • 简介
  • pom.xml 引入依赖
  • 对Response进行包装
  • 定义GzipFilter对输出进行拦截
  • 注册 GzipFilter 拦截器
  • 定义 Controller
  • 定义 Springboot 引导类
  • 测试

简介

项目中,请求时发送超大 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 

到此这篇关于Springboot 中的 Filter 实现超大响应 JSON 数据压缩的文章就介绍到这了,更多相关Springboot JSON 数据压缩内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • springboot对压缩请求的处理方法

    目录 springboot对压缩请求的处理 一.Tomcat设置压缩原理 二.银联报文压缩 补充:java springbooot使用gzip压缩字符串 springboot对压缩请求的处理 最近对接银联需求,为了节省带宽,需要对报文进行压缩处理.但是使用springboot自带的压缩设置不起作用: server.compression.enabled=true server.compression.mime-types=application/javascript,text/css,appli

  • SpringBoot如何获取Get请求参数详解

    目录 前言 一.直接在请求路径中 二.参数跟在 ? 号后面 1,获取参数的基本方法 2.使用 map 来接收参数 3.接收一个集合 4.通过对象接收参数 总结 前言 利用 Spring Boot 来制作 Web 应用,就必定会涉及到前端与后台之间互相传递参数.下面演示 Controller 如何接收以 GET 方式传递过来的参数. 一.直接在请求路径中 (1).假设请求地址是如下这种 RESTful 风格,Springboot 这个参数值直接放在路径里面 http://localhost:808

  • springboot单文件下载和多文件压缩zip下载的实现

    单文件下载 //下载单个文件 public void downloadFile(HttpServletResponse response){ String path = "D:\test\ce\1.txt" File file = new File(path); if(file.exists()){ String fileName = file.getName(); response.setHeader("Content-Disposition", "at

  • springboot实现图片大小压缩功能

    本文实例为大家分享了springboot实现图片大小压缩的具体代码,供大家参考,具体内容如下 application.properties配置文件 #后端接收图片大小 spring.servlet.multipart.max-file-size=50MB spring.servlet.multipart.max-request-size=50MB java工具类 import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jav

  • 解析SpringBoot项目开发之Gzip压缩过程

    为了减少数据在网络中的传输量,从而减少传输时长,增加用户体验,浏览器大都是支持Gzip压缩技术的,http的请求头 Accept-Encoding:gzip, deflate 就表示这次请求可以接受Gzip压缩后的数据,图片不要进行压缩,因为图片完全可以在项目开发中使用压缩后的图片.压缩会有一定的CPU性能损耗. 下面介绍几种 Gzip压缩方式 1.SpringBoot开启Gzip压缩 在application.properties中加入如下配置: server.compression.enab

  • springboot中JSONObject遍历并替换部分json值

    使用场景 如何修改JSONObject 的值,如何替换json中的部分内容,比如检查报告我们再数据库存的是json格式的字符串varchar,然后前端传来确认更新报告的json,后台接口需要将前端传来的json里面的内容更新到后台数据库(当然,前端传来的不一定是完整的字符串,可能是一个,两个,总之只是部分不是全部).这个时候就需要使用这个方案了. 代码展示 @PutMapping("/result/{checkNum}") public ApiReturnObject update(@

  • SpringBoot中@ConfigurationProperties注解实现配置绑定的三种方法

    properties配置文件如下: human.name=Mr.Yu human.age=21 human.gender=male 如何把properties里面的配置绑定到JavaBean里面,以前我们的做法如下: public class PropertiesUtil { public static void getProperties(Person person) throws IOException { Properties properties = new Properties();

  • SpringBoot中使用Filter和Interceptor的示例代码

    一.Filter(过滤器) Filter接口定义在javax.servlet包中,是Servlet规范定义的,作用于Request/Response前后,被Servlet容器调用,当Filter被Sring管理后可以使用Spring容器资源. 实现一个Filter 自定义的过滤器需要实现javax.servlet.Filter,Filter接口中有三个方法: init(FilterConfig filterConfig):过滤器初始化的被调用. doFilter(ServletRequest s

  • Java中快速把map转成json格式的方法

    在日常的使用中,我们一般会遇到map转json,如果遍历的话会浪费大量的时间,其实我们拥有这样的jar包 复制代码 代码如下: The method *** is undefined for the type JSONObject 缺哪个包------ json-lib.jar 这样还是不行的 需要一个依赖的jar包要不然会报错 复制代码 代码如下: java.lang.ClassNotFoundException: net.sf.ezmorph.Morpher 当当当当   jar包是ezmo

  • springboot中filter的用法详解

    一.在spring的应用中我们存在两种过滤的用法,一种是拦截器.另外一种当然是过滤器.我们这里介绍过滤器在springboot的用法,在springmvc中的用法基本上一样,只是配置上面有点区别. 二.filter功能,它使用户可以改变一个 request和修改一个response. Filter 不是一个servlet,它不能产生一个response,它能够在一个request到达servlet之前预处理request,也可以在离开 servlet时处理response.换种说法,filter

  • Springboot 如何实现filter拦截token验证和跨域

    Springboot filter拦截token验证和跨域 背景 web验证授权合法的一般分为下面几种 使用session作为验证合法用户访问的验证方式 使用自己实现的token 使用OCA标准 在使用API接口授权验证时,token是自定义的方式实现起来不需要引入其他东西,关键是简单实用. 合法登陆后一般使用用户UID+盐值+时间戳使用多层对称加密生成token并放入分布式缓存中设置固定的过期时间长(和session的方式有些相同),这样当用户访问时使用token可以解密获取它的UID并据此验

  • SpringBoot之自定义Filter获取请求参数与响应结果案例详解

    一个系统上线,肯定会或多或少的存在异常情况.为了更快更好的排雷,记录请求参数和响应结果是非常必要的.所以,Nginx 和 Tomcat 之类的 web 服务器,都提供了访问日志,可以帮助我们记录一些请求信息. 本文是在我们的应用中,定义一个Filter来实现记录请求参数和响应结果的功能. 有一定经验的都知道,如果我们在Filter中读取了HttpServletRequest或者HttpServletResponse的流,就没有办法再次读取了,这样就会造成请求异常.所以,我们需要借助 Spring

  • Springboot工程中使用filter过程解析

    一.什么是filter 过滤器实际上就是用来对web资源进行拦截,做一些处理后再交给下一个过滤器或servlet处理 通常都是用来拦截request进行处理的,也可以对返回的response进行拦截处理 . filter可以在请求到达servlet前或者请求完成响应后进行后续的处理. 二.在springboot工程中使用filter 创建一个filter,并使用注解配置该filter的名称和拦截路径等属性 @WebFilter(filterName = "AFilter",urlPat

  • 详解json在SpringBoot中的格式转换

    @RestController自动返回json /** * json 三种实现方法 * 1 @RestController自动返回json */ @GetMapping("/json") public Student getjson() { Student student = new Student("bennyrhys",158 ); return student; } @ResponseBody+@Controller 组合返回json //@RestContr

  • SpringBoot中使用Servlet三大组件的方法(Servlet、Filter、Listener)

    本篇主要讲解SpringBoot当中使用Servlet三大组件,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧! 三大组件作用 1.Servlet Servlet是用来处理客户端请求的动态资源,也就是当我们在浏览器中键入一个地址回车跳转后,请求就会被发送到对应的Servlet上进行处理. Servlet的任务有: 1.接收请求数据:我们都知道客户端请求会被封装成HttpServletRequest对象,里面包含了请求头.参数等各种信息. 2.处理请求:通常我

随机推荐