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
Springboot 之 Filter 实现超大响应 JSON 数据压缩的更多相关文章
- Springboot添加filter方法
在springboot添加filter有两种方式: (1).通过创建FilterRegistrationBean的方式(建议使用此种方式,统一管理,且通过注解的方式若不是本地调试,如果在filter中 ...
- Springboot使用Filter以及踩过的坑
Springboot使用Filter以及踩过的坑 在Springboot中使用Filter有两种方式,注解方式,注册bean方式 一.注解@WebFilter 1.实现Filter接口(javax.s ...
- springmvc 怎么响应json数据
springmvc 怎么响应json数据@Controller@RequestMapping("/items") class ItemsController{ @RequestM ...
- 02-02:springboot 整合filter
1.通过注解扫描完成Filter组件的注册 1.1编写filter (添加拦截的servlet) //@WebFilter(filterName = "FirstFilter",u ...
- SpringBoot自定义Filter
SpringBoot自定义Filter SpringBoot自动添加了OrderedCharacterEncodingFilter和HiddenHttpMethodFilter,当然我们可以自定 义F ...
- 【使用篇二】SpringBoot整合Filter(2)
两种方式: 通过注解扫描完成 Filter 组件的注册 通过方法完成 Filter 组件的注册 一.通过注解扫描完成 Filter 组件的注册 1. 编写Filter类 /** * SpringBoo ...
- springboot整合filter
新建一个项目 新建Firstfilter类 Firstfliter.java package com.example.filter; import java.io.IOException; impor ...
- spring MVC 返回值信息和ResponseBody的响应json数据
spring mvc的界面返回: 如果我们定义的返回类型是String 那么我们返回的时候直接写入 我们的界面的名字就可以了 springmvc会自动去找到我们的界面,如果是void类型的返回那么 ...
- idea创建springboot工程,总出现响应超时问题,或者无法连接http://start.spring.io导致创建失败
问题描述如下: idea创建springboot工程,总出现响应超时问题,或者无法连接http://start.spring.io导致创建失败 从我出现此类问题几次的解决方案 依照解决效率分为一下三种 ...
随机推荐
- 开源一个自动整理B站UWP客户端软件进行批量下载的视频文件的小工具BiliVideosReoganizeHelper
大家都知道B站是一个很受欢迎的视频学习网站,上面有很多无私的up主上传了大量优秀的教学视频,在此向B站致敬,向广大UP主致敬. 有时,我们需要下载收藏一些视频,以防止以后找不到了.那么我们可以用B ...
- 使用Python3.7+Django2.0.4配合vue.js2.0的组件递归来实现无限级分类(递归层级结构)
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_133 所谓的无限极分类是啥?其实简单点说就是一个人类可以繁衍出多个后代,然后一个后代又可以分另外多个后代这样无限繁衍下去(可以想象 ...
- 把酒言欢话聊天,基于Vue3.0+Tornado6.1+Redis发布订阅(pubsub)模式打造异步非阻塞(aioredis)实时(websocket)通信聊天系统
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_202 "表达欲"是人类成长史上的强大"源动力",恩格斯早就直截了当地指出,处在蒙昧时代即低 ...
- Docker部署kafka|Go操作实践
前言 写作本文的背景是由于字节的暑期青训营中,某个项目要求编写一个简易的流处理引擎(flink),开发语言不限,推荐Java,本着好奇心的驱使,我打算使用Go语言进行部分尝试. 既然是流处理引擎,那么 ...
- 【原创】Python 使用jmpy模块加密|加固 python代码
本文所有教程及源码.软件仅为技术研究.不涉及计算机信息系统功能的删除.修改.增加.干扰,更不会影响计算机信息系统的正常运行.不得将代码用于非法用途,如侵立删! 使用jmpy模块 将py文件加密为so或 ...
- Apache DolphinScheduler 1.2.0 task 任务存储结构说明
本文章经授权转载 Table of Contents 任务总体存储 Shell节点 SQL节点 存储过程节点 SPARK节点 MapReduce(MR)节点 Python节点 Flink节点 HTTP ...
- 60行自己动手写LockSupport是什么体验?
60行自己动手写LockSupport是什么体验? 前言 在JDK当中给我们提供的各种并发工具当中,比如ReentrantLock等等工具的内部实现,经常会使用到一个工具,这个工具就是LockSupp ...
- java中使用 POI导出excel表格的简单实现
大概流程分7步: 1.创建工作簿 --> 2.创建sheet表 --> 3.创建row行(建议使用循环) --> 4.用row行逐一创建单元格(建议使用循环) --> 5.单元 ...
- pnpm凭什么这么快
前端包管理器层出不穷,pnpm算是一个后起之秀.它和npm有什么不同,为什么有了npm还要造一个pnpm? npm的问题 npm是最早的包管理器,安装nodejs自带npm,v3版本之前,npm安全依 ...
- 超实用在线工具!能将文字加密为Emoji表情
试想一下,如果你需要将一段比较敏感的内容发送给你的好友. 但如果这段内容不小心外泄,被别人看到了,可能会带来很多麻烦. 那么,有什么方法能够让传输的文本内容不那么容易被"看破"呢? ...