Skip to content

Commit

Permalink
feat: 添加返回结果压缩示例
Browse files Browse the repository at this point in the history
  • Loading branch information
liuyueyi committed Nov 8, 2023
1 parent 1ada0fb commit ae326bc
Show file tree
Hide file tree
Showing 9 changed files with 402 additions and 8 deletions.
8 changes: 1 addition & 7 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,7 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependency>3
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand Down
3 changes: 2 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ Rainbow Brackets: 不同层级的括号颜色不一样
| [205-web-thymeleaf](spring-boot/205-web-thymeleaf) | 【web】thymeleaf引擎整合 | thymeleaf |
| [206-web-beetl](spring-boot/206-web-beetl) | 【web】beetl引擎整合 | beetl |
| [207-web-response](spring-boot/207-web-response) | 【web】http响应的各种姿势 | 基本数据返回 <br/> 重定向 <br/> 错误页面配置 <br/> 定制http code |
| [207-web-res-gzip](spring-boot/207-web-res-gzip) | 【web】返回结果压缩 | json/text/静态资源返回压缩 |
| [208-web-mapping](spring-boot/208-web-mapping) | 【web】自定义url映射规则 | `RequestCondition` |
| [209-web-error](spring-boot/209-web-error) | 【web】全局异常处理 | `ControllerAdvice`, `ExceptionHandler` |
| [210-web-filter](spring-boot/210-web-filter) | 【web】filter使用姿势 | HttpFilter过滤器 |
Expand Down Expand Up @@ -575,7 +576,7 @@ web系列的东西就比较多了,基本上日常开发中,你需要的都
- [【WEB系列】404、500异常页面配置](http://spring.hhui.top/spring-blog/2019/09/30/190930-SpringBoot%E7%B3%BB%E5%88%97%E6%95%99%E7%A8%8Bweb%E7%AF%87%E4%B9%8B404%E3%80%81500%E5%BC%82%E5%B8%B8%E9%A1%B5%E9%9D%A2%E9%85%8D%E7%BD%AE/)
- [【WEB系列】xml传参与返回使用姿势](http://spring.hhui.top/spring-blog/2020/07/06/200706-SpringBoot%E7%B3%BB%E5%88%97%E6%95%99%E7%A8%8B%E4%B9%8Bxml%E4%BC%A0%E5%8F%82%E4%B8%8E%E8%BF%94%E5%9B%9E%E4%BD%BF%E7%94%A8%E5%A7%BF%E5%8A%BF/)
- [【WEB系列】整合resin容器](http://spring.hhui.top/spring-blog/2021/01/26/210126-SpringBoot%E7%B3%BB%E5%88%97%E6%95%B4%E5%90%88resin%E5%AE%B9%E5%99%A8/)

- [【WEB系列】压缩返回结果实例演示 | 一灰灰Blog](http://spring.hhui.top/spring-blog/2023/11/08/231108-SpringBoot%E7%B3%BB%E5%88%97%E4%B9%8B%E5%8E%8B%E7%BC%A9%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C%E5%AE%9E%E4%BE%8B%E6%BC%94%E7%A4%BA/)

**采坑、填坑**

Expand Down
49 changes: 49 additions & 0 deletions spring-boot/207-web-res-gzip/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
## web-res-gzip
### 项目说明

本项目主要介绍Spring返回结果压缩的使用姿势

1. 压缩配置

```yaml
server:
compression:
enabled: true # 开启支持gzip压缩
min-response-size: 128 # 当响应长度超过128时,才执行压缩
```
2. 返回JsonObject无效场景
当接口返回的是 application/json 时,上面配置的 min-response-size 无效,全部都走压缩,原因是因为无法计算 content-length,可行的解决方案:
- 设置统一的Filter, 通过`ContentCachingResponseWrapper`包装返回结果 -> 见:Application中的配置

3. 返回压缩的静态资源

```java
public class Application implements WebMvcConfigurer {
/**
* 配置返回的静态资源的压缩与缓存方式
*
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.maxAge(7, TimeUnit.DAYS).cachePrivate())
.resourceChain(true)
// https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/reactive/resource/EncodedResourceResolver.html
.addResolver(new EncodedResourceResolver())
.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
}
}
```

上面配置了静态资源文件的缓存策略,压缩方式,需要注意 EncodedResourceResolver 需要在 VersionResourceResolver 之前(上面Spring的连接说明)

### 博文说明

本项目对应的博文内容为

[231108-SpringBoot系列之压缩返回结果实例演示 | 一灰灰Blog](http://spring.hhui.top/spring-blog/2023/11/08/231108-SpringBoot%E7%B3%BB%E5%88%97%E4%B9%8B%E5%8E%8B%E7%BC%A9%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C%E5%AE%9E%E4%BE%8B%E6%BC%94%E7%A4%BA/)
32 changes: 32 additions & 0 deletions spring-boot/207-web-res-gzip/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<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>
<parent>
<groupId>com.git.hui.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>

<artifactId>207-web-res-gzip</artifactId>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>


<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.git.hui.boot.web;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.http.CacheControl;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.resource.EncodedResourceResolver;
import org.springframework.web.servlet.resource.VersionResourceResolver;
import org.springframework.web.util.ContentCachingResponseWrapper;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
* @author YiHui
* @date 2023/11/6
*/
@SpringBootApplication
public class Application implements WebMvcConfigurer {

/**
* 配置返回的静态资源的压缩与缓存方式
*
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.maxAge(7, TimeUnit.DAYS).cachePrivate())
.resourceChain(true)
.addResolver(new EncodedResourceResolver())
.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
}

/**
* 所有的返回结果,包装一个 content-length 返回
*
* @return
*/
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterBean = new FilterRegistrationBean();
filterBean.setFilter(new AddContentLengthFilter());
filterBean.setUrlPatterns(Arrays.asList("*"));
return filterBean;
}

/**
* 现象:当返回的是json对象时, server.compression.min-response-size不起作用,不管这个对象的大小,默认全部做gzip压缩。
* 原因:
* - 当返回的是字符串,即Content-Type: text/plain 时,会设置Content-Length,则会根据实际返回的大小来判断是否需要进行gzip压缩
* - 而当返回的是对象,即Content-Type: application/json时,不会设置Content-Length,服务端无法判断长度,并且是通过Transfer-Encoding: chunked的方式发送给客户端,因此一定会做压缩。
* 解决方案:
* - 加上全局的 content-length
*/
class AddContentLengthFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// ContentCachingResponseWrapper会缓存所有写给OutputStream的数据,并且因为缓存了内容,所以可以获取Content-Length并帮忙设置
ContentCachingResponseWrapper cacheResponseWrapper;
if (response instanceof ContentCachingResponseWrapper) {
cacheResponseWrapper = (ContentCachingResponseWrapper) response;
} else {
cacheResponseWrapper = new ContentCachingResponseWrapper(response);
}

filterChain.doFilter(request, cacheResponseWrapper);
cacheResponseWrapper.copyBodyToResponse();
}
}

public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package com.git.hui.boot.web.rest;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

/**
* 返回结果
*
* @author YiHui
* @date 2023/11/6
*/
@Controller
public class RspController {

@Data
@NoArgsConstructor
@AllArgsConstructor
public static final class UserInfo {
private String name;
private Integer code;
}

private List<UserInfo> allUsers(int size) {
List<UserInfo> list = new ArrayList<>();
for (int i = 0; i < size; i++) {
list.add(new UserInfo(UUID.randomUUID() + "_用户", i));
}
return list;
}

/**
* 对象方式返回, 用于模拟不管返回的内容多小,都会进行gzip压缩
*
* @return
*/
@ResponseBody
@GetMapping(path = "list")
public List<UserInfo> list(@RequestParam(name = "size", defaultValue = "128", required = false) Integer size) {
List<UserInfo> list = allUsers(size);
return list.subList(0, size);
}

/**
* 字符串方式返回, 会根据设置的最小长度,来确定是否会对返回结果进行gzip压缩
*
* @return
*/
@ResponseBody
@GetMapping(path = "strList")
public String strList(@RequestParam(name = "size", defaultValue = "128", required = false) Integer size) {
List<UserInfo> list = allUsers(size);
return JSON.toJSONString(list);
}


/**
* 模拟返回文件的case
*
* @param size
* @return
*/
@ResponseBody
@GetMapping(path = "file", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public byte[] fileResp(int size) {
List<UserInfo> list = allUsers(size);
String ans = JSON.toJSONString(list);
return ans.getBytes();
}


@Data
@JsonFormat(with = {JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES})
public static class TT {
String pOrder;
String SOrderNum;
Long c2Num;
}

public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
TT t = new TT();
t.setPOrder("1230981217834143");
t.setSOrderNum("1230981212334143");
t.setC2Num(12L);
String str = mapper.writeValueAsString(t);
TT t2 = mapper.readValue(str, TT.class);
System.out.println(t2);



// 下面这个主要用于解决gzip解压
// RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory(HttpClientBuilder.create().build()));
RestTemplate restTemplate = new RestTemplate();
HttpEntity<String> ans = restTemplate.getForEntity("http://localhost:8080/strList?size=1", String.class);
System.out.println(ans);

HttpEntity<ArrayList> ans2 = restTemplate.getForEntity("http://localhost:8080/list?size=1", ArrayList.class);
System.out.println(ans2);

HttpEntity<ArrayList> a3 = restTemplate.getForEntity("http://localhost:8080/static/txt.json", ArrayList.class);
System.out.println(a3);


// 以下时解决京东开放接口的数据,返回中文乱码情况

HttpHeaders DEFAULT_HEAD = new HttpHeaders();
// DEFAULT_HEAD.set("Accept", "*/*");
// 支持gzip压缩传输
// gzip 压缩,默认的RestTemplate不支持,需要借助 HttpComponentsClientHttpRequestFactory
// deflate zlib格式压缩, Brotli压缩 默认的RestTemplate都可以
DEFAULT_HEAD.set("Accept-Encoding", "deflate,br,gzip");
DEFAULT_HEAD.setAcceptCharset(Arrays.asList(StandardCharsets.UTF_8));
DEFAULT_HEAD.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
//封装请求头
HttpEntity<MultiValueMap<String, Object>> formEntity = new HttpEntity<MultiValueMap<String, Object>>(DEFAULT_HEAD);

HttpEntity<String> ans3 = restTemplate.exchange("https://api-iop.jd.com/oauth2/accessToken", HttpMethod.POST, formEntity, String.class);
System.out.println(ans3);

DEFAULT_HEAD.set("Accept", "*/*");
formEntity = new HttpEntity<>(DEFAULT_HEAD);
HttpEntity<String> ans4 = restTemplate.exchange("https://api-iop.jd.com/oauth2/accessToken", HttpMethod.POST, formEntity, String.class);
System.out.println(ans4);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
server:
compression:
enabled: true # 开启支持gzip压缩
min-response-size: 128 # 当响应长度超过128时,才执行压缩
Loading

0 comments on commit ae326bc

Please sign in to comment.