API接口優(yōu)化!基于Spring Boot 實現Deflate壓縮技術
環(huán)境:SpringBoot3.2.5
1. 簡介
一個應用的性能是決定用戶體驗好壞的關鍵因素。提高性能的最有效方法之一是減少服務器與客戶端之間傳輸的數據大小。這正是壓縮技術發(fā)揮作用的地方。Spring Boot 提供了對各種壓縮技術的內置支持,以優(yōu)化數據傳輸。
在本篇文章中,我們將探討Deflate壓縮,包括它為什么重要、何時使用它以及如何在Spring Boot應用程序中實現它。通過本篇文章你將清楚地了解如何使用Deflate壓縮來優(yōu)化應用程序的性能。
1.1 什么是Deflate壓縮?
Deflate是一種無損數據壓縮算法,它結合了LZ77算法和霍夫曼編碼來減小數據的大小。在Web應用程序中,Deflate被廣泛用于在向客戶端發(fā)送HTTP響應之前對其進行壓縮。
當客戶端(例如瀏覽器或API使用者)從服務器請求數據時,服務器可以使用Deflate對響應進行壓縮,從而減小通過網絡傳輸的數據大小??蛻舳嗽诮邮盏綌祿筮M行解壓縮。
1.2 為什么使用Deflate壓縮?
- 提高性能
網絡上的數據傳輸速度更快。延遲降低,特別是對于使用慢速或帶寬有限的連接的用戶而言。 - 節(jié)省帶寬
壓縮減少了通過網絡發(fā)送的數據量,這對于流量高或負載大的應用程序(例如,JSON或XML響應)非常重要。 - 提升用戶體驗
更快的響應時間帶來更好的用戶體驗,特別是對于移動用戶或從遠程位置訪問的應用程序的用戶而言。
1.3 應用場景
- 響應結果很大
如果你的API返回大的JSON或XML響應,壓縮數據可以顯著減少響應時間。 - 靜態(tài)內容
壓縮HTML、CSS和JavaScript文件等靜態(tài)資源可以改善頁面加載時間
注意:如果對應響應結果比較小的(小于2kb)時候反而使用壓縮技術會對性能造成影響。
2. 實戰(zhàn)案例
2.1 Deflate過濾器
public class DeflateCompressionFilter implements Filter {
private static final int MIN_RESPONSE_SIZE = 2 * 1024 ;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
String acceptEncoding = req.getHeader("Accept-Encoding");
// 這里我們更加請求header中的Accept-Encoding進行判斷,只有包含指定的值才進行處理
if (acceptEncoding == null || !acceptEncoding.toLowerCase().contains("deflate")) {
chain.doFilter(request, response);
return;
}
// 自定義Response包裝類,我們需要對響應結果進行獲取處理
DeflateResponseWrapper responseWrapper = new DeflateResponseWrapper(resp);
chain.doFilter(request, responseWrapper);
// 只有響應的數據大小超過這里指定的值(2KB)才進行壓縮處理
if (responseWrapper.getContentLength() > MIN_RESPONSE_SIZE) {
// 必須設置,否則客戶端將無法解析解壓數據
resp.setHeader("Content-Encoding", "deflate");
try (DeflaterOutputStream dos= new DeflaterOutputStream(resp.getOutputStream())) {
dos.write(responseWrapper.getCapturedData());
}
} else {
// Write the uncompressed response
resp.getOutputStream().write(responseWrapper.getCapturedData());
}
}
}
關鍵的注釋已經在源碼中進行了處理。
注意:這里沒有判斷響應數據的類型可以根據Content-Type進行判斷。
2.2 Response包裝類
public class DeflateResponseWrapper extends HttpServletResponseWrapper {
private final ByteArrayOutputStream capture;
private ServletOutputStream outputStream;
private PrintWriter writer;
public DeflateResponseWrapper(HttpServletResponse response) {
super(response);
capture = new ByteArrayOutputStream() ;
}
public ServletOutputStream getOutputStream() {
if (writer != null) {
throw new IllegalStateException("Writer already in use");
}
if (outputStream == null) {
outputStream = new ServletOutputStream() {
public void write(int b) throws IOException {
capture.write(b);
}
public void flush() throws IOException {
capture.flush();
}
public void close() throws IOException {
capture.close();
}
public boolean isReady() {
return true;
}
public void setWriteListener(WriteListener writeListener) {
}
};
}
return outputStream;
}
public PrintWriter getWriter() {
if (outputStream != null) {
throw new IllegalStateException("OutputStream already in use");
}
if (writer == null) {
writer = new PrintWriter(capture);
}
return writer;
}
public byte[] getCapturedData() {
return capture.toByteArray();
}
public int getContentLength() {
return capture.size();
}
}
我們需要將數據先寫入到內存輸出流中,這樣我們才能得到當前寫入到響應流中的數據。
2.3 注冊過濾器
在Spring Boot中我們可以通過如下方式注冊過濾器,也可以通過@WedFilter的方式注冊。
@Bean
FilterRegistrationBean<DeflateCompressionFilter> deflateCompressionFilter() {
FilterRegistrationBean<DeflateCompressionFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new DeflateCompressionFilter());
// 對所有的請求都進行過去了處理
registrationBean.addUrlPatterns("/*") ;
registrationBean.setName("DeflateCompressionFilter") ;
registrationBean.setOrder(1) ;
return registrationBean ;
}
以上我們就完成了所有的代碼,接下來我們進行測試。
2.4 測試
接下來,我們通過如下接口進行測試:
@GetMapping("/data")
public List<User> getData() {
List<User> data = new ArrayList<>() ;
for (long i = 0; i < 10000; i++) {
data.add(new User(i, "姓名 - " + i, new Random().nextInt(100))) ;
}
return data;
}
public static record User (Long id, String name, Integer age) {}
首先,我們將請求的Accept-Encoding隨意寫一個值,響應結果
圖片
最后,我們在將Accept-Encoding設置為deflate,響應結果:
圖片
與壓縮前相比:壓縮了近6.7倍。
顯著提升應用程序的性能,減少帶寬使用,并增強用戶體驗。
注意:你完全可以使用GZIP進行壓縮,并且使用GZIP也是當前最為推薦流行的方式,并且兼容性要比deflate好。
如果啟用GZIP?如下配置即可:
server:
compression:
enabled: true
min-response-size: 1024
mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json,application/xml
而本篇文章的目標是讓你了解這壓縮技術的實現原理。而在上面自定義的過濾器中,我們也完全可以使用GZIP對應的輸出流進行壓縮數據,如下代碼:
if (responseWrapper.getContentLength() > MIN_RESPONSE_SIZE) {
// 設置響應的內容編碼類型為gzip
resp.setHeader("Content-Encoding", "gzip");
// 使用gzip進行壓縮數據
try (GZIPOutputStream gos = new GZIPOutputStream(resp.getOutputStream())) {
gos.write(responseWrapper.getCapturedData()) ;
}
} else {
resp.getOutputStream().write(responseWrapper.getCapturedData());
}