标签归档:lua

【Lua教程】ngx_lua模块中正则表达式相关api

ngx.re.match

语法:captures, err = ngx.re.match(subject, regex, options?, ctx?, res_table?)

只有第一次匹配的结果被返回,如果没有匹配,则返回nil;或者匹配过程中出现错误时,也会返回nil,此时错误信息会被保存在err中。

当匹配的字符串找到时,一个Lua table captures会被返回,captures[0]中保存的就是匹配到的字串,captures[1]保存的是用括号括起来的第一个子模式的结果,captures[2]保存的是第二个子模式的结果,依次类似。

    local m, err = ngx.re.match("hello, 1234", "[0-9]+")
    if m then
        -- m[0] == "1234"
    else
        if err then
            ngx.log(ngx.ERR, "error: ", err)
            return
        end
 
        ngx.say("match not found")
    end

上面例子中,匹配的字符串是1234,因此m[0] == “1234”,但是没有用括号括起来的子模式,因此,m[1],m[2]等均为nil。

    local m, err = ngx.re.match("hello, 1234", "([0-9])[0-9]+")
    -- m[0] == "1234"
    -- m[1] == "1"

命名方式的捕获,从v0.7.14版本后开始支持,如下所示:

例1:
    local m, err = ngx.re.match("hello, 1234", "([0-9])(?<remaining>[0-9]+)")
    -- m[0] == "1234"
    -- m[1] == "1"
    -- m[2] == "234"
    -- m["remaining"] == "234"
例2:
    local m, err = ngx.re.match("hello, world", "(world)|(hello)|(?<named>howdy)")
    -- m[0] == "hello"
    -- m[1] == nil
    -- m[2] == "hello"
    -- m[3] == nil
    -- m["named"] == nil

例2中,为什么m[1]等于nil?因为在给定的模式串中,最先匹配的是hello,因此,其他子模式在找到的匹配串中查找不到对应的匹配串,因此,除了hello子模式外,其他的子模式的匹配结果都是nil。一定要记住,是谁最先被匹配的

options选项可以是下面的取值的组合:

 a 锚定模式,只从头开始匹配. 
 d DFA模式,或者称最长字符串匹配语义,需要PCRE 6.0+支持.
 D 允许重复的命名的子模式,该选项需要PCRE 8.12+支持,例如
 local m = ngx.re.match("hello, world",
 "(?<named>\w+), (?<named>\w+)",
 "D")
 -- m["named"] == {"hello", "world"}
 i 大小写不敏感模式.
 j 启用PCRE JIT编译, 需要PCRE 8.21+ 支持,并且必须在编译时加上选项--enable-jit,
 为了达到最佳性能,该选项总是应该和'o'选项搭配使用. 
 J 启用PCRE Javascript的兼容模式,需要PCRE 8.12+ 支持. 
 m 多行模式.
 o 一次编译模式,启用worker-process级别的编译正则表达式的缓存.
 s 单行模式.
 u UTF-8模式. 该选项需要在编译PCRE库时加上--enable-utf8 选项.
 U 与"u" 选项类似,但是该项选禁止PCRE对subject字符串UTF-8有效性的检查.
 x 扩展模式

两个例子:

    local m, err = ngx.re.match("hello, world", "HEL LO", "ix")
    -- m[0] == "hello"
    local m, err = ngx.re.match("hello, 美好生活", "HELLO, (.{2})", "iu")
    -- m[0] == "hello, 美好"
    -- m[1] == "美好"

第四个可选参数ctx可以传入一个Lua Table,传入的Lua Table可以是一个空表,也可以是包含pos字段的Lua Table。如果传入的是一个空的Lua Table,那么,ngx.re.match将会从subject字符串的起始位置开始匹配查找,查找到匹配串后,修改pos的值为匹配字符串的下一个位置的值,并将pos的值保存到ctx中,如果匹配失败,那么pos的值保持不变;如果传入的是一个非空的Lua Table,即指定了pos的初值,那么ngx.re.match将会从指定的pos的位置开始进行匹配,如果匹配成功了,修改pos的值为匹配字符串的下一个位置的值,并将pos的值保存到ctx中,如果匹配失败,那么pos的值保持不变。

    local ctx = {}
    local m, err = ngx.re.match("1234, hello", "[0-9]+", "", ctx)
         -- m[0] = "1234"
         -- ctx.pos == 5
    local ctx = { pos = 2 }
    local m, err = ngx.re.match("1234, hello", "[0-9]+", "", ctx)
         -- m[0] = "34"
         -- ctx.pos == 5

