Nginx与Gzip请求
前些天,移动端的同事跑来问:某些API需要传输大数据,Nginx服务器能否支持Gzip请求?一方面可以节省移动端流量;另一方面还可以加快传输速度,提升用户体验。对于Apache来说,利用SetInputFilter,可以很轻松的实现这个功能,那么Nginx如何做呢?
既然移动端发送的是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。
在我们的实际情况里,很多接口都是用PHP做的,于是自然想到用PHP的gzdecode方法来解压缩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。
知道的问题的原委,我们自然就知道如何修改代码了:
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更快一些。
建议继续学习:
- 配置Nginx+uwsgi更方便地部署python应用 (阅读:105407)
- 搜狐闪电邮箱的 Nginx/Postfix 使用模式 (阅读:32532)
- 解析nginx负载均衡 (阅读:14527)
- Nginx模块开发入门 (阅读:9888)
- 检查nginx配置,重载配置以及重启的方法 (阅读:9109)
- Cacti 添加 Nginx 监控 (阅读:8925)
- Nginx+FastCgi+Php 的工作机制 (阅读:8862)
- nginx的配置文件 (阅读:8838)
- 奇怪的 Nginx 的 upstream timed out 引起响应 502 (阅读:8167)
- 解决IE6从Nginx服务器下载图片不Cache的Bug (阅读:7110)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:老王 来源: 火丁笔记
- 标签: Gzip Nginx
- 发布时间:2013-09-15 22:36:04
- [46] 界面设计速成
- [40] 视觉调整-设计师 vs. 逻辑
- [40] Oracle MTS模式下 进程地址与会话信
- [38] IOS安全–浅谈关于IOS加固的几种方法
- [37] android 开发入门
- [36] 如何拿下简短的域名
- [36] 程序员技术练级攻略
- [35] 【社会化设计】自我(self)部分――欢迎区
- [35] 图书馆的世界纪录
- [32] 读书笔记-壹百度:百度十年千倍的29条法则