前些天,移动端的同事跑来问:某些API需要传输大数据,Nginx服务器能否支持Gzip请求?一方面可以节省移动端流量;另一方面还可以加快传输速度,提升用户体验。对于Apache来说,利用,可以很轻松的实现这个功能,那么Nginx如何做呢?
既然移动端发送的是Gzip请求,自然需要想想如何在服务端解压缩。搜索一下现成的Nginx的,发现和Gzip相关的模块有如下几个:
- : Gzip responses.
- : Serves precompressed versions of static files.
- : On-the-fly decompressing of gzipped responses.
可惜它们都是和Response相关的Gzip,而我们需要的是和Request相关的Gzip。
在我们的实际情况里,很多接口都是用PHP做的,于是自然想到用PHP的方法来解压缩Gzip请求,不过最终出于效率的担心放弃了。
每当我遇到难题的时候就会想起,它总是能屡建奇功,这次自然也不例外,仔细搜索了一下社区,发现有人遇到了同样的,春哥在讨论中给出了建议,不过并没有涉及具体的实现逻辑,于是我查了资料总结了一下。
方案
第一个选择是使用:
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)) endend
第二个选择是通过LuaJIT的库来包装模块,里有一些现成的可供参考的的例子,不过例子里介绍的是Deflate,而不是Gzip,自己用FFI封装Gzip的话又有点小复杂,好在别人已经做了相关的工作,那就是:
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 endendlocal function writer() local t = {} return function(data, sz) if not data then return table.concat(t) end t[#t + 1] = ffi.string(data, sz) endendlocal 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()) endend
如上例子代码源自,乍看上去,代码里的reader和writer可能会令人费解,其实你可以把它们理解成输入输出接口,可以修改成文件,数据库等等形式。
别高兴太早,当你运行时,很可能会遇到如下错误:
libzlib.so: cannot open shared object file.
实际上这是因为如下代码的缘故:
local C = ffi.load 'zlib'
运行时,会自动补全文件名,如果是Windows,则加载zlib.dll文件,如果是Linux,则加载libzlib.so,但实际上在Linux下,ZLIB扩展的名字是libz.so,而非libzlib.so。
知道的问题的原委,我们自然就知道如何修改代码了:
local Cif ffi.os == "Windows" then C = ffi.load "zlib"else C = ffi.load "z"end
有时候我们不推荐直接修改第三方库的代码,因为这样的话,每次第三库更新代码,我们都要做对应的修改,一旦忘记就会出错,这时候可以考虑做一个软连接别名。
测试
开篇说过,接口都是用PHP做的,不过请求里的Gzip数据是用LUA处理的,如何让PHP使用LUA处理后的数据呢?不同的语言似乎是个难题,好在Nginx有一说,PHP作为FastCGI模块工作在content阶段,LUA可以工作在阶段,这样它们就和谐了:
location ~ \.php$ { access_by_lua_file /path/to/lua/file; include fastcgi.conf; fastcgi_pass 127.0.0.1:9000;}
那么lua-zlib和lua-files两种方案效率如何?下面是我用PHP写的测试脚本:
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更快一些,这是因为目前的FFI还不能完整编译LUA代码,新版本会好些。
当使用 FFI 的时候,只有当你的 Lua 代码确实被 JIT 编译才有可能比使用 CFunction 的 Lua 绑定更快,否则在 LuaJIT 的解释器上运行时肯定更慢。你可以使用 LuaJIT 自带的 jit.v 或者 jit.dump 模块检查你的 Lua 代码有哪些能被 JIT 编译,哪些不能。
当然,由于 LuaJIT 2.0 的 JIT 编译器缺少很多功能,所以使用 LuaJIT 2.0 的时候就不指望了。可以尝试使用最新的 LuaJIT 2.1,并配合使用 ngx_lua 基于 FFI 实现的新接口 lua-resty-core:
另外,我们总是可以使用 on-CPU 火焰图分析 CPU 时间在各条代码路径上是如何分配的。
谨记春哥教诲!
这个讲得是移动端发送gzip请求,服务器端解析。如果是反反过来,服务器段发送的数据是gzip压缩的,移动端需要进行相应解压缩数据吗?现在我们的APP接口数据是json格式的
我比较好奇,直接用php去gzdecode真会影响效率么?理论上request过的的参数一般都比较小。
王哥 我测试的显示用了lua-zlib CPU耗时更长,有时候还会出现长时间的延迟。
长时间的延迟?然后CPU一直在等待?
你好, 我想请教一下 ,怎么安装lua_zlib , 为什么我在make linux的时候会出现以下问题呢 ?
make[1]: Entering directory `/opt/script/lua-zlib-0.1′ gcc -O -shared -fPIC -L/usr/lib -L/usr/lib lua_zlib.o -lz -llua -lm -o zlib.so /usr/bin/ld: skipping incompatible /usr/lib/libz.so when searching for -lz /usr/bin/ld: skipping incompatible /usr/lib/libz.a when searching for -lz /usr/bin/ld: skipping incompatible /usr/lib/libz.so when searching for -lz /usr/bin/ld: skipping incompatible /usr/lib/libz.a when searching for -lz /usr/bin/ld: /usr/local/lib/liblua.a(lapi.o): relocation R_X86_64_32 against `luaO_nilobject_’ can not be used when making a shared object; recompile with -fPIC /usr/local/lib/liblua.a: could not read symbols: Bad value collect2: ld returned 1 exit status make[1]: *** [zlib.so] Error 1 make[1]: Leaving directory `/opt/script/lua-zlib-0.1′ make: *** [linux] Error 2同问,现在您解决了吗?
这个是因为你nginx版本太高的缘故,你nginx版本是多少?我记得只支持到1.7以下
Hello,麻烦请问 Nginx 的 ngx_http_gunzip_module 模块是不是就能处理相同的事情了?
写的很好,我是一个wordpress爱好者,想问一下博主,如果我使用php(一个缓存插件)生成了静态的压缩文件(xxx.tar.gz),那么nginx的gzip功能是不是就可以关闭了,而且在访问的时候会更加节省CPU(省去了每次gzip),期待回复。。
Hello, replace_filter無法處理gzip過的php output, 是否有辦法在nginx中即時gunzip php的output呢?
想请教下作者一个问题,下面这段代码中,
local zlib = require(“zlib”)— web服务器支持的返回内容压缩编码类型
— gzip = gzip头(10字节) + deflate编码的实际内容 + gzip尾(8字节) 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() local r = stream(body); ngx.req.set_body_data(r); local jsonObject = json_decode(r)else
local answer = {} answer.result = “100000” answer.desc = “请求参数不存在” ngx.print(json.encode(answer)) return end else ngx.print(“body未经过gzip压缩”) ngx.exit(ngx.HTTP_OK) return end ================ 那个jsonObject返回的是个nil值,还望作者指点一番。卡在这地方要久了。谢谢