注意:

如果需要传入ctx参数,但并不需要第三个可选参数options时,第三个参数也不能简单去掉,这时需要传入一个空的字符串作为第三个参数的值。

第四个可选参数还不是很熟悉,暂且留空。

 

ngx.re.find

语法:from, to, err = ngx.re.find(subject, regex, options?, ctx?, nth?)

该方法与ngx.re.match方法基本类似,不同的地方在于ngx.re.find返回的是匹配的字串的起始位置索引和结束位置索引,如果没有匹配成功,那么将会返回两个nil,如果匹配出错,还会返回错误信息到err中。

例子:

    local s = "hello, 1234"
    local from, to, err = ngx.re.find(s, "([0-9]+)", "jo")
    if from then
        ngx.say("from: ", from)
        ngx.say("to: ", to)
        ngx.say("matched: ", string.sub(s, from, to))
    else
        if err then
            ngx.say("error: ", err)
            return
        end
        ngx.say("not matched!")
    end

输出结果:

from: 8

to: 11

matched: 1234

该方法相比ngx.re.match,不会创建新的Lua字符串,也不会创建新的Lua Table,因此,该方法比ngx.re.match更加高效,因此,在可以使用ngx.re.find的地方应该尽量使用。

第五个参数可以指定返回第几个子模式串的起始位置和结束位置的索引值,默认值是0,此时将会返回匹配的整个字串;如果nth等于1,那么将返回第一个子模式串的始末位置的索引值;如果nth等于2,那么将返回第二个子模式串的始末位置的索引值,依次类推。如果nth指定的子模式没有匹配成功,那么将会返回两个nil。

    local str = "hello, 1234"
    local from, to = ngx.re.find(str, "([0-9])([0-9]+)", "jo", nil, 2)
    if from then
        ngx.say("matched 2nd submatch: ", string.sub(str, from, to))  -- yields "234"
    end

 

ngx.re.gmatch

语法:iterator, err = ngx.re.gmatch(subject, regex, options?)

与ngx.re.match相似,区别在于该方法返回的是一个Lua的迭代器,这样就可以通过迭代器遍历所有匹配的结果。

如果匹配失败,将会返回nil,如果匹配出现错误,那么还会返回错误信息到err中。

    local iterator, err = ngx.re.gmatch("hello, world!", "([a-z]+)", "i")
    if not iterator then
        ngx.log(ngx.ERR, "error: ", err)
        return
    end
 
    local m
    m, err = iterator()    -- m[0] == m[1] == "hello"
    if err then
        ngx.log(ngx.ERR, "error: ", err)
        return
    end
 
    m, err = iterator()    -- m[0] == m[1] == "world"
    if err then
        ngx.log(ngx.ERR, "error: ", err)
        return
    end
 
    m, err = iterator()    -- m == nil
    if err then
        ngx.log(ngx.ERR, "error: ", err)
        return
    end

更多情时候,只需要把迭代过程放入一个while循环中即可:

    local it, err = ngx.re.gmatch("hello, world!", "([a-z]+)", "i")
    if not it then
        ngx.log(ngx.ERR, "error: ", err)
        return
    end
 
    while true do
        local m, err = it()
        if err then
            ngx.log(ngx.ERR, "error: ", err)
            return
        end
 
        if not m then
            -- no match found (any more)
            break
        end
 
        -- found a match
        ngx.say(m[0])
        ngx.say(m[1])
    end

options选项的使用与ngx.re.match中的options选项的用法是一样的。

注意点:

ngx.re.gmatch返回的迭代器只能在一个请求所在的环境中使用,就是说,我们不能把返回的迭代器赋值给持久存在的命名空间(比如一个Lua Packet)中的某一个变量。

 

ngx.re.sub

语法:newstr, n, err = ngx.re.sub(subject, regex, replace, options?)

该方法主要实现匹配字符串的替换,会用replace替换匹配的字串,replace可以是纯字符串,也可以是使用$0, $1等子模式串的形式,ngx.re.sub返回进行替换后的完整的字符串,同时返回替换的总个数;options选项,与ngx.re.match中的options选项是一样的。

    local newstr, n, err = ngx.re.sub("hello, 1234", "([0-9])[0-9]", "[$0][$1]")
    if newstr then
        -- newstr == "hello, [12][1]34"
        -- n == 1
    else
        ngx.log(ngx.ERR, "error: ", err)
        return
    end

在上面例子中,$0表示整个匹配的子串,$1表示第一个子模式匹配的字串,以此类推。

可以用大括号{}将相应的0,1,2…括起来,以区分一般的数字:

    local newstr, n, err = ngx.re.sub("hello, 1234", "[0-9]", "${0}00")
        -- newstr == "hello, 100234"
        -- n == 1

如果想在replace字符串中显示$符号,可以用$进行转义(不要用反斜杠\$对美元符号进行转义,这种方法不会得到期望的结果):

    local newstr, n, err = ngx.re.sub("hello, 1234", "[0-9]", "$$")
        -- newstr == "hello, $234"
        -- n == 1

如果replace是一个函数,那么函数的参数是一个”match table”, 而这个”match table”与ngx.re.match中的返回值captures是一样的,replace这个函数根据”match table”产生用于替换的字符串。

    local func = function (m)
        return "[" .. m[0] .. "][" .. m[1] .. "]"
    end
    local newstr, n, err = ngx.re.sub("hello, 1234", "( [0-9] ) [0-9]", func, "x")
        -- newstr == "hello, [12][1]34"
        -- n == 1

注意:

通过函数形式返回的替换字符串中的美元符号$不再是特殊字符,而只是被看作一个普通字符。

 

ngx.re.gsub

语法:newstr, n, err = ngx.re.gsub(subject, regex, replace, options?)

该方法与ngx.re.sub是类似的,但是该方法进行的是全局替换。

看两个例子:

    local newstr, n, err = ngx.re.gsub("hello, world", "([a-z])[a-z]+", "[$0,$1]", "i")
    if newstr then
        -- newstr == "[hello,h], [world,w]"
        -- n == 2
    else
        ngx.log(ngx.ERR, "error: ", err)
        return
    end
    local func = function (m)
        return "[" .. m[0] .. "," .. m[1] .. "]"
    end
    local newstr, n, err = ngx.re.gsub("hello, world", "([a-z])[a-z]+", func, "i")
        -- newstr == "[hello,h], [world,w]"
        -- n == 2

【Lua教程】模块与包

模块类似于一个封装库,从 Lua 5.1 开始,Lua 加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。
Lua 的模块是由变量、函数等已知元素组成的 table,因此创建一个模块很简单,就是创建一个 table,然后把需要导出的常量、函数放入其中,最后返回这个 table 就行。以下为创建自定义模块 module.lua,文件代码格式如下:

-- 文件名为 module.lua
-- 定义一个名为 module 的模块
module = {}
-- 定义一个常量
module.constant = "这是一个常量"
-- 定义一个函数
function module.func1()
    io.write("这是一个公有函数!\n")
end
local function func2()
    print("这是一个私有函数!")
end
function module.func3()
    func2()
end
return module

由上可知,模块的结构就是一个 table 的结构,因此可以像操作调用 table 里的元素那样来操作调用模块里的常量或函数。
上面的 func2 声明为程序块的局部变量,即表示一个私有函数,因此是不能从外部访问模块里的这个私有函数,必须通过模块里的公有函数来调用.
Lua提供了一个名为require的函数用来加载模块。要加载一个模块,只需要简单地调用就可以了。例如:
require(“<模块名>”)
或者
require “<模块名>”
执行 require 后会返回一个由模块常量或函数组成的 table,并且还会定义一个包含该 table 的全局变量。

