之前Nginx那篇文章讲过,我希望通过Nginx做一个测试环境,目标其实很简单,其实就是使用Nginx做反响代理服务器,绕过现在的鉴权系统,将前端请求转发到相应人员的“开发机”上去,其实在过程中遇到几个比较棘手的问题,如下:
- 如何确定相应的前端请求到后端服务映射,即区别前端请求,将需求A的前端请求转发需求A的机器上而不会转到需求B的开发机上
- 后端服务会对请求体进行SHA-1数据加密校验,将请求体通过特定的密钥加密,并将加密结果和鉴权系统给的加密结果对比,不同则校验失败。Nginx要像鉴权系统一样计算加密结果并传给后台服务
- 原鉴权系统会将后台服务返回结果格式化成统一的Jsno,测试环境使用Nginx绕过了鉴权系统,所以Nginx要完成Json格式化的这一过程
然而Nginx原生是不支持上述加密、Json格式化功能的,经过调研之后了解到,Nginx可以和Lua结合使用,那么上述问题2、3应该可以通过Lua实现。至于问题1,最终的解决方案是通过Nginx提供一个静态网页,网页上可以选择相应后台开发人员,选择的目的是set浏览器Cookie,在请求服务的时候会将Cookie带给Nginx服务器,Nginx通过Cookie实现服务转发。
最终选择选择了使用OpenResty,OpenResty由Nginx核心加很多第三方模块组成,其最大的亮点是默认集成了Lua开发环境,使得Nginx可以作为一个Web Server使用。借助于Nginx的事件驱动模型和非阻塞IO,可以实现高性能的Web应用程序。而且OpenResty提供了大量组件如Mysql、Redis、Memcached 等等,使在Nginx上开发Web应用更方便更简单。目前在京东如实时价格、秒杀、动态服务、单品页、列表页等都在使用Nginx+Lua 架构,其他公司如淘宝、去哪儿网等。本文会简单介绍一下OpenResty开发环境搭建并分享我测试环境使用的一些Lua脚本。
1. OpenResty开发环境搭建
1.1 创建安装目录
mkdir -p /usr/servers
cd /usr/servers/
1.2 安装依赖
apt-get install libreadline-dev libncurses5-dev libpcre3-dev libssl-dev perl
1.3 下载ngx_openresty-1.7.7.2.tar.gz并解压
wget http://openresty.org/download/ngx_openresty-1.7.7.2.tar.gz
tar -xzvf ngx_openresty-1.7.7.2.tar.gz
ngx_openresty-1.7.7.2/bundle目录里存放着Nginx核心和很多第三方模块,比如有我们需要的Lua和LuaJIT。
1.4 安装LuaJIT
cd bundle/LuaJIT-2.1-20150120/
make clean && make && make install
ln -sf luajit-2.1.0-alpha /usr/local/bin/luajit
1.5 下载ngx_cache_purge模块
ngx_cache_purge模块用于清理Nginx缓存
cd /usr/servers/ngx_openresty-1.7.7.2/bundle
wget https://github.com/FRiCKLE/ngx_cache_purge/archive/2.3.tar.gz
tar -xvf 2.3.tar.gz
1.5 下载nginx_upstream_check_module模块
nginx_upstream_check_module模块用于upstream健康检查
cd /usr/servers/ngx_openresty-1.7.7.2/bundle
wget https://github.com/yaoweibin/nginx_upstream_check_module/archive/v0.3.0.tar.gz
tar -xvf v0.3.0.tar.gz
1.6 安装ngx_openresty
cd /usr/servers/ngx_openresty-1.7.7.2
./configure --prefix=/usr/servers --with-http_realip_module --with-pcre --with-luajit --add-module=./bundle/ngx_cache_purge-2.3/ --add-module=./bundle/nginx_upstream_check_module-0.3.0/ -j2
make && make install
with*** 安装一些内置/集成的模块
- with-http_realip_module:取用户真实ip模块
- with-pcre:Perl兼容的达式模块
- with-luajit:集成luajit模块
- add-module:添加自定义的第三方模块,如此次的ngx_che_purge
1.7 检查是否安装成功
cd /usr/servers/
ll
会发现多出来了如下目录,说明安装成功
- /usr/servers/luajit:luajit环境,luajit类似于java的jit,即时编译,lua是一种解释语言,通过luajit可以即时编译lua代码到机器代码,得到很好的性能
- /usr/servers/lualib:要使用的lua库,里边提供了一些默认的lua库,如redis,json库等,也可以把一些自己开发的或第三方的放在这;
- /usr/servers/nginx :安装的Nginx;
1.8 启动Nginx
/usr/servers/nginx/sbin/nginx
注意,如果之前安装过Nginx,nginx这一命令已经存在软链接了,链接到原来apt安装的Nginx,所以启动Openresty Nginx需要使用绝对路径,还有在启动之前如果已经启动了apt安装的Nginx,记得将apt Nginx关闭。
2. OpenResty ngx_lua模块使用
安装过OpenResty后,Nginx模块的配置跟上篇文章讲的Nginx配置一致。不同的是OpenResty内置了一个ngx_lua模块,可以通过这个模块在Nginx中集成lua,完成一些原生Nginx不能完成的工作。下面看一个简单的示例:
2.1 nginx.conf配置
/usr/servers/nginx/conf/目录下的nginx.conf文件是Nginx的配置文件
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log logs/access.log main;
lua_package_path "/usr/servers/lualib/?.lua;;"; #lua 模块
lua_package_cpath "/usr/servers/lualib/?.so;;"; #c模块
proxy_buffers 16 512k;
proxy_buffer_size 512k;
sendfile on;
keepalive_timeout 65;
include lua.conf;
server {
listen 80;
server_name localhost;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
location = /50x.html {
root html;
}
}
}
配置文件中,指明了nginx的另一个配置文件地址,lua.conf
2.2 lua.conf配置
server {
listen 80;
server_name zhuoli.test.com;
location /lua {
default_type 'text/html';
#关闭lua缓存,修改lua脚本可以不用重启nginx,测试时可以打开,方便调试
lua_code_cache off;
content_by_lua_file conf/lua/test.lua;
}
}
监听80端口,将来自zhuoli.test.com(可以通过本地绑host文件的方式实现)的请求,通过content_by_lua_file内容处理器,接收请求处理并输出响应。
2.3 test.lua配置
ngx.say('hello world');
lua脚本只有一句,输出hello world。打开浏览器,访问http://zhuoli.test.com/lua,就可以在浏览器看到hello world这一行内容。说明OpenResty ngx_lua模块可以正常工作。
3. 测试环境搭建所使用的ngx_lua模块
上文已经提到,本次通过nginx搭建测试环境需要解决两个问题,一个是通过ngxin对请求体进行加密,并将加密结果request header传给后台服务;另一个是将从后台服务拿到的结果统一成一个格式。下面简单介绍一下我的实现。
3.1 lua.conf
server {
listen 80;
server_name zhuoli.test.com;
location /omsapi/oss/ {
#设置相应头response header
add_header 'Access-Control-Allow-Origin' '$http_origin';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'POST, GET, OPTIONS, DELETE, PUT';
add_header 'Access-Control-Allow-Headers' 'Origin, X-Requested-With, Content-Type, X_Requested_With, Accept, X-HTTP-Method-Override, Cookie, AccessToken, PASSID';
if ($request_method = 'OPTIONS') {
#跨域请求,直接204返回
return 204;
}
#设置后端服务请求头
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header App-Key *****;
proxy_set_header User-Id *****;
#对request body加密,得到的加密传塞到request header
set $backend '';
rewrite_by_lua_file conf/lua/get_sign.lua;
proxy_set_header Sign $backend;
proxy_redirect off;
#根据cookie进行请求转发,转到不同的机器上
include oss_proxy;
proxy_pass http://zhuoli.test.com;
#拦截后端服务请求,转json格式
body_filter_by_lua_file conf/lua/convert.lua;
}
}
3.2 路由转发oss_proxy
if ( $http_cookie ~* "ad_biz_env=(.*)$") {
set $env $1;
}
if ($env ~ "a-1_aniu") {
proxy_pass http://*.*.*.*:30902;
break;
}
if ($env ~ "a-1_zhuoli") {
proxy_pass http://*.*.*.*:30903;
break;
}
if ($env ~ "a-1_chitang") {
proxy_pass http://*.*.*.*:30904;
break;
}
if ($env ~ "a-2_afu") {
proxy_pass http://*.*.*.*:30900;
break;
}
if ($env ~ "a-2_xihejian") {
proxy_pass http://*.*.*.*:30901;
break;
}
if ($env ~ "a-6_luoluo") {
proxy_pass http://*.*.*.*:30905;
break;
}
if ($env ~ "a-3_hema") {
proxy_pass http://*.*.*.*:30906;
break;
}
3.3 获取加密签名
local resty_sha1 = require "resty.sha1";
local sha1 = resty_sha1:new();
if not sha1 then
ngx.say("failed to create the sha1 object");
return;
end
ngx.req.read_body();
local body_data = ngx.req.get_body_data();
#用于加密的盐
local salt = 'saltsaltsalt';
#如果请求体为nil,则转为空字符串,防止salt..body_data时报错
if (body_data == nil) then
body_data = '';
end
local tmp = salt..body_data;
#打印日志,注意Nginx日志级别时ERR,这里低于ERR级别的日志都不会打印,所以选择ERR级别
ngx.log(ngx.ERR, 'BBBBBBBB'..tmp);
local ok = sha1:update(salt..body_data);
if not ok then
ngx.say("failed to add data");
return;
end
#获取加密结果
local digest = sha1:final();
#转为16进制
local str = require "resty.string";
--ngx.log(ngx.ERR, 'RESULT'..str.to_hex(digest));
#将加密结果赋值给变量backend
ngx.var.backend = str.to_hex(digest);
都在注释中说明了,不特殊阐述了。
3.4 json格式转换convert.lua
从后台服务获取结果后,nginx转成统一格式传给前端。主要使用了lua的cjson模块,如下:
local cjson = require("cjson")
local str = ngx.arg[1]
ngx.log(ngx.ERR, 'BBBBBBBB'..str);
local obj = cjson.decode(str)
local retTable = {}
retTable["result"] = obj["success"]
retTable["data"] = obj.result
retTable["server_time"] = os.time()
#注意设置该条件,否则空数组转化时会有问题
cjson.encode_empty_table_as_object(false)
local returnResult = cjson.encode(retTable)
ngx.log(ngx.ERR, 'RRRRRRRR'..returnResult);
ngx.arg[1] = returnResult
ngx.arg[2] = true
通过以上步骤,基本完成了测试环境搭建。期间因为完全没接触过nginx和lua,遇到了很多坑,但是最终都一一解决了,最后还是挺有成就感的。这里分享一个过程中遇到的最诡异的一个情况,我发现前端跨域请求发两次,理论上后台请求肯定也是两次,但是现象确是后台概率收到3~10次不等的请求,且都是处理成功的,浏览器console却有一定概率显示是失败的。我首先怀疑应该是我转json的脚本convert.lua扛不住后端那么多的json数据一次性都过来,所以对请求根据url做了缓存,一秒钟convert.lua对于同一个url的json只转一次,但是情况并没有得到改善。之后,我觉得有可能存量数据json有可能有非法数据,所以我将所有存量数据都删掉了,然后自己一个一个的加进去,刚开始好像恢复了,不再有多次后端请求,但当我数据量加到第14条记录的时候,发现又有报错了,但是14条记录之前都是正常的,从此定位到真正的问题所在,json结果因为太长,被nginx截断了,所以在转json的时候报错了。最终通过设置Nginx buffer大小得到解决。修改nginx.conf配置,如下:
proxy_buffers 16 512k;
proxy_buffer_size 512k;
参考链接: