您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 订阅
  捐助
nginx+lua 入门
 
作者: SRE
  2311  次浏览      16
 2019-10-17
 
编辑推荐:
文章简单的说明Nginx 安装和通过小功能大体上了解了怎样编写 nginx+lua 模块以及nginx的配置
本文来自于微信公众号小米云技术,由火龙果软件琪琪编辑推荐

nginx+lua 编译安装

1. nginx 版本的选择

我们选择最新 Stable 版本的 nginx-1.14.0:

# wget https://nginx.org/download/nginx-1.14.0.tar.gz
# tar xvf nginx-1.14.0.tar.gz

2. 编译 nginx 需要解决前置的一些依赖,因为我们使用的是 centos 系统,有一些依赖可以直接使用 yum 安装,但是有一些我们希望使用特定的版本,不想使用系统自带的版本,比如 OpenSSL。

yum 安装需要的依赖

yum install gcc pcre pcre-devel gd-devel xz -y

下载需要的 OpenSSL,现在 OpenSSL 最新的稳定版是 1.1.0 系列,1.0.2 是长期支持版本并且支持到 2019 年 12 月 31 号。0.9.8、1.0.0 和 1.0.1 版本现在已经不再维护,官方建议不要使用这些版本。

# cd nginx-1.14.0/src
# wget https://www.openssl.org/source/openssl-1.1.0h.tar.gz
# tar xvf openssl-1.1.0h.tar.gz

下载 Nginx Development Kit (NDK) 模块,lua-nginx-module 依赖该模块

# cd src
# git clone https://github.com/simplresty/ngx_devel_kit.git

下载 headers-more-nginx-module 模块

# git clone https://github.com/openresty/headers-more -nginx-module.git

下载 ngx_cache_purge 模块

# git clone https://github.com/FRiCKLE/ngx_cache_purge.git

下载 lua 支持模块 lua-nginx-module,该模块的依赖详细介绍请见 github

# git clone https://github.com/simplresty/ngx_devel_kit.git

3. 编译安装 Lua-JIT,现在 Lua-JIT 的最新版本为 2.1.0-beta3,于 2017 年 5 月 1 号发布,我们就使用这个版本:

# wget http://luajit.org/download/LuaJIT-2.1.0-beta3.tar.gz
# tar xvf LuaJIT-2.1.0-beta3.tar.gz
# mv LuaJIT-2.1.0-beta3 LuaJIT-2.1
# cd LuaJIT-2.1# make -j 10
# make install
# cd /usr/local/bin && ln -sf luajit-2.1.0-beta3 luajit
# sed -ri '/export.*LUAJIT_(LIB|INC)/d' /etc/profile
# echo -e "\nexport LUAJIT_LIB=/usr/local/lib\n \
export LUAJIT_INC=/usr/local/include/luajit-2.1\n \
export LD_LIBRARY_PATH=\$LUAJIT_LIB:\$LD_LIBRARY_PATH" >> /etc/profile
# . /etc/profile

4. 编译安装 nginx,由于我们使用的是 24core 服务器,所以在编译 nginx 的时候我们使用了并行编译,速度会快一些:

# cd nginx-1.14.0
# ./configure --prefix=/home/work/app/nginx \
--with-http_realip_module \
--with-http_gzip_static_module \
--with-http_addition_module \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_sub_module \
--without-mail_smtp_module \
--without-mail_imap_module \
--with-http_image_filter_module \
--without-mail_pop3_module \
--with-pcre=./src/pcre-8.42 \
--add-module=./src/ngx_devel_kit \
--add-module=./src/ngx_cache_purge \
--add-module=./src/headers-more-nginx-module \
--add-module=./src/lua-nginx-module \--with-pcre-jit \
--with-openssl=./src/openssl-1.1.0h \
--with-openssl-opt='-O3 -fPIC'
# make -j 20
# make install

编译安装完成后可以看到在 /home/work/app/nginx 已经有了我们的 nginx 相关的文件:

# ls /home/work/app/nginx
conf html logs sbin

安装需要的 lua 模块

由于这里我们的 nginx 只是一个静态文件服务器,没有后端 server 的处理逻辑,为了实现判断用户的归属,我们需要通过 nginx 来实现。考虑到使用 GEOIP 库后续升级维护可能比较麻烦,我们使用的是能通过 API 来获取用户归属的服务。

首先,我们手头有这样的服务,通过访问可以返回以下示例:

# curl "http://xxx.xxxxx.com/api/v1/ip/ xx.xxx.xx.xx2/query"
{
country: "中国", //国家
province:"北京", //省份
city: "北京", //城市
district: "海淀区", //区县
isp: "联通", //运营商
srcip: "xx.xxx.xx.xx2" //查询IP

}