-- test_module.lua 文件
-- module 模块为上文提到到 module.lua
require("module")
print(module.constant)
module.func3()

以上代码执行结果为:
这是一个常量
这是一个私有函数!
或者给加载的模块定义一个别名变量,方便调用:

-- test_module2.lua 文件
-- module 模块为上文提到到 module.lua
-- 别名变量 m
local m = require("module")
print(m.constant)
m.func3()

以上代码执行结果为:
这是一个常量
这是一个私有函数!

【Lua教程】ngx_lua模块中共享内存字典项API

在NGX LUA WAF中使用到了共享内存来做两件事:
1)CC防御,单位时间内单IP针对单URL访问频次超过阈值时封禁。
2)Web攻击防御,单位时间内触发WAF规则超过阈值时封禁。
在ngx_lua模块中使用共享内存字典项相关API的前提条件是已经使用lua_shared_dict命令定义了一个字典项对象,该命令的具体用法为:
语法:lua_shared_dict <name> <size>
该命令主要是定义一块名为name的共享内存空间,内存大小为size。通过该命令定义的共享内存对象对于Nginx中所有worker进程都是可见的,当Nginx通过reload命令重启时,共享内存字典项会从新获取它的内容,当时当Nginx退出时,字典项的值将会丢失。

http {
    lua_shared_dict dogs 10m;
    server {
        location /set {
            content_by_lua '
                local dogs = ngx.shared.dogs
                dogs:set("Jim", 8)
                ngx.say("STORED")
            ';
        }
        location /get {
            content_by_lua '
                local dogs = ngx.shared.dogs
                ngx.say(dogs:get("Jim"))
            ';
        }
    }
}

输出结果是:
$ curl localhost/set
STORED
$ curl localhost/get
8

可以通过ngx.shared.DICT接口获取共享内存字典项对象:

语法:dict = ngx.shared.DICT
dict = ngx.shared[name_var]

其中,DICT和name_var表示的名称是一致的,比如上面例子中,dogs =ngx.shared.dogs 就是dict = ngx.shared.DICT的表达形式,也可以通过下面的方式达到同样的目的:
dogs = ngx.shared[“dogs”]

通过上面的API获取得到的共享内存字典项对象,具有如下相应的接口:

> ngx.shared.DICT.get
语法:value, flags = ngx.shared.DICT:get(key)
获取共享内存上key对应的值。如果key不存在,或者key已经过期,将会返回nil;如果出现错误,那么将会返回nil以及错误信息。
local cats = ngx.shared.cats
local value, flags = cats:get(“Marry”)
返回列表中的flags,是在ngx.shared.DICT.set方法中设置的值,默认值为0. 如果设置的flags为0,那么在这里flags的值将不会被返回。

> ngx.shared.DICT.get_stale
语法:value, flags, stale = ngx.shared.DICT:get_stale(key)
与get方法类似,区别在于该方法对于过期的key也会返回,第三个返回参数表明返回的key的值是否已经过期,true表示过期,false表示没有过期。

> ngx.shared.DICT.set
语法:success, err, forcible = ngx.shared.DICT:set(key, value, exptime?, flags?)
“无条件”地往共享内存上插入key-value对,这里讲的“无条件”指的是不管待插入的共享内存上是否已经存在相同的key。三个返回值的含义:
success:成功插入为true,插入失败为false
err:操作失败时的错误信息,可能类似”no memory”
forcible:true表明需要通过强制删除(LRU算法)共享内存上其他字典项来实现插入,false表明没有删除共享内存上的字典项来实现插入。
第三个参数exptime表明key的有效期时间,单位是秒(s),默认值为0,表明永远不会过期;flags参数是一个用户标志值,会在调用get方法时同时获取得到。
local cats = ngx.shared.cats
local succ, err, forcible = cats:set(“Marry”, “it is a nice cat!”)

> ngx.shared.DICT.safe_set
语法:ok, err = ngx.shared.DICT:safe_set(key, value, exptime?, flags?)
与set方法类似,区别在于不会在共享内存用完的情况下,通过强制删除(LRU算法)的方法实现插入。如果内存不足,会直接返回nil和err信息”no memory”
注意:
set和safe_set共同点是:如果待插入的key已经存在,那么key对应的原来的值会被新的value覆盖!

> ngx.shared.DICT.add
语法:success, err, forcible = ngx.shared.DICT:add(key, value, exptime?, flags?)
与set方法类似,与set方法区别在于不会插入重复的键(可以简单认为add方法是set方法的一个子方法),如果待插入的key已经存在,将会返回nil和和err=”exists”

> ngx.shared.DICT.safe_add
语法:ok, err = ngx.shared.DICT:safe_add(key, value, exptime?, flags?)
与safe_set方法类似,区别在于不会插入重复的键(可以简单认为safe_add方法是safe_set方法的一个子方法),如果待插入的key已经存在,将会返回nil和和err=”exists”

> ngx.shared.DICT.replace
语法:success, err, forcible = ngx.shared.DICT:replace(key, value, exptime?, flags?)
与set方法类似,区别在于只对已经存在的key进行操作(可以简单认为replace方法是set方法的一个子方法),如果待插入的key在字典上不存在,将会返回nil和错误信息”not found”

> ngx.shared.DICT.delete
语法:ngx.shared.DICT:delete(key)
无条件删除指定的key-value对,其等价于
ngx.shared.DICT:set(key, nil)

> ngx.shared.DICT.incr
语法:newval, err = ngx.shared.DICT:incr(key, value)
对key对应的值进行增量操作,增量值是value,其中value的值可以是一个正数,0,也可以是一个负数。value必须是一个Lua类型中的number类型,否则将会返回nil和”not a number”;key必须是一个已经存在于共享内存中的key,否则将会返回nil和”not found”.

> ngx.shared.DICT.flush_all
语法:ngx.shared.DICT:flush_all()
清除字典上的所有字段,但不会真正释放掉字段所占用的内存,而仅仅是将每个字段标志为过期。

> ngx.shared.DICT.flush_expired
语法:flushed = ngx.shared.DICT:flush_expired(max_count?)
清除字典上过期的字段,max_count表明上限值,如果为0或者没有给出,表明需要清除所有过期的字段,返回值flushed是实际删除掉的过期字段的数目。
注意:
与flush_all方法的区别在于,该方法将会释放掉过期字段所占用的内存。

> ngx.shared.DICT.get_keys
语法:keys = ngx.shared.DICT:get_keys(max_count?)
从字典上获取字段列表,个数为max_count,如果为0或没有给出,表明不限定个数。默认值是1024个
注意:
强烈建议在调用该方法时,指定一个max_count参数,因为在keys数量很大的情况下,如果不指定max_count的值,可能会导致字典被锁定,从而阻塞试图访问字典的worker进程。

【Lua教程】string库和模式匹配

Lua 语言中字符串可以使用以下三种方式来表示:
单引号间的一串字符。
双引号间的一串字符。
[[和]]间的一串字符。
Lua中连接字符串使用..
Lua 提供了很多的方法来支持字符串的操作:
string.upper(argument):
字符串全部转为大写字母。
string.lower(argument):
字符串全部转为小写字母。
string.sub(s,i,j)
函数截取字符串s的从第i个字符到第j个字符之间的串。Lua中,字符串的第一个字符索引从1开始。你也可以使用负索引,负索引从字符串的结尾向前计数:-1指向最后一个字符,-2指向倒数第二个,以此类推。
string.len(“222”)
获取字符串长度。
string.char将整型数字转成字符并连接
string.char(97,98,99,100)
abcd
string.byte转换字符为整数值(可以指定某个字符,默认第一个字符)。
string.byte(“ABCD”,4)
68
string.byte(“ABCD”)
65
string.rep(string, n))返回字符串string的n个拷贝
string.rep(“abcd”,2)
abcdabcd
string.reverse(arg)字符串反转
string.reverse(“Lua”)
auL

lua string库里最强大的函数是那些模式匹配函数:find, match, gsub, gmatch。和其他脚本语言不同,lua既没有用POSIX的正则表达式,也没有用perl的正则表达式。原因是实现这些导致lua占用更多内存,而lua的初衷是小巧的,嵌入应用的语言。 lua用少于500行的代码实现了自己的一套模式匹配,虽然不如标准的正则表达式强(一般需要4000以上代码),但也足够强大。
Lua支持的匹配模式:
. 任意字符
%s 空白符
%p 标点
%c 控制字符
%d 数字
%x 十六进制数
%z 代表0的字符
%a 字母
%l 小写字母
%u 大写字母
%w 字母数字
字符类的大写形式代表相应集合的补集, 比如 %A 表示除了字母以外的字符集
另外,* + – 三个,作为通配符分别表示:
*: 匹配前面指定的 0 或多个同类字符, 尽可能匹配更长的符合条件的字串,也就是正则中的贪婪匹配。
+: 匹配前面指定的 1 或多个同类字符, 尽可能匹配更长的符合条件的字串
-: 匹配前面指定的 0 或多个同类字符, 尽可能匹配更短的符合条件的字串,也就是正则中的非贪婪匹配。
用%进行转义。’%%’代表’%’

string.find
print(string.find(“haha”, ‘ah’) ) —– 输出 2 3
注意: lua 里面数组或者字符串的字符, 其下标索引是从 1 开始, 不是 0
string.find 默认情况下返回两个值, 即查找到的子串的 起止下标, 如果不存在匹配返回 nil。
如果我们只想要 string.find 返回的第二个值, 可以使用 虚变量(即 下划线)
_, q=string.find(“haha”, ‘ah’)
print(q) —– 输出 3
另外在WAF中匹配上传文件的名字时用到:
start_pos,end_pos,capture,capture2 = string.find(v,’Content%-Disposition: form%-data; name=”(.+)”; filename=”(.*)”\r\n’)
因为-表示的是非贪婪匹配,所以都需要用%-转义。另外用小括号括起来表示捕捉匹配到的字符串。这里捕捉了两次,所以多返回了两个值。

Lua中实现分割字符串的函数:

local function explode ( _str,seperator )  
    local pos, arr = 0, {}  
        for st, sp in function() return string.find( _str, seperator, pos, true ) end do  
            table.insert( arr, string.sub( _str, pos, st-1 ) )  
            pos = sp + 1  
        end  
    table.insert( arr, string.sub( _str, pos ) )  
    return arr  
end

string.match
string.match和string.find类似。都是在指定的string中

date = "now is 2014/10/6 17:58"
d = string.match(date, "%d+/%d+/%d+")
print(d)
输出2014/10/6
a,b = string.match("hello+world","(%a*)%+(%a*)")
print(a.."--->"..b) 
输出hello--->world

string.gmatch
将返回一个迭代器,用于迭代所有出现在给定字符串中的匹配字符串。
例如WAF中获取文件扩展名的函数写法如下:

function split_string(filename)--获取上传文件扩展名
    local _t = {}
    for w in string.gmatch(filename,"([^'.']+)") do     --按照.分割字符串
        table.insert(_t,w) 
    end
    local length = table.getn(_t)
    return _t[length]--返回table的最后一个元素
end
print(split_string("image.jpg.php"))

输出php

string.gsub(字符串,匹配模式,替代字符串,替换次数为空代表全部替换)
将所有符合匹配模式的地方都替换成替代字符串。并返回替换后的字符串,以及替换次数。

s,c = string.gsub("Lua is cute", "%s", "|")
print(s.." count:"..c) --> Lua|is|cute count:2
s,c = string.gsub("Lua is cute", "%s", "|",1)
print(s) --> Lua|is cute

【Lua教程】文件上传

一般情况下我们在HTML中使用POST提交数据的时候,一般POST表单代码如下:

<form method="post"action=# >
         <input type="text" name="txt1" value="txt1"></br>
         <input type="text" name="txt2" value="txt2"></br>
         <input type="submit" value="Submit"">
</form>

可以看到POST请求如下:

POST /test.php HTTP/1.1
Host: 172.16.100.168
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://172.16.100.168/test.php
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 19

