openKylin论坛

 找回密码

Nginx与Gzip请求 [复制链接]



前些天,移动端的同事跑来问:某些API需要传输大数据,Nginx服务器能否支持Gzip请求?一方面可以节省移动端流量;另一方面还可以加快传输速度,提升用户体验。我自信满满的认为这就是分分钟的事儿,于是便一口答应了下来。

既然移动端发送的是Gzip请求,自然需要想想如何在服务端解压缩。客观情况是很多接口都是用PHP做的,于是首先想用PHP的gzdecode方法来解压缩Gzip请求,不过出于效率的担心放弃了,接着搜索一下Nginx的模块,发现和Gzip相关的模块有如下几个:

    Gzip: Gzip responses.
    Gzip Precompression: Serves precompressed versions of static files.
    Gunzip: On-the-fly decompressing of gzipped responses.

可惜它们都是和Response相关的Gzip,而我们需要的是和Request相关的Gzip。

每当我遇到难题的时候就会想起lua-nginx-module,它总是能屡建奇功,这次自然也不例外,仔细搜索了一下OpenResty社区,发现有人遇到了同样的问题,春哥在讨论中给出了建议,不过并没有涉及具体的实现逻辑,于是我查了资料总结了一下。
方案

第一个选择是使用lua-zlib:

local zlib = require "zlib"

local encoding = ngx.req.get_headers()["Content-Encoding"]

if encoding == "gzip" then
    local body = ngx.req.get_body_data()

    if body then
        local stream = zlib.inflate()
        ngx.req.set_body_data(stream(body))
    end
end

第二个选择是通过LuaJIT的FFI库来包装ZLIB模块,官方教程里有一些现成的可供参考的的例子,不过例子里介绍的是Deflate,而不是Gzip,自己用FFI封装Gzip的话又有点小复杂,好在别人已经做了相关的工作,那就是lua-files:

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" then
    local body = ngx.req.get_body_data()

    if body then
        local write = writer()
        zlib.inflate(reader(body), write, nil, "gzip")
        ngx.req.set_body_data(write())
    end
end

如上例子代码源自zlib_test.lua,乍看上去,代码里的reader和writer可能会令人费解,其实你可以把它们理解成输入输出接口,可以修改成文件,数据库等等形式。

别高兴太早,当你运行时,很可能会遇到如下错误:

    libzlib.so: cannot open shared object file.

实际上这是因为如下zlib.lua代码的缘故:

local C = ffi.load 'zlib'

运行时,ffi.load会自动补全文件名,如果是Windows,则加载zlib.dll文件,如果是Linux,则加载libzlib.so,但实际上在Linux下,ZLIB扩展的名字是libz.so,而非libzlib.so:

shell> locate libz.so
/lib/libz.so
/lib/libz.so.1
/lib/libz.so.1.2.3
/lib64/libz.so
/lib64/libz.so.1
/lib64/libz.so.1.2.3
/usr/lib/libz.so
/usr/lib/libz.so.1
/usr/lib/libz.so.1.2.3
/usr/lib64/libz.so
/usr/lib64/libz.so.1
/usr/lib64/libz.so.1.2.3

知道的问题的原委,我们只要按如下方式修改代码即可:

local C

if ffi.os == "Windows" then
    C = ffi.load "zlib"
else
    C = ffi.load "z"
end

测试

开篇说过,接口都是用PHP做的,不过请求里的Gzip数据是用LUA处理的,如何让PHP使用LUA处理后的数据呢?不同的语言似乎是个难题,好在Nginx有Phases一说,PHP作为FastCGI模块工作在content阶段,LUA工作在access阶段,这样它们就协同工作了:

location ~ \.php$ {
    access_by_lua_file /path/to/lua/file;

    include fastcgi.conf;
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_index index.php;
}

那么lua-zlib和lua-files两种方案效率如何?下面是我用PHP写的测试脚本:

<?php

$url = 'http://url';

$header = implode("\r\n", array(
    'Content-Type: application/x-www-form-urlencoded',
    'Content-Encoding: gzip',
    'Connection: close',
));

$content = gzencode(http_build_query(
    'foo' => str_repeat('x', 100),
    'bar' => str_repeat('y', 100),
));

$options = array(
    'http' => array(
        'protocol_version' => '1.1',
        'method' => 'POST',
        'header' => $header,
        'content' => $content,
    ),
);

$context = stream_context_create($options);

for ($i = 0; $i < 1000; $i++) {
    file_get_contents($url, false, $context);
}

?>

实际测试的时候,利用Linux自带的time就可以获取运行时间:

shell> time php /path/to/php/file

按春哥说的,FFI应该更高效,不过从我的测试结果看,lua-zlib比lua-files更快一些。

原文链接: http://huoding.com/2013/09/02/283
楼主
发表于 2013-9-3 08:22:09
回复

使用道具 举报

openKylin

GMT+8, 2024-5-14 12:35 , Processed in 0.021183 second(s), 17 queries , Gzip On.

Copyright ©2022 openKylin. All Rights Reserved .

ICP No. 15002470-12 Tianjin

快速回复 返回顶部 返回列表