从上面可以看出我们可以获取我们需要的国家信息了,其余信息可以先不管,毕竟我们要做的是国家粒度。正常的返回结果是一个 json 串,那么我们选择使用 lua-cjson 来解析该 json 串,要在 nginx 中发起 http 请求,我们选择使用 luasocket 来实现。

1. 安装编译 lua 模块需要的依赖

# yum install lua-devel

2. 安装 luasocket

# cd /tmp
# wget http://files.luaforge.net/releases/ luasocket/\
luasocket/luasocket-2.0.2/luasocket-2.0.2.tar.gz
# tar xvf luasocket-2.0.2.tar.gz
# cd luasocket-2.0.2
# make && make install

3. 安装 cjson

# cd /tmp && wget https://www.kyne.com.au/~mark/\
software/download/lua-cjson-2.1.0.tar.gz
# tar xvf lua-cjson-2.1.0.tar.gz
# cd lua-cjson-2.1.0
# make && make install
# cp /usr/local/lib/lua/5.1/cjson.so /usr/lib64/lua/5.1/

4. 验证安装

能正常发起请求表示 luasocket 安装成功

# lua
Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio
> http = require("socket.http")
> url='http://iplib.pt.xiaomi.com/api/v1/ip/61.148. 40.192/single/query'
> result, statuscode, content = http.request(url)
> print(result)
{
"country": "中国",
"province": "北京",
"city": "北京",
"district": "海淀区",
"isp": "联通",
"srcip": "61.148.40.192"
}
> print(statuscode)
200

能正常解析 json 串,表示 cjson 安装成功

# lua
Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio
> jsoncontent='{"name":"xiaomi","age":8}'
> cjson = require("cjson")
> tb = cjson.decode(jsoncontent)
> print(tb.name)
xiaomi
> print(tb.age)
8

nginx lua 处理原理介绍

基于 nginx 使用的多模块设计思想,nginx 对 http 请求的处理划分为 11 个阶段。这样可以使一个 http 请求的处理过程由很多模块参与处理,每个模块只专注于一个独立而简单的功能处理,可以拥有更好的扩展。11 个阶段如下所示:

typedef enum {
NGX_HTTP_POST_READ_PHASE = 0,
NGX_HTTP_SERVER_REWRITE_PHASE,
NGX_HTTP_FIND_CONFIG_PHASE,
NGX_HTTP_REWRITE_PHASE,
NGX_HTTP_POST_REWRITE_PHASE,
NGX_HTTP_PREACCESS_PHASE,
NGX_HTTP_ACCESS_PHASE,
NGX_HTTP_POST_ACCESS_PHASE,
NGX_HTTP_TRY_FILES_PHASE,
NGX_HTTP_CONTENT_PHASE,
NGX_HTTP_LOG_PHASE
} ngx_http_phases;

这 11 个阶段的意义分别如下:

NGX_HTTP_POST_READ_PHASE:接收完请求头之后的处理阶段,它位于 uri 重写之前,一般很少有模块注册在该阶段;

NGX_HTTP_SERVER_REWRITE_PHASE:server 级别的 URL 重写,在 location 块之前;

NGX_HTTP_FIND_CONFIG_PHASE:根据请求的 URI 寻找匹配的 location 表达式,这个阶段只能由ngx_http_core_module模块实现;

NGX_HTTP_REWRITE_PHASE: location 级别的 uri 重写阶段,该阶段执行 location 基本的重写指令,可能会被执行多次;

NGX_HTTP_POST_REWRITE_PHASE:重写 URL 后,防止错误的 nginx.conf 配置导致死循环(递归修改 URI),并根据结果跳到合适的阶段;

NGX_HTTP_PREACCESS_PHASE:访问权限控制的前一阶段,该阶段在权限控制阶段之前,一般也用于访问控制,比如限制访问频率,链接数等;

NGX_HTTP_ACCESS_PHASE:访问权限控制阶段,比如基于 ip 黑白名单的权限控制,基于用户名密码的权限控制等;

NGX_HTTP_POST_ACCESS_PHASE:访问权限控制的后一阶段,用于上一阶段的收尾工作;

NGX_HTTP_TRY_FILES_PHASE:该阶段是为 try_files 设置的;

NGX_HTTP_CONTENT_PHASE:内容生成阶段,该阶段产生响应,并发送到客户端;

NGX_HTTP_LOG_PHASE:日志写入阶段。ngx_http_log_module 实际上也是在这里注册了一个 handler 实现日志写入的。

Lua 可使用的主要阶段:

编写 nginx lua 模块

终于到了该模块啦,在该模块中我们将实现通过用户 IP 的归属将用户重定向到中 / 英文首页。

1. 首先构造获取用户的真实 IP:

cjson = require("cjson")
http = require("socket.http")
local remote_ip = ngx.req.get_headers() ["x-forwarded-for"]

