lua数据类型

数据类型 描述
nil 这个最简单,只有值nil属于该类,表示一个无效值(在条件表达式中相当于false)。
boolean 包含两个值:false和true。
number 表示双精度类型的实浮点数
string 字符串由一对双引号或单引号来表示
function 由 C 或 Lua 编写的函数
userdata 表示任意存储在变量中的C数据结构
thread 表示执行的独立线路,用于执行协同程序
table Lua 中的表(table)其实是一个”关联数组”(associative arrays),数组的索引可以是数字、字符串或表类型。在 Lua 里,table 的创建是通过”构造表达式”来完成,最简单构造表达式是{},用来创建一个空表。

type()

没有赋值的变量也就为nil类型

> type(a)
nil

使用type得到的数据类型作比较时应加上双引号

> type(a) == nil
false
> type(a) == "nil"
true

因为 type(type(X))==string

table

相当于 关联数组 ,遍历表的方式:

a = {
  ['a'] = 1,
  ['b'] = 2
}

for k, v in pairs(a) do
  print(k .. " : " .. v)
end

for k, v in ipairs(a) do
  print(k .. " : " .. v)
end

pairs遍历table中的全部的key-vale,并非严格按照顺序

而ipairs会依据key的数值从1开始加1递增遍历相应的table[i]值

注意 Lua 中表的默认初始索引一般以 1 开始.

userdata(自定义类型)

userdata 是一种用户自定义数据,用于表示一种由应用程序或 C/C++ 语言库所创建的类型,可以将任意 C/C++ 的任意数据类型的数据(通常是 struct 和 指针)存储到 Lua 变量中调用。

从redis 中取数据时,若不存在所取的key,则返回值为 userdata 类型的null.

ngx.null

将lua 程序编译成二进制文件

lua本身可以使用luac将脚本编译为字节码

使用方法如下:

$ echo "print('hello')" > test.lua
$ luac -o test test.lua
$ lua test
hello

Lua 结合 nginx (openresty)

LuaNginxModule 的执行阶段说明

set_by_lua*: 流程分支处理判断变量初始化
rewrite_by_lua*: 转发、重定向、缓存等功能(例如特定请求代理到外网)
access_by_lua*: IP 准入、接口权限等情况集中处理(例如配合 iptable 完成简单防火墙) waf 通常部署阶段
content_by_lua*: 内容生成
header_filter_by_lua*: 响应头部过滤处理(例如添加头部信息)
body_filter_by_lua*: 响应体过滤处理(例如完成应答内容统一成大写)
log_by_lua*: 会话完成后本地异步完成日志记录(日志可以记录在本地,还可以同步到其他机器)

nginx 配置

...
error_log /usr/local/openresty/nginx/logs/debug.log debug;

server {
        listen  80;
        server_name _;
        index index.php index.html index.htm index.nginx-debian.html;
   			location  / {
        			default_type 'text/html';
    					set_by_lua_file "set.lua";
    					content_by_lua_block {
     							ngx.say("hello,world")
    					}
        			access_by_lua_file "init.lua";
        			lua_code_cache off;
    }

ngx_lua_module api

print()                         # ngx.print()方法有区别,print() 相当于ngx.log()  
ngx.ctx                         #这是一个lua的table,用于保存ngx上下文的变量,在整个请求的生命周期内都有效,详细参考官方  
ngx.location.capture()          #发出一个子请求,详细用法参考官方文档。  
ngx.location.capture_multi()    #发出多个子请求,详细用法参考官方文档。  
ngx.status                      #读或者写当前请求的相应状态. 必须在输出相应头之前被调用.  
ngx.header.HEADER               #访问或设置http header头信息,详细参考官方文档。  
ngx.req.set_uri()               #设置当前请求的URI,详细参考官方文档  
ngx.set_uri_args(args)          #根据args参数重新定义当前请求的URI参数.  
ngx.req.get_uri_args()          #返回一个LUA TABLE,包含当前请求的全部的URL参数  
ngx.req.get_post_args()         #返回一个LUA TABLE,包括所有当前请求的POST参数  
ngx.req.get_headers()           #返回一个包含当前请求头信息的lua table.  
ngx.req.set_header()            #设置当前请求头header某字段值.当前请求的子请求不会受到影响.  
ngx.req.read_body()             #在不阻塞ngnix其他事件的情况下同步读取客户端的body信息.[详细]  
ngx.req.discard_body()          #明确丢弃客户端请求的body  
ngx.req.get_body_data()         #以字符串的形式获得客户端的请求body内容  
ngx.req.get_body_file()         #当发送文件请求的时候,获得文件的名字  
ngx.req.set_body_data()         #设置客户端请求的BODY  
ngx.req.set_body_file()         #通过filename来指定当前请求的file data  
ngx.req.clear_header()          #清求某个请求头  
ngx.exec(uri,args)              #执行内部跳转,根据uri和请求参数  
ngx.redirect(uri, status)       #执行301或者302的重定向。  
ngx.send_headers()              #发送指定的响应头  
ngx.headers_sent                #判断头部是否发送给客户端ngx.headers_sent=true  
ngx.print(str)                  #发送给客户端的响应页面  
ngx.say()                       #作用类似ngx.print,不过say方法输出后会换行  
ngx.log(log.level,...)          #写入nginx日志  
ngx.flush()                     #将缓冲区内容输出到页面(刷新响应)  
ngx.exit(http-status)           #结束请求并输出状态码  
ngx.eof()                       #明确指定关闭结束输出流  
ngx.escape_uri()                #URI编码(本函数对逗号,不编码,而php的urlencode会编码)  
ngx.unescape_uri()              #uri解码  
ngx.encode_args(table)          #tabel解析成url参数  
ngx.decode_args(uri)            #将参数字符串编码为一个table  
ngx.encode_base64(str)          #BASE64编码  
ngx.decode_base64(str)          #BASE64解码  
ngx.crc32_short(str)            #字符串的crs32_short哈希  
ngx.crc32_long(str)             #字符串的crs32_long哈希  
ngx.hmac_sha1(str)              #字符串的hmac_sha1哈希  
ngx.md5(str)                    #返回16进制MD5  
ngx.md5_bin(str)                #返回2进制MD5  
ngx.today()                     #返回当前日期yyyy-mm-dd  
ngx.time()                      #返回当前时间戳  
ngx.now()                       #返回当前时间  
ngx.update_time()               #刷新后返回  
ngx.localtime()                 #返回 yyyy-mm-dd hh:ii:ss  
ngx.utctime()                   #返回yyyy-mm-dd hh:ii:ss格式的utc时间  
ngx.cookie_time(sec)            #返回用于COOKIE使用的时间  
ngx.http_time(sec)              #返回可用于http header使用的时间        
ngx.parse_http_time(str)        #解析HTTP头的时间  
ngx.is_subrequest               #是否子请求(值为 true or false  
ngx.re.match(subject,regex,options,ctx)     #ngx正则表达式匹配,详细参考官网  
ngx.re.gmatch(subject,regex,opt)            #全局正则匹配  
ngx.re.sub(sub,reg,opt)         #匹配和替换(未知)  
ngx.re.gsub()                   #未知  
ngx.shared.DICT                 #ngx.shared.DICT是一个table 里面存储了所有的全局内存共享变量  
ngx.shared.DICT.get    
ngx.shared.DICT.get_stale      
ngx.shared.DICT.set    
ngx.shared.DICT.safe_set       
ngx.shared.DICT.add    
ngx.shared.DICT.safe_add       
ngx.shared.DICT.replace    
ngx.shared.DICT.delete     
ngx.shared.DICT.incr       
ngx.shared.DICT.flush_all      
ngx.shared.DICT.flush_expired      
ngx.shared.DICT.get_keys  
ndk.set_var.DIRECTIVE

lua Require 问题

默认寻找路径:

/usr/local/openresty/lualib

可通过lua_package_path来指定:

 lua_package_path '/foo/bar/?.lua;/blah/?.lua;;';
local config = require "waf.config"
local redis = require "resty.redis"
local cjson = require "cjson"

共享内存

ngx_lua 提供了一系列共享内存相关的 API (ngx.shared.DICT),可以很方便地通过设置过期时间来使得缓存被动过期,共享内存就是在内存块中分配出一个空间,让几个不相干的进程都能访问存储在这里面的变量数据。

当开辟的共享内存存满了之后,若继续插入,会使用强制删除(LRU算法)进行插入

LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

 http {
  #lua_shared_dict <name> <size>
  lua_shared_dict ip_blacklist 10m;
  server {
    .....
      content_by_lua_block{
      	local ip_blacklist = ngx.shared.ip_blacklist
      	local ok, err = ip_blacklist:set("192.168.2.2", 1)
        if ok then
        	ngx.say("ok")';
      	end
        ngx.say(ip_blacklist:get("192.168.2.2"))
        
       	local ok, err = ip_blacklist:expire("192.168.2.2", 10)
        ...
    }                  
  }
}

lua+Redis

local config = require "waf.config"
local redis = require "resty.redis"

local red = redis:new()
red:set_timeouts(2000)
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
    ngx.say("failed to connect: ", err)
    red:close()
end

red:get
red:set
red:expire
red:SMEMBERS
red:auth
...

例子:ip动态封禁

ngx.var.VARIABLE

读或者写nginx的变量

location /foo {
    set $my_var '';
    content_by_lua_block {
        ngx.var.my_var = 123
        ...
    }
}

仅仅是已经定义了的 Nginx 变量可以被写入

无法动态为nginx创建变量

设置 ngx.var.my_var 为 nil 值将会删除 $my_var Nginx 变量:

local config = require "waf.config"
local redis = require "resty.redis"
local cjson = require "cjson"

local red = redis:new()
red:set_timeouts(2000) -- 2 sec
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
    ngx.say("failed to connect: ", err)
    red:close()
end

res, err = red:get("pass_proxy")

if tostring(res) == "userdata: NULL" then
    red:set("pass_proxy","false")
elseif red:get("pass_proxy") == "true" then
    local dynamic_target = ngx.req.get_post_args()["dynamic_target"]
    red:set("proxy_addr",dynamic_target)
end

....

local add = red:get("proxy_addr")
ngx.var.proxy_addr = add
location  / {
            default_type 'text/html';
            set $proxy_addr '';
            proxy_pass  http://$proxy_addr/;
            proxy_set_header   X-Real-IP        $remote_addr;
            proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
            access_by_lua_file "init.lua";
            lua_code_cache off;
        }

Lua Waf 老版本通用绕过 (lua-nginx-module < v0.10.13)

Nginx Lua获取参数时,默认获取前100个参数值,其余的将被丢弃。

https://www.freebuf.com/column/171728.html