我们致力于一个Apache知识的分享网站

Home » 未分类 » gzip 与 deflate

gzip 与 deflate


2009-02-06 14:07:24  |   才被看了3,078次  |   要评论?
分类: 未分类  |   发布:   |   来源:唐福林

Tags: ,

新东家还没有报道,就安排先做一个小任务:把 nginx 的 gzip 换成 deflate ,问为什么,老大说能省 18 个字节。

在baidu上搜了好久,搜到的中文基本上都是讲 apache 的 gzip(apache 1.3) 和 deflate(apache 2.x)的配置的,仅有的几个跟 nginx 相关的,也逃不出配置文件的范畴。至于原理,算法等等,只有去 Google 英文资料了。

换了关键词,直接搜 zlib ,终于找到一些有用的东西,在 http://www.cppblog.com/jinq0123/archive/2007/07/09/HttpCompressConv.html 处看到这样一段话:

deflate与gzip解压的代码几乎相同,应该可以合成一块代码。
区别仅有:

  1. deflate使用inflateInit(),而gzip使用inflateInit2()进行初始化,比 inflateInit()多一个参数: -MAX_WBITS,表示处理raw deflate数据。因为gzip数据中的zlib压缩数据块没有zlib header的两个字节。使用inflateInit2时要求zlib库忽略zlib header。在zlib手册中要求windowBits为8..15,但是实际上其它范围的数据有特殊作用,见zlib.h中的注释,如负数表示raw deflate。
  2. Apache的deflate变种可能也没有zlib header,需要添加假头后处理。即MS的错误deflate (raw deflate).zlib头第1字节一般是0x78, 第2字节与第一字节合起来的双字节应能被31整除,详见rfc1950。例如Firefox的zlib假头为0x7801,python zlib.compress()结果头部为0x789c。

再去检查 zlib.h 中的注释说明,在 zlib-1.2.3/zlib.h Line 500 的地方发现这样一段话:

The windowBits parameter is the base two logarithm of the window size
(the size of the history buffer). It should be in the range 8..15 for this
version of the library. Larger values of this parameter result in better
compression at the expense of memory usage. The default value is 15 if
deflateInit is used instead.

windowBits can also be -8..-15 for raw deflate. In this case, -windowBits
determines the window size. deflate() will then generate raw deflate data
with no zlib header or trailer, and will not compute an adler32 check value.

windowBits can also be greater than 15 for optional gzip encoding. Add
16 to windowBits to write a simple gzip header and trailer around the
compressed data instead of a zlib wrapper. The gzip header will have no
file name, no extra data, no comment, no modification time (set to zero),
no header crc, and the operating system will be set to 255 (unknown).  If a
gzip stream is being written, strm->adler is a crc32 instead of an adler32.

回过头来看 nginx 和 apache 的实现:

 nginx-0.6.34/src/http/modules/ngx_http_gzip_filter_module.c Line 335:

rc = deflateInit2(&ctx->zstream, (int) conf->level, Z_DEFLATED,
-wbits, memlevel, Z_DEFAULT_STRATEGY);

httpd-2.0.63/modules/filters/mod_deflate.c Line 374:

zRC = deflateInit2(&ctx->stream, c->compressionlevel, Z_DEFLATED,
c->windowSize, c->memlevel,
Z_DEFAULT_STRATEGY);

(Line 153: c->windowSize = i * -1; )

也就是说,nginx 和 apache 在程序里处理的都是 raw deflate data ,windowBits 都是负数,那为什么 Content-Encoding 都写的是 gzip 而不是 deflate 呢?

在 apache 的 mod_deflate.c 里,首先发现了这样一个写入 gzip header 的动作:

       /* RFC 1952 Section 2.3 dictates the gzip header:
*
* +—+—+—+—+—+—+—+—+—+—+
* |ID1|ID2|CM |FLG|     MTIME     |XFL|OS |
* +—+—+—+—+—+—+—+—+—+—+
*
* If we wish to populate in MTIME (as hinted in RFC 1952), do:
* putLong(date_array, apr_time_now() / APR_USEC_PER_SEC);
* where date_array is a char[4] and then print date_array in the
* MTIME position.  WARNING: ENDIANNESS ISSUE HERE.
*/
buf = apr_psprintf(r->pool, “%c%c%c%c%c%c%c%c%c%c”, deflate_magic[0],
deflate_magic[1], Z_DEFLATED, 0 /* flags */,
0, 0, 0, 0 /* 4 chars for mtime */,
0 /* xflags */, OS_CODE);

deflate_magic 是这样定义的:

/* magic header */
static char deflate_magic[2] = { ‘37’, ‘213’ };

而 OS_CODE 是在 zutil.h 里定义的,AMIGA 是 1,VAXC 是 2,OS2 是 6,WIN32 是 11,默认 unix 是 3(从这个顺序也可以看出操作系统的发展历史了)

数一下,10个字节,再联想到老大说的 18 个字节,仔细找找,终于在 Line 462 里发现这样一个附加 tail 的动作:

buf = apr_palloc(r->pool, 8) ;
putLong((unsigned char *)&buf[0], ctx->crc);
putLong((unsigned char *)&buf[4], ctx->stream.total_in);

b = apr_bucket_pool_create(buf, 8, r->pool, f->c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(ctx->bb, b);

不多不少,8个字节。10个字节的头加上 8 个字节的尾巴,就是老大说的多出来的 18 个字节。apache 调用 zlib 的接口产生了 raw defalte 的数据,然后手工的添加了 gzip 头和尾。

同样的,在 nginx 的 ngx_http_gzip_filter_module.c 首先在 Line 179看到 Igor Sysoev 同学很不负责任的定义了这样一个 gzip header:

static u_char  gzheader[10] = { 0x1f, 0x8b, Z_DEFLATED, 0, 0, 0, 0, 0, 0, 3 };

仔细看最后一位!居然直接写了一个 3 !这会不会导致 windows 上编译的 nginx 在输出 gzip 压缩过的页面的时候,客户端解压不正常?回头有空再去看看 zlib 里关于解压的算法代码中,对于这个 OS_CODE 是怎么处理的吧。

继续寻找,在 Line 351 的地方,作者还写了一段注释(虽然我越看越不明白他在试图表达什么意思):

        b->memory = 1;
b->pos = gzheader;
b->last = b->pos + 10;

out.buf = b;
out.next = NULL;

/*
* We pass the gzheader to the next filter now to avoid its linking
* to the ctx->busy chain.  zlib does not usually output the compressed
* data in the initial iterations, so the gzheader that was linked
* to the ctx->busy chain would be flushed by ngx_http_write_filter().
*/

大致是说把 gzheader 传给下一个 filter 去处理,这个 filter 只出来 raw deflate 数据,以及附加的 tail 吧。在 Line 605 的地方:

#if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED)

trailer->crc32 = ctx->crc32;
trailer->zlen = ctx->zin;

#else
trailer->crc32[0] = (u_char) (ctx->crc32 & 0xff);
trailer->crc32[1] = (u_char) ((ctx->crc32 >> 8) & 0xff);
trailer->crc32[2] = (u_char) ((ctx->crc32 >> 16) & 0xff);
trailer->crc32[3] = (u_char) ((ctx->crc32 >> 24) & 0xff);

trailer->zlen[0] = (u_char) (ctx->zin & 0xff);
trailer->zlen[1] = (u_char) ((ctx->zin >> 8) & 0xff);
trailer->zlen[2] = (u_char) ((ctx->zin >> 16) & 0xff);
trailer->zlen[3] = (u_char) ((ctx->zin >> 24) & 0xff);
#endif

幸亏有 IBM MOTOROLA  们造了 Big Endian 机器,这样一来,这段代码的意思再明白不过了。

连上网搜资料加读代码,一共花了大约3个小时,到现在,大约清楚了这么几个问题:

  1. deflate 是最基础的算法,在 zlib 里面有实现
  2. gzip 在 deflate 的 raw data 前增加了 10 个字节的 gzheader,尾部添加了 8 个字节的校验字节(可选 crc32 和 adler32) 和长度标识字节,gzip 的 magic number 是 0x1f, 0x8b
  3. zlib 自己也有 header 和尾部校验的数据,如果使用 deflateInit 而不是 deflateInit2,或者 windowBits 设置为正数8~15的话
  4. zlib windowBits 设置为 16 的时候,zlib 自己会产生一个 gzip 的头和尾,这种情况下 OS_CODE 被设置为 255(unknown),尾部校验使用 crc32 。问题是,既然 zlib 本身就提供了这种功能,为什么 apache 和 nginx 不用,反而都选择手工添加呢?
  5. 为 nginx 添加 deflate 支持,只需要把输出中的头,尾去掉,并把 Content-Encoding 改为 deflate 即可。18 个字节,就这样省下来了。

参考资料:

PS,在腾讯最后一天上班,并祝所有爱我的和我爱的人小年快乐!大年也快乐!

apache最大连接数性能测试 Apache AllowOverride 指令

Leave a Reply