使用 gzip 压缩请求正文

在一些论坛, 博客等项目中. 用户发送的帖子, 文章内容可能会存在太长的情况. 这时如果用户的网速不佳, 或者网络不稳定. 那么将会面临 ** 响应过慢、发送失败 ** 的情况. 如果网站还有自动保存的功能的话, 这种情况会明显增多. 这时如果将传输的内容在本地进行压缩上传, 然后在服务器进行解压. 对长文本的处理能够得到完好解决, 同时极大减少了移动端用户的网络开销.

本文创作思路来源于 Jerry Qu 的博客 如何压缩 HTTP 请求正文

实现思路

在前台对请求正文使用 pako_deflate.js 进行本地 gzip 格式压缩
在后台使用 Java 对请求正文进行解压

操作环境

  • jdk 1.8.0_77
  • idea 2016.2.1
  • maven 3.3.9

项目依赖

  • commons-io-2.5 (简化 IO 操作)
  • json-lib-2.4 (处理请求正文中的参数)
  • spring-webmvc-4.3.4.RELEASE
  • pako_deflate-1.0.3.js (JS 文本压缩工具类)

JS 压缩请求正文

因为只在前台进行压缩, 所以只需引用 pako 的压缩专用文件 pako_deflate.min.js
又因为我在项目中主要使用 jQuery 发送 Ajax 请求, 所以引入 jQuery

<script src="jquery-2.2.4.min.js"></script>
<script src="pako_deflate.min.js"></script>

将发送的参数转换为 JSON 字符串

var params = encodeURIComponent(JSON.stringify({
    title: "标题",
    content: "内容"
}));

gzip 虽然能极大的压缩请求正文. 但是如果内容过小, 压缩后内容反而会增大, 经测试, 对于 params.length 大于 1000 的文本压缩效果能够达到 60% 以上, 所以在压缩前, 需要对内容进行判断

var params = encodeURIComponent(JSON.stringify({
    title: title,
    content: content
}));
var compressBeginLen = params.length;
if (compressBeginLen > 1000) {
    // 对 JSON 字符串进行压缩
    // pako.gzip(params) 默认返回一个 Uint8Array 对象, 如果此时使用 Ajax 进行请求, 参数会以数组的形式进行发送
    // 为了解决该问题, 添加 {to: "string"} 参数, 返回一个二进制的字符串
    params = pako.gzip(params, {to: "string"});
}
$.ajax({
    url: "/gzip",
    data: params,
    dataType: "text",
    type: "post",
    headers: {
        // 如果 compressBeginLen 大于 1000, 标记此次请求的参数使用了 gzip 压缩
        "Content-Encoding": params.length>1000?"gzip":""
    },
    success: function (data) {
        //dosomething
    }
})

Java 解压请求正文

首先获取 Content-Encoding 请求头, 根据该请求头中的内容进行逻辑处理

@ResponseBody
@RequestMapping(value = "/gzip")
public String gzip(HttpServletRequest request) {
    String params = "";
    try {
        // 获取 Content-Encoding 请求头
        String contentEncoding = request.getHeader("Content-Encoding");
        if (contentEncoding != null && contentEncoding.equals("gzip")) {
            // 获取输入流
            BufferedReader reader = request.getReader();
            // 将输入流中的请求实体转换为 byte 数组, 进行 gzip 解压
            byte[] bytes = IOUtils.toByteArray(reader, "iso-8859-1");
            // 对 bytes 数组进行解压
            params = GZIPUtil.uncompress(bytes);
        } else {
            BufferedReader reader = request.getReader();
            params = IOUtils.toString(reader);
        }
        if (params != null && params.trim().length() > 0) {
            // 因为前台对参数进行了 url 编码, 在此进行解码
            params = URLDecoder.decode(params, "utf-8");
            // 将解码后的参数转换为 json 对象
            JSONObject json = JSONObject.fromObject(params);
            // 从 json 对象中获取参数进行后续操作
            System.out.println("title:\t" + json.getString("title"));
            System.out.println("content:\t" + json.getString("content"));
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    return params;
}

Java gzip 解压方法 GZIPUtil.uncompress 参考 Java 使用 GZIP 进行压缩和解压缩(GZIPOutputStream,GZIPInputStream) 一文而成

/**
 * 解压 gzip 格式 byte 数组
 * @param bytes gzip 格式 byte 数组
 * @param charset 字符集
 */
public static String uncompress(byte[] bytes, String charset) {
    if (bytes == null || bytes.length == 0) {
        return null;
    }
    ByteArrayOutputStream byteArrayOutputStream = null;
    ByteArrayInputStream byteArrayInputStream = null;
    GZIPInputStream gzipInputStream = null;
    try {
        byteArrayOutputStream = new ByteArrayOutputStream();
        byteArrayInputStream = new ByteArrayInputStream(bytes);
        gzipInputStream = new GZIPInputStream(byteArrayInputStream);
        // 使用 org.apache.commons.io.IOUtils 简化流的操作
        IOUtils.copy(gzipInputStream, byteArrayOutputStream);
        return byteArrayOutputStream.toString(charset);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 释放流资源
        IOUtils.closeQuietly(gzipInputStream);
        IOUtils.closeQuietly(byteArrayInputStream);
        IOUtils.closeQuietly(byteArrayOutputStream);
    }
    return null;
}

另外 Jerry Qu 实现了一个服务器使用 Node.js 解压的 DEMO 并提供 deflate,zlib,gzip 三种压缩, 解压方式
以上完整代码可在 gzip 项目中查看

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容