coding……
但行好事 莫问前程

Nginx + Lua开发

之前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;

参考链接:

  1. 跟我学 Nginx+Lua 开发
  2. Lua利用cjson读写json
  3. nginx + lua 根据 POST 数据动态路由
  4. Lua操作String
  5. nginx基本使用系列(三)_nginx常用配置文件解析

赞(0) 打赏
Zhuoli's Blog » Nginx + Lua开发
分享到: 更多 (0)

相关推荐

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

zhuoli's blog

联系我关于我

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