当 Nginx 标准模块和配置不能灵活地适应系统要求时,就可以考虑使用 Lua 扩展和定制 Nginx 服务。OpenResty集成了大量精良的 Lua 库、第三方模块,可以方便地搭建能够处理超高并发、扩展性极高的 Web 服务,所以这里选择 OpenResty 提供的lua-nginx-module方案。

安装Lua环境

lua-nginx-module 依赖于 LuaJIT 和 ngx_devel_kit。LuaJIT 需要安装,ngx_devel_kit 只需下载源码包,在 Nginx 编译时指定 ngx_devel_kit 目录。

系统依赖库

首先确保系统已安装如下依赖库。

$ yum install readline-devel pcre-devel openssl-devel gcc

安装LuaJIT

首先,安装LuaJIT环境,如下所示:

$ wget http://luajit.org/download/LuaJIT-2.0.5.tar.gz
$ tar zxvf LuaJIT-2.0.5.tar.gz
$ cd LuaJIT-2.0.5
$ make install
# 安装成功
==== Successfully installed LuaJIT 2.0.5 to /usr/local ====

设置 LuaJIT 有关的环境变量。

$ export LUAJIT_LIB=/usr/local/lib
$ export LUAJIT_INC=/usr/local/include/luajit-2.0
$ echo "/usr/local/lib" > /etc/ld.so.conf.d/usr_local_lib.conf
$ ldconfig

下载相关模块

下载ngx_devel_kit源码包,如下:

$ wget https://github.com/simpl/ngx_devel_kit/archive/v0.3.0.tar.gz
$ tar zxvf v0.3.0.tar.gz
# 解压缩后目录名
ngx_devel_kit-0.3.0

接下来,下载 Lua 模块lua-nginx-module源码包,为 Nginx 编译作准备。

$ wget https://github.com/openresty/lua-nginx-module/archive/v0.10.10.tar.gz
$ tar zxvf v0.10.10.tar.gz
# 解压缩后目录名
lua-nginx-module-0.10.10

加载Lua模块

Nginx 1.9 版本后可以动态加载模块,但这里由于版本太低只能重新编译安装 Nginx。下载 Nginx 源码包并解压:

$ wget http://nginx.org/download/nginx-1.13.5.tar.gz
$ tar zxvf nginx-1.13.5.tar.gz

编译并重新安装 Nginx:

$ cd nginx-1.13.5
# 增加--add-module=/usr/src/lua-nginx-module-0.10.10 --add-module=/usr/src/ngx_devel_kit-0.3.0
$ ./configure --prefix=/usr/local/nginx --with-http_ssl_module --with-http_v2_module --with-http_stub_status_module --with-pcre --add-module=/usr/src/lua-nginx-module-0.10.10 --add-module=/usr/src/ngx_devel_kit-0.3.0
$ make
$ make install
# 查看是否安装成功
$ nginx -v

配置Nginx环境

现在只需配置 Nginx,即可嵌入 Lua 脚本。首先,在 http 部分配置 Lua 模块和第三方库路径:

# 第三方库(cjson)地址luajit-2.0/lib
lua_package_path '/home/www/lua/?.lua;;';
lua_package_cpath '/usr/local/include/luajit-2.0/lib/?.so;;';

接着,配置一个 Lua 脚本服务:

# hello world测试
server {
location /lua_content {
# 定义MIME类型
default_type 'text/plain';
content_by_lua_block {
ngx.say('Hello,world!')
}
}
}

测试安装和配置是否正常:

$ service nginx test
$ service nginx reload
# 访问地址/lua_content输出
Hello,world!

Lua调用Nginx

lua-nginx-module 模块中已经为 Lua 提供了丰富的 Nginx 调用 API,每个 API 都有各自的作用环境,详细描述见Nginx API for Lua。这里只列举基本 API 的使用 。

先配一个 Lua 脚本服务,配置文件如下:

location ~ /lua_api {  
    # 示例用的Nginx变量  
    set $name $host;
    default_type "text/html";  
    # 通过Lua文件进行内容处理
    content_by_lua_file /home/www/nginx-api.lua;  
}

请求部分

可以通过ngx.var.var_name形式获取或设置 Nginx 变量值,例如 request_uri、host、request 等。

-- ngx.say打印内容
ngx.say(ngx.var.request_uri)
ngx.var.name = 'www.fanhaobai.com'

该方法会以表的形式返回当前请求的头信息。查看请求的头信息:

ngx.say('Host : ', ngx.req.get_headers().host, '<br>')
for k,v in pairs(ngx.req.get_headers()) do
if type(v) == "table" then
ngx.say(k, " : ", table.concat(v, ","), '<br>')
else
ngx.say(k," : ", v, '<br>')
end
end

当然,通过ngx.req.set_header()也可以设置头信息。

