告别大请求卡顿!原生 CompressionStream 实现 axios 请求体自动 Gzip 压缩(前后端全适配)

前端请求体 Gzip 压缩最佳实践:原生 CompressionStream + axios 全自动方案

前言

在后台管理系统、批量导入导出、大数据表单提交、大批量表格保存等场景中,我们经常遇到请求体过大导致接口缓慢、上传耗时、甚至超时失败的问题。

大部分开发者只熟悉后端对响应体开启 Gzip,却忽略了:请求体同样可以压缩,而且在大数据提交场景下收益往往更高。

本文将带你使用浏览器原生 CompressionStream API,结合 axios 拦截器,实现一套零依赖、高性能、全自动的请求体 Gzip 压缩方案,大幅降低传输体积,提升接口速度与系统稳定性。


一、实现思路

  1. 只对 POST 请求 + 普通 JSON 对象进行压缩
  2. 计算请求体大小,超过阈值再压缩,避免小请求浪费性能
  3. 使用浏览器原生 CompressionStream 做 Gzip 压缩
  4. 自动添加请求头 Content-Encoding: gzip 标识格式
  5. 接入 axios 全局拦截器,业务代码无感使用

二、核心工具函数封装

1. 判断是否为普通对象

排除 FormData、Blob、File、ArrayBuffer 等不需要/无法压缩的类型。

javascript 复制代码
/**
 * 判断是否为普通对象(可 JSON 序列化)
 */
const isPlainObject = (data) => {
  return typeof data === 'object' &&
    data !== null &&
    !ArrayBuffer.isView(data) &&
    !(data instanceof FormData) &&
    !(data instanceof Blob) &&
    !(data instanceof File) &&
    !(data instanceof ArrayBuffer);
};

2. 判断是否为 POST 请求

javascript 复制代码
/**
 * 判断是否 POST 请求
 */
const isPostMethod = (axiosConfig) => {
  return axiosConfig.method?.toLowerCase() === 'post';
};

3. 判断请求体是否可压缩

javascript 复制代码
/**
 * 判断请求体是否为普通对象
 */
const isPlainRequestBody = (axiosConfig) => {
  const bodyData = axiosConfig.data;
  return isPlainObject(bodyData);
};

4. 计算请求体大小(支持 byte/kb/mb)

javascript 复制代码
/**
 * 计算请求体大小
 * @param data 请求数据
 * @param measuringType 单位 byte/kb/mb
 */
const getRequestBodySize = (data, measuringType = 'mb') => {
  if (!data) return 0;
  let byteLength = 0;

  if (typeof data === 'string') {
    byteLength = new Blob([data]).size;
  } else if (isPlainObject(data)) {
    try {
      const jsonString = JSON.stringify(data);
      byteLength = new Blob([jsonString]).size;
    } catch (e) {
      console.warn('对象序列化失败', e);
    }
  } else if (data instanceof Blob || data instanceof File) {
    byteLength = data.size;
  }

  const unitMap = {
    byte: 1,
    kb: 1024,
    mb: 1024 * 1024
  };

  return byteLength / unitMap[measuringType];
};

三、原生 Gzip 压缩函数(核心)

使用浏览器原生 API,不依赖 pako,零打包体积

javascript 复制代码
/**
 * 原生 Gzip 压缩
 * @param data 待压缩对象
 * @returns 压缩后的 ArrayBuffer
 */
const compress = async (data) => {
  // 1. JSON 序列化
  const jsonString = JSON.stringify(data);

  // 2. 转为二进制 Uint8Array
  const encoder = new TextEncoder();
  const inputData = encoder.encode(jsonString);

  // 3. 创建可读流
  const readableStream = new ReadableStream({
    start(controller) {
      controller.enqueue(inputData);
      controller.close();
    }
  });

  // 4. 创建 gzip 压缩流
  const compressionStream = new CompressionStream('gzip');
  const compressedStream = readableStream.pipeThrough(compressionStream);

  // 5. 转换为 ArrayBuffer
  return await new Response(compressedStream).arrayBuffer();
};

四、axios 自动压缩拦截器

请求前自动判断大小,大于 1.5MB 自动压缩

javascript 复制代码
/**
 * axios 请求体压缩拦截器
 */
async function compressionInterceptor(config) {
  // 浏览器兼容判断
  if (!window.CompressionStream) {
    return config;
  }

  // 只处理普通 POST 对象
  if (isPlainRequestBody(config)) {
    const sizeMB = getRequestBodySize(config.data);
    // 大于 1.5MB 执行压缩
    if (sizeMB > 1.5) {
      config.data = await compress(config.data);
      // 添加压缩请求头
      config.headers['Content-Encoding'] = 'gzip';
      config.headers['Content-Type'] = 'application/json';
    }
  }
  return config;
}

五、挂载到 axios 全局使用

javascript 复制代码
// 注册拦截器
axios.interceptors.request.use(compressionInterceptor);

六、CompressionStream 与 pako 对比

