响应压缩是 Web 应用一种常见的优化手段,通过压缩算法减小传输数据的体积,提高传输效率、节约带宽。客户端接收到数据后,使用相同的算法对数据进行解压从而获取到原始数据。
客户端和服务器需要通过 Header 来协商双方支持的压缩算法。
Accept-Encoding
:请求头,告诉服务器客户端支持的压缩算法(多个使用逗号分割)。例如:Accept-Encoding: gzip, deflate
。Content-Encoding
:响应头,告诉客户端当前 Payload 使用的编码方式(压缩算法)。例如:Content-Encoding: gzip
。
常用的压缩算法如下:
- gzip
- deflate
- br
JDK 提供了对 GZIP 压缩算法的实现:GZIPOutputStream
和 GZIPInputStream
,我们可以用它们来实现 Gzip 压缩和解压缩。
使用 Gzip 压缩响应 {#使用-gzip-压缩响应}
在 Spring Boot 应用中创建一个 Controller,使用 GZIPOutputStream
把一张图片文件(20 KB)压缩后响应给客户端。
package cn.springdoc.demo.web.controller;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.zip.GZIPOutputStream;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@RestController
@RequestMapping("/demo")
public class DemoController {
@GetMapping
public void file (HttpServletRequest request, HttpServletResponse response) throws IOException {
// 20.0 KB 大小的图片文件
Path file = Paths.get("C:\\Users\\KevinBlandy\\Desktop\\springdoc-logo.png");
// 设置文件类型
response.setContentType(Optional.ofNullable(Files.probeContentType(file)).orElse(MediaType.APPLICATION_OCTET_STREAM_VALUE));
// 设置压缩方式为 gzip 【关键点 1:设置正确的 CONTENT_ENCODING 头】
response.setHeader(HttpHeaders.CONTENT_ENCODING, "gzip");
// 包装 response 流为 gzip 流 【关键点 2:使用 GZIPOutputStream 封装 response 流,并写出数据】
try(GZIPOutputStream gzipOutputStream = new GZIPOutputStream(response.getOutputStream())){
// 响应给客户端
Files.copy(file, gzipOutputStream);
}
}
}
如上。关键点在于设置 CONTENT_ENCODING
Header 为 gzip
,告诉浏览器使用了 gzip 压缩算法,浏览器会自动使用相同算法进行解压缩。
最后,使用 GZIPOutputStream
封装 response 流,往 gzipOutputStream
中写入的数据就会被 gzip 压缩。
启动应用,使用浏览器访问:http://localhost:8080/demo
:
通过控制台的网络面板,你可以看到:
- 浏览器通过
Accept-Encoding
告诉服务器,它支持gzip
压缩算法。 - 服务器正确地指定了 Payload 的编码类型为
gzip
。 - 由于使用了 Gzip 压缩,数据的传输体积小于文件体积。
图片在浏览器中预览成功,也说明服务器和客户端都进行了正确的编解码。
Spring Boot 配置响应压缩 {#spring-boot-配置响应压缩}
对于这种如此常用的功能,Spring Boot 早已提供了开箱即用的支持。
可以在 application.yaml
/ application.properties
文件中配置如下属性,开启全局 Gzip 响应压缩:
| 属性 | 说明 | 默认值 |
|------------------------------------------------------------------|-----------------------------------------------|---------------------------------------------------------------------------------------------------------------------------|
| server.compression.enabled
| 是否开启全局响应压缩 | false
|
| server.compression.excluded-user-agents
| 以逗号分隔的 User Agent 列表,对这些 User Agent 的响应不会被压缩。 | |
| server.compression.mime-types
| 逗号分割的文件 MIME Type(媒体类型),这些类型的文件才会被压缩。 | [text/html, text/xml, text/plain, text/css, text/javascript, application/javascript, application/json, application/xml]
|
| server.compression.min-response-size
| 进行压缩的最低 Content-Length
值。 | 2KB
|
在 application.yaml
中添加如下配置:
server:
compression:
# 开启响应压缩
enabled: true
mime-types:
- image/png # 压缩 png 图片
# 进行压缩的最小体积
min-response-size: 1KB
其实只需要设置 server.compression.enabled=true
即可,这里故意设置 server.compression.min-response-size=1KB
完全是为了进行演示,因为示例图片不足 2KB
。
server.compression.min-response-size
值不应该过小,否则压缩后的数据体积可能比原始数据还大。
还需要覆盖 server.compression.mime-types
配置,因为默认配置的压缩的文件类型列表中不包含图片。
修改 Controller,如下:
@GetMapping
public ResponseEntity<Resource> file (HttpServletRequest request, HttpServletResponse response) throws IOException {
// 20.0 KB 大小的图片文件
Path file = Paths.get("C:\\Users\\KevinBlandy\\Desktop\\springdoc-logo.png");
return ResponseEntity.ok()
.contentType(MediaType.IMAGE_PNG) // 正确设置图片的 Content Type,浏览器才会预览图片
.body(new InputStreamResource(Files.newInputStream(file)));
}
这次不自己使用 GZIPOutputStream
进行压缩响应,而是直接返回 ResponseEntity<Resource>
对象。这也是关键点,如果你想基于配置的全局 Gzip 响应压缩生效,则不能自己使用 HttpServletResponse
进行数据响应 ,必须要通过返回对象,由 DispatcherServlet
处理,全局响应压缩才会生效。
重启应用,用浏览器再次请求 http://localhost:8080/demo
,你会发现结果跟上节中的测试结果一样。全局 Gzip 压缩配置生效。