txt1=txt1&txt2=txt2

其中Content-Type为application/x-www-form-urlencoded。这意味着消息内容会经过URL编码。
获取到POST内容的lua代码如下:

ngx.req.read_body()
for k,v in pairs(ngx.req.get_post_args()) do
    ngx.say(k..":"..v)
end

默认情况下不会读取POST的内容,需要ngx.req.read_body()来开启。也可以在nginx配置中直接使用lua_need_request_body开启。
另外如果提交的数据Content-length大于client_body_buffer_size的设置,那么req.get_post_args()会获取不到数据。

最早的HTTP POST是不支持文件上传的,后来Content-Type的类型扩充了multipart/form-data用以支持向服务器发送二进制数据。
其实form表单在你不写enctype属性时,也默认为其添加了enctype属性值,默认值是enctype=”application/x- www-form-urlencoded”.
我们修改表单的内容如下:

<form method="post"action=# enctype="multipart/form-data">
         <input type="text" name="txt1" value="txt1"></br>
         <input type="text" name="txt2" value="txt2"></br>
         <input type="submit" value="Submit"">
</form>
<?php
echo $_POST['txt1'];
echo $_POST['txt2']
?>

POST请求数据包如下:

POST /test.php HTTP/1.1
Host: 172.16.100.168
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://172.16.100.168/test.php
Connection: close
Content-Type: multipart/form-data; boundary=---------------------------285751406325668
Content-Length: 246

-----------------------------285751406325668
Content-Disposition: form-data; name="txt1"

txt1
-----------------------------285751406325668
Content-Disposition: form-data; name="txt2"

txt2
-----------------------------285751406325668--

可以看到Content-Type变为multipart/form-data; 里面的内容是不经过URL编码的。
读取文件内容的代码如下:
ngx.req.read_body()
ngx.say(ngx.req.get_body_data())
然后返回结果如图:

123

ngx.req.get_body_data()
存在两种情况是会将body内容写入到文件中导致获取不到
1)body体大于设置的client_body_buffer_size(在32位系统中默认为8K,在64位系统中默认为16K)
2)client_body_in_file_only打开
例如上传一个800k的图片,打印的内容就是nil。
然后我们修改lua代码如下:

ngx.req.read_body()
if ngx.req.get_body_data() ~= nil then
    ngx.say(ngx.req.get_body_data())
else
    ngx.say(ngx.req.get_body_file())
end

然后上传800K的图片,返回结果如图:
123

返回了临时文件的图片路径。
然后我们再修改一下lua代码,读取临时文件的内容

ngx.req.read_body()
if ngx.req.get_body_data() ~= nil then
    ngx.say(ngx.req.get_body_data())
else
    RulePath = ngx.req.get_body_file()
    ngx.say(RulePath)
    file = io.open(RulePath,"r")
    if file==nil then
        return
    end
    file:seek("set")--设置从文件头开始读取
    --ngx.say(file:read("*a"))--从当前位置读取整个文件
    ngx.say(file:read(1000))--从当前位置读取1000个字节
    file:close()
end

这样就可以读取到了POST的内容。

【Lua教程】lua安装配置

1)
LuaJIT即采用C语言写的Lua的解释器的代码。

wget http://luajit.org/download/LuaJIT-2.0.3.tar.gz
tar xf LuaJIT-2.0.3.tar.gz
cd LuaJIT-2.0.3
make && make install
ln -sv /usr/local/lib/libluajit-5.1.so.2.0.3 /lib64/libluajit-5.1.so.2

下载ngx_devel_kit并解压
2)

wget https://github.com/simpl/ngx_devel_kit/archive/v0.2.19.tar.gz
tar zxvf v0.2.19.tar.gz

下载ngx_lua并解压
3)

wget https://github.com/chaoslawful/lua-nginx-module/archive/v0.9.6.tar.gz
tar zxvf v0.9.6.tar.gz

4)
重新编译安装nginx

./configure  --with-pcre --add-module=/opt/lua-nginx-module-0.9.6 --add-module=/opt/ngx_devel_kit-0.2.19 
make && make install