对比项 CompressionStream(原生) pako(第三方库)
体积 0 依赖,0 打包体积 约 45KB,增加打包体积
性能 浏览器底层实现,流式处理更快 纯 JS 实现,大数据略慢
API 风格 异步 Stream,现代化架构 同步调用,简单直接
兼容性 现代浏览器支持 支持 IE 等老旧浏览器
适用场景 中后台、移动端、现代项目 需要兼容极低版本浏览器

结论:现代前端优先使用原生 CompressionStream,pako 仅做降级兼容。


七、后端如何处理 Gzip 压缩请求体

前端发送 Content-Encoding: gzip 后,后端必须解压才能正常解析 JSON,否则会收到二进制乱码。

1. Nginx 配置(必须开启)

nginx 复制代码
server {
    listen 80;
    server_name your.domain.com;

    # 开启请求体解压
    gunzip on;
    gunzip_buffers 16 8k;

    # 请求体大小限制
    client_max_body_size 20m;
    client_body_buffer_size 20m;
}

2. SpringBoot 处理(Java)

过滤器:自动解压 Gzip

java 复制代码
@Configuration
public class GzipRequestFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        String encoding = req.getHeader("Content-Encoding");

        if (encoding != null && encoding.contains("gzip")) {
            GzipRequestWrapper wrapper = new GzipRequestWrapper(req);
            chain.doFilter(wrapper, response);
        } else {
            chain.doFilter(request, response);
        }
    }
}

解压包装类 GzipRequestWrapper

java 复制代码
import org.apache.commons.io.IOUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.GZIPInputStream;

public class GzipRequestWrapper extends HttpServletRequestWrapper {
    private final byte[] body;

    public GzipRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        try (GZIPInputStream gzipIn = new GZIPInputStream(request.getInputStream());
             ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            IOUtils.copy(gzipIn, out);
            body = out.toByteArray();
        }
    }

    @Override
    public ServletInputStream getInputStream() {
        ByteArrayInputStream bais = new ByteArrayInputStream(body);
        return new ServletInputStream() {
            @Override
            public int read() { return bais.read(); }
            @Override public boolean isFinished() { return bais.available() == 0; }
            @Override public boolean isReady() { return true; }
            @Override public void setReadListener(ReadListener listener) {}
        };
    }
}

3. Node.js (Express/Koa)

javascript 复制代码
const express = require('express');
const compression = require('compression');
const app = express();

// 自动解压 gzip 请求体
app.use(compression());
app.use(express.json({ limit: '20mb' }));

八、完整注意事项(生产必看)

  1. 后端必须配置解压,否则收到二进制乱码,前端压缩无意义。
  2. Nginx 必须开启 gunzip on,否则不会解压请求体。
  3. 不要压缩文件类数据:FormData、Blob、File、图片、音频等本身已压缩,再压缩无收益。
  4. 阈值不要过低,建议 ≥ 256KB 或 1MB,过小数据压缩反而耗性能。
  5. 注意配置请求体大小限制client_max_body_size、服务端请求体大小限制,避免 413 错误。
  6. 浏览器兼容处理 :低版本浏览器不支持 CompressionStream,需加判断跳过。
  7. axios 拦截器为 async 函数,必须 return config,否则请求会中断。
  8. 必须设置 Content-Type: application/json,否则后端可能无法自动解析。
  9. 避免链路重复压缩,只在前端或网关其中一处做 Gzip
  10. 本地调试可临时关闭压缩,方便在 Network 查看明文请求体。
  11. 超大数据(10MB+)建议配合分片、分批、异步导入,不依赖单一压缩。
  12. 生产环境建议使用 HTTPS,避免压缩内容被中间人劫持或篡改。

九、效果总结

  • JSON 数据压缩率通常可达 60%~80%
  • 大数据提交、批量保存接口速度明显提升
  • 接口超时、上传失败概率大幅降低
  • 零第三方依赖,不增加打包体积
  • 接入简单、全局生效、业务无侵入

总结

本文实现的前端请求体全自动 Gzip 压缩方案 ,基于浏览器原生 CompressionStream,配合 axios 拦截器实现按体积智能压缩,是中后台系统、大数据交互场景下非常实用的高阶优化手段。

配合后端 Nginx + 服务端解压配置,可真正实现前后端一体化提速,大幅提升用户体验与系统稳定性。

相关推荐
|晴 天|2 小时前
Element Plus 组件库实战技巧与踩坑记录
前端·javascript·vue.js·typescript
im_AMBER2 小时前
从面试题看JS变量提升
开发语言·前端·javascript·前端框架
Mintopia2 小时前
不用学微服务,也能设计不崩的系统:最小可行思路
前端
llf_cloud2 小时前
Vue2 项目中的全局自动弹窗队列设计
前端·javascript·vue.js
百锦再3 小时前
使用JavaScript获取和解析页面内容的完整指南
开发语言·前端·javascript·python·flask·fastapi
sakana3 小时前
如何写一个自己的skill
前端·人工智能
wsdswzj3 小时前
web前端基础知识
前端
一条小鲨鱼3 小时前
所遇到的响应式问题
前端·vue.js
M ? A3 小时前
你的 Vue 路由,VuReact 会编译成什么样的 React 路由?
前端·javascript·vue.js·经验分享·react.js·面试·vureact