ngx.req.set_header("Content-Type", "text/html")

该方法以表形式返回当前请求的所有 GET 参数。查看请求 query 为?name=fhb的 GET 参数:

ngx.say('name : ', ngx.req.get_uri_args().name, '<br>')
for k,v in pairs(ngx.req.get_uri_args()) do
if type(v) == "table" then
ngx.say(k, " : ", table.concat(v, ","), '<br>')
else
ngx.say(k," : ", v, '<br>')
end
end

同样,可以通过ngx.req.set_uri_args()设置请求的所有 GET 参数。

ngx.req.set_uri_args({name='fhb'}) --{name='fhb'}可以为query形式name=fhb

该方法以表形式返回当前请求的所有 POST 参数,POST 数据必须是 application/x-www-form-urlencoded 类型。查看请求curl --data 'name=fhb' localhost/lua_api的 POST 参数:

--必须先读取body体
ngx.req.read_body()
ngx.say('name : ', ngx.req.get_post_args().name, '<br>')
for k,v in pairs(ngx.req.get_post_args()) do
if type(v) == "table" then
ngx.say(k, " : ", table.concat(v, ","), '<br>')
else
ngx.say(k," : ", v, '<br>')
end
end

通过ngx.req.get_body_data()方法可以获取未解析的请求 body 体内容字符串。

获取请求的大写字母形式的请求方式,通过ngx.req.set_method()可以设置请求方式。例如:

ngx.say(ngx.req.get_method())

响应部分

通过ngx.header.header_name的形式获取或设置响应头信息。如下:

ngx.say(ngx.header.content_type)
ngx.header.content_type = 'text/plain'

ngx.print() 方法会填充指定内容到响应 body 中。如下所示:

ngx.print(ngx.header.content_type)

如上述使用,ngx.say() 方法同 ngx.print() 方法,只是会在后追加一个换行符。

以某个状态码返回响应内容,状态码常量对应关系见HTTP status constants部分,也支持数字形式的状态码。

ngx.exit(403)

重定向当前请求到新的 url,响应状态码可选列表为 301、302(默认)、303、307。

ngx.redirect('http://www.fanhaobai.com')

其他

该方法提供了正则表达式匹配方法。请求?name=fhb&age=24匹配 GET 参数中的数字:

local m, err = ngx.re.match(ngx.req.set_uri_args, "[0-9]+")
if m then
ngx.say(m[0])
else
ngx.say("match not found")
end

通过该方法可以将内容写入 Nginx 日志文件,日志文件级别需同 log 级别一致。

它们都是字符串编码方式。ngx.md5() 可以对字符串进行 md5 加密处理,而 ngx.encode_base64() 是对字符串 base64 编码, ngx.decode_base64() 为 base64 解码。

Nginx中嵌入Lua

上面讲述了怎么在 Lua 中调用 Nginx 的 API 来扩展或定制 Nginx 的功能,那么编写好的 Lua 脚本怎么在 Nginx 中得到执行呢?其实,Nginx 是通过模块指令形式在其 11 个处理阶段做插入式处理,指令覆盖 http、server、server if、location、location if 这几个范围。

模块指令列表

这里只列举基本的 Lua 模块指令,更多信息参考Directives部分。

指令 所在阶段 使用范围 说明
init_by_lua
init_by_lua_file
加载配置文件 http 可以用于初始化全局配置
set_by_lua
set_by_lua_file
rewrite server
location
location if
复杂逻辑的变量赋值,注意是阻塞的
rewrite_by_lua
rewrite_by_lua_file
rewrite http
server
location
location if
实现复杂逻辑的转发或重定向
content_by_lua
content_by_lua_file
content location
location if
处理请求并输出响应
header_filter_by_lua
header_filter_by_lua_file
响应头信息过滤 http
server
location
location if
设置响应头信
body_filter_by_lua
body_filter_by_lua_file
输出过滤 http
server
location
location if
对输出进行过滤或修改

使用指令

注意到,每个指令都会有*_lua*_lua_file两个指令,*_lua指令后为 Lua 代码块,而*_lua_file指令后为 Lua 脚本文件路径。下面将只对*_lua指令进行说明。

  • init_by_lua

该指令会在 Nginx 的 Master 进程加载配置时执行,所以可以完成 Lua 模块初始化工作,Worker 进程同样会继承这些。

nginx.conf配置文件中的 http 部分添加如下代码:

-- 所有worker共享的全局变量
lua_shared_dict shared_data 1m;  
init_by_lua_file /usr/example/lua/init.lua;

init.lua初始化脚本为:

local cjson = require 'cjson'
local redis = require 'resty.redis'
local shared_data = ngx.shared.shared_data
  • set_by_lua

我们直接使用 set 指令很难实现很复杂的变量赋值逻辑,而 set_by_lua 模块指令就可以解决这个问题。

nginx.conf配置文件 location 部分内容为:

location /lua {
set_by_lua_file $num /home/www/set.lua;
default_type 'text/html';
echo $num;
}

set.lua脚本内容为:

local uri_args = ngx.req.get_uri_args()
local i = uri_args.a or 0
local j = uri_args.b or 0
return i + j

上述赋值逻辑,请求 query 为?a=10&b=2时响应内容为 12。

  • rewrite_by_lua

可以实现内部 URL 重写或者外部重定向。nginx.conf配置如下:

location /lua {
default_type "text/html";
rewrite_by_lua_file /home/www/rewrite.lua;
}

rewrite.lua脚本内容:

if ngx.req.get_uri_args()["type"] == "app" then
ngx.req.set_uri("/m_h5", false);
end
  • access_by_lua

用于访问权限控制。例如,只允许带有身份标识用户访问,nginx.conf配置为:

location /lua {  
default_type "text/html";
access_by_lua_file /home/www/access.lua;
}  

access.lua脚本内容为:

if ngx.req.get_uri_args()["token"] == "fanhb" then
return ngx.exit(403)
end
  • content_by_lua

该指令在Lua 调用 Nginx部分已经使用过了,用于输出响应内容。

案例

访问权限控制

使用 Lua 模块对本站的 ES 服务做受信操作控制,即非受信 IP 只能查询操作。nginx.conf配置如下:

location / {
set $allowed '115.171.226.212';
access_by_lua_block {
if ngx.re.match(ngx.req.get_method(), "PUT|POST|DELETE") and not ngx.re.match(ngx.var.request_uri, "_search") then
start, _ = string.find(ngx.var.allowed, ngx.var.remote_addr)
if not start then
ngx.exit(403)
end
end
} proxy_pass http://127.0.0.1:9200$request_uri;
}

访问频率控制

在 Nginx 配置文件的 location 部分配置 Lua 脚本基本参数,并配置 Lua 模块指令:

default_type "text/html";
set rate_per 300
access_by_lua_file /home/www/access.lua;

Lua 脚本实现频率控制逻辑,使用 Redis 对单位时间内的访问次数做缓存,key 为访问 uri 拼接 token 后的 md5 值。具体内容如下:

local redis = require "resty.redis"
local red = redis:new() local limit = tonumber(ngx.var.rate_per) or 200
local expire_time = tonumber(ngx.var.rate_expire) or 1000
local key = "rate.limit:string:" red:set_timeout(500)
local ok, err = red:connect("www.fanhaobai.com", 6379)
if not ok then
ngx.log(ngx.ERR, "failed to connect redis: " .. err)
return
end key = key .. ngx.md5(ngx.var.request_uri .. (ngx.req.get_uri_args()['token'] or ngx.req.get_post_args()['token']))
local times, err = red:incr(key)
if not times then
ngx.log(ngx.ERR, "failed to exec incr: " .. err)
return
elseif times == 1 then
ok, err = red:expire(key, expire_time)
if not ok then
ngx.log(ngx.ERR, "failed to exec expire: " .. err)
return
end
end if times > limit then
return ngx.exit(403)
end return

转载樊浩柏科学院

相关推荐:

nginx lua模块常用的指令

最新文章

  1. 解决新版Android studio导入微信支付和支付宝官方Demo的问题
  2. ASP.NET Core 中文文档 第三章 原理(2)中间件
  3. asp.net 文件下载(txt,rar,pdf,word,excel,ppt)
  4. 【2016-10-26】【坚持学习】【Day13】【WCF】【EF + Data Services】
  5. Discovering-论文
  6. Spark学习(三) -- SparkContext初始化
  7. Oracle死锁查询及处理
  8. 【转】Flume日志收集
  9. 我用工具怎么连接不上mysql数据库的? MySql access denied for user错误
  10. Apache Tomcat关于shtml和SSI技术
  11. 关于用Max导出Unity3D使用的FBX文件流程注解
  12. postgresql 分区表
  13. Provider Communication with Apple Push Notification Service
  14. html和css 基础梳理之一
  15. .config-20150410
  16. Best Time to Buy and Sell Stock I,II,III [leetcode]
  17. 一个人开发的html整站源码分享网站就这么上线了
  18. shell脚本 案例
  19. HBase存储剖析与数据迁移
  20. C语言程序设计基础-第1周作业-初步

热门文章

  1. JVM学习十一 - (复习)性能调优
  2. 常见消息处理api
  3. eclipse使用的步骤
  4. 友盟分享,极光推送Demo
  5. WebLogic11g- 集群化
  6. linux_20
  7. JVM性能调优与实战基础理论篇-中
  8. ClassPath资源的读取
  9. Solution -「JOISC 2020」「UOJ #509」迷路的猫
  10. MyBatis功能点二应用:第三方分页插件使用