if remote_ip == nil
then
remote_ip = ngx.req.get_headers()["x-real-ip"]
else
local remote_ip_tmp = split(remote_ip, ',')
remote_ip = remote_ip_tmp[1]
end

if remote_ip == nil
then
remote_ip = ngx.var.remote_addr
end

因为 x-forwarded-for 获取的用户 IP 要么是 nil,要么是 192.168.1.100,要么是 192.168.1.100,192.168.100.10 这种格式,所以要进行处理,获取到用户最真实的 IP,如果该字段没有内容则取 remote_addr 作为用户 IP。因为 lua 中的字符串操作没有 split 函数,所以这里我们自己实现了 splite 操作:

function split( str,reps )
local resultStrList = {}
string.gsub(str,'[^'..reps..']+', function ( w )
table.insert(resultStrList,w)
end)
return resultStrList
end

2. 构造获取用户 IP 归属的 http 请求。我们在上一步中已经获取到了用户的真实 IP,现在进一步获取用户所属的国家。

url_prefix = "http://xxx.xx.com/api/v1/ip/"
url_postfix = "/single/query"
real_url = url_prefix..remote_ip..url_postfix
result, statuscode, content = http.request(real_url)

现在我们已经获取到用户 IP 的归属信息,但是由于 cjson 处理的 json 串只能在一行中,所以我们要深入处理下获取到的数据: * 去掉多于的换行 * 去掉多于的空格

newstr = string.gsub(result, '\n', '')
newstr = string.gsub(newstr, ' ', '')

接下来可以对处理后的 newstr 进行 cjson decode 操作了:

tb = cjson.decode(newstr)
if tb == nil
then
ngx.redirect("/en", ngx.HTTP_MOVED_TEMPORARILY)
else
if (tb.country == "中国")
then
ngx.redirect("/zh", ngx.HTTP_MOVED_TEMPORARILY)
else
ngx.redirect("/en", ngx.HTTP_MOVED_TEMPORARILY)
end
end

在处理上我们为了鲁棒性考虑,当 cjson decode 失败时 tb 的值会是 nil,则后面所取的国家属性也是 nil,如果这种情况发生则返回英文首页,还有一个问题,如果我们根本就没有获取到客户的 IP,是不是可以省略掉内部发起 http 访问的环节?直接返回英文首页,嗯,下面在发起 http 请求的代码前面添加如下代码:

if remote_ip == nil or remote_ip == ""
then
ngx.redirect("/en", ngx.HTTP_MOVED_ TEMPORARILY)end

还有一个问题,如果我们向后端发起 http 请求后,请求被 block 了,等了 1 分钟才返回怎么办?这样的话我们的页面岂不是也会有问题?等一分钟再返回给用户?这个太不优雅了,我们这里可以给请求设置超时时间,比如超过 1s 中就强制结束,所以我们构造 http 请求的代码变成了这样:

http = require("socket.http")
http.TIMEOUT = 1

最后一个需求,怎么看我们写的脚本有没有执行成功呢?怎么判断我们通过用户 IP 获取到的国家是否正确呢?我们需要加打印一些日志信息,我们在if (tb.country == "中国")前面添加了如下代码:

ngx.log(ngx.ERR, "reqeustInfo : remote_ip:", \
remote_ip, ",country:", tb.country)

这样我们就能知道获取到的用户是来自于哪里了。 中国用户访问我们的测试域名 xxxx.srv 会被重定向到 xxxx.srv/zh 页面,国外 IP 访问会被重定向到 xxxx.srv/en 页面,日志示例如下:

2018/05/28 18:28:22 [error] 27267#0: *46639625 [lua] docs.lua:75:
reqeustInfo : remote_ip:xx.xxx.xxx.179,
country:印度, client: xx.xxx.xxx.179,
server: docs-tst.srv,
request: "GET / HTTP/1.1", host: "docs-tst.srv"

-

2018/05/28 18:30:33 [error] 27267#0: *46640486 [lua] docs.lua:75:
reqeustInfo : remote_ip:111.xx.xxx.196,
country:中国, client: 111.xx.xx.196,
server: docs-tst.srv, request: "GET / HTTP/1.1", host: "docs-tst.srv"

最后 nginx 配置如下:

location / {
rewrite_by_lua_file lua/docs.lua;
}

location /zh {
alias docs-zh/;
autoindex on;
autoindex_localtime on;
expires 24h;
}

location /en {
alias docs-en/;
autoindex on;
autoindex_localtime on;
expires 24h;
}
   
2311 次浏览       16
????

HTTP????
nginx??????
SD-WAN???
5G?????
 
????

??????????
IPv6???????
??????????
???????
????

????????
????????
???????????????
??????????