上篇文章中,我介绍了 HTTP 协议中的 Accept-Encoding 和 Content-Encoding 机制。它可以很好地用于文本类响应正文的压缩,减少网络数据的传输,已被广泛使用。但 HTTP 请求的发起方浏览器,无法事先知晓要访问的服务端是否支持解压,所以现阶段的浏览器没有压缩请求正文。
有一些通讯协议基于 HTTP 做了扩展,他们的客户端和服务端是专用的,完全可以针对请求正文进行压缩,例如 WebDAV 客户端就是这么做的。
实际的 Web 项目中,存在请求正文非常大的场景,例如发表长篇博客,上报用于调试的网络数据等等。这些数据如果能在本地压缩后再提交,就可以节省大量流量、减少传输时间。本文介绍如何对 HTTP 请求正文进行压缩,包含如何在服务端解压、如何在客户端压缩两个部分。
开始之前,先来介绍本文涉及的三种数据压缩格式:
- DEFLATE,是一种使用 Lempel-Ziv 压缩算法(LZ77)和哈夫曼编码的压缩格式。详见 RFC 1951;
- ZLIB,是一种使用 DEFLATE 的压缩格式,对应 HTTP 中的 Content-Encoding: deflate。详见 RFC 1950;
- GZIP,也是一种使用 DEFLATE 的压缩格式,对应 HTTP 中的 Content-Encoding: gzip。详见 RFC 1952;
Content-Encoding 中的 deflate,实际上是 ZLIB。为了清晰,本文将 DEFLATE 称之为 RAW DEFLATE,ZLIB 和 GZIP 都是 RAW DEFLATE 的不同 Wrapper。
解压请求正文
服务端收到请求正文后,需要分析请求头中的 Content-Encoding 字段,才能知道正文采用了哪种压缩格式。本文规定用 gzip、deflate 和 deflate-raw 分别表示请求正文采用 GZIP、ZLIB 和 RAW DEFLATE 压缩格式。
Nginx
Nginx 没有类似于 Apache 的 SetInputFilter
指令,不能直接给请求添加处理逻辑,还好有 OpenResty。OpenResty 通过集成 Lua 及大量 Lua 库,极大地提升了 Nginx 的功能丰富度和可扩展性。而 LuaJIT 中的 FFI 库,允许纯 Lua 代码调用外部 C 函数,使用 C 数据结构。
把这一切结合起来,就能方便地实现这个需求:首先安装 OpenResty;下载并解压 Zlib 库的 FFI 版;然后在 Nginx 的配置中,通过 lua_package_path
指令将这个库引入;再新建一个 lua 文件,如 request-compress.lua
,调用 Zlib 库实现解压功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
local ffi = require "ffi" local zlib = require "zlib" local function reader(s) local done return function() if done then return end done = true return s end end local function writer() local t = {} return function(data, sz) if not data then return table.concat(t) end t[#t + 1] = ffi.string(data, sz) end end local encoding = ngx.req.get_headers()['Content-Encoding'] if encoding == 'gzip' or encoding == 'deflate' or encoding == 'deflate-raw' then ngx.req.clear_header('Content-Encoding'); ngx.req.read_body() local body = ngx.req.get_body_data() if body then local write = writer() local map = { gzip = 'gzip', deflate = 'zlib', ['deflate-raw'] = 'deflate' } local format = map[encoding] zlib.inflate(reader(body), write, nil, format) ngx.req.set_body_data(write()) end end |
我们的 Nginx 一般都是挡在最前面,背后还有 PHP、Node.js 等实际服务。这段代码从 Content-Encoding 请求头中获取请求压缩格式,并在解压后移除了这个头部。这样对于 Nginx 背后的服务来说,完全感知不到跟平常有什么不一样。
现在还差最后一步,找到 Nginx 中配置 xxx_pass(proxy_pass、uwsgi_pass、fastcgi_pass 等)的地方,加入 lua 处理逻辑:
1 2 3 4 5 6 |
location ~ .php$ { access_by_lua_file /your/path/to/request-compress.lua; fastcgi_pass 127.0.0.1:9000; #... ... } |
这个配置目的是让这个 lua 逻辑工作在 Nginx 的 Access 阶段。
到此为止,基于 OpenResty 的解压方案已经写好。它能否按预期正常工作呢?我决定先放一放,后面再验证。
Node.js
Node.js 内置了对 Zlib 库的封装。使用 Node.js 也可以轻松应对压缩内容。直接上代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
var http = require('http'); var zlib = require('zlib'); http.createServer 上篇文章中,我介绍了 HTTP 协议中的 Accept-Encoding 和 Content-Encoding 机制。它可以很好地用于文本类响应正文的压缩,减少网络数据的传输,已被广泛使用。但 HTTP 请求的发起方浏览器,无法事先知晓要访问的服务端是否支持解压,所以现阶段的浏览器没有压缩请求正文。 有一些通讯协议基于 HTTP 做了扩展,他们的客户端和服务端是专用的,完全可以针对请求正文进行压缩,例如 WebDAV 客户端就是这么做的。 实际的 Web 项目中,存在请求正文非常大的场景,例如发表长篇博客,上报用于调试的网络数据等等。这些数据如果能在本地压缩后再提交,就可以节省大量流量、减少传输时间。本文介绍如何对 HTTP 请求正文进行压缩,包含如何在服务端解压、如何在客户端压缩两个部分。 开始之前,先来介绍本文涉及的三种数据压缩格式:
Content-Encoding 中的 deflate,实际上是 ZLIB。为了清晰,本文将 DEFLATE 称之为 RAW DEFLATE,ZLIB 和 GZIP 都是 RAW DEFLATE 的不同 Wrapper。 解压请求正文服务端收到请求正文后,需要分析请求头中的 Content-Encoding 字段,才能知道正文采用了哪种压缩格式。本文规定用 gzip、deflate 和 deflate-raw 分别表示请求正文采用 GZIP、ZLIB 和 RAW DEFLATE 压缩格式。 NginxNginx 没有类似于 Apache 的 把这一切结合起来,就能方便地实现这个需求:首先安装 OpenResty;下载并解压 Zlib 库的 FFI 版;然后在 Nginx 的配置中,通过
我们的 Nginx 一般都是挡在最前面,背后还有 PHP、Node.js 等实际服务。这段代码从 Content-Encoding 请求头中获取请求压缩格式,并在解压后移除了这个头部。这样对于 Nginx 背后的服务来说,完全感知不到跟平常有什么不一样。 现在还差最后一步,找到 Nginx 中配置 xxx_pass(proxy_pass、uwsgi_pass、fastcgi_pass 等)的地方,加入 lua 处理逻辑:
这个配置目的是让这个 lua 逻辑工作在 Nginx 的 Access 阶段。 到此为止,基于 OpenResty 的解压方案已经写好。它能否按预期正常工作呢?我决定先放一放,后面再验证。 Node.jsNode.js 内置了对 Zlib 库的封装。使用 Node.js 也可以轻松应对压缩内容。直接上代码:
|