Upload
ho-kim
View
306
Download
3
Embed Size (px)
Citation preview
OpenResty/Lua高级编程技巧技术部 - kim
Debug
逻辑运算符 and、or 和 not 是经常隐藏 bug 的地方,比如:
if (type(t) == 'table' and t.x == 'abc')
就算 t 不是 table 类型,那么 lua 的短路求值也不会对 t
进行求值,所以不会引发运行时错误
用 xpcall debug
function errorFunc()
local a = 20
print(a[10])
end
function errorHandle()
print(debug.traceback())
end
if xpcall(errorFunc,errorHandle) then
print("This is OK.")
else
print("This is error.")
end
ngx.now() :获取当前时间,包括毫秒数
os.clock() :返回CPU时间的描述,通常用于计算一段代码的执行效率
os.data 日期对象函数:
print(os.date("%Y-%m-%d")) --输出2012-04-05
简单的写log函数(排重)
local logsUniquify = {} ——是的,这是个全局数组
function log(message, level)
if ngx and type(ngx.log) == 'function' then
local level = level or ngx.EMERG
if not logsUniquify[level .. message] then
ngx.log(level, message)
logsUniquify[level .. message] = true
end
end
return nil
end
以下的debug方式我们一般不用
1)直接抛出错误:error(“抛出个error!”)
2)assert(io.read("*number"), "invalid input")
3)Lua提供了错误处理函数pcall: r, msg = pcall(foo)
4)还可以用 xpcall
一般情况下我们是直接面对 error log 来开发 tailf
/home/openresty/nginx/logs/error.log
Safety
URL参数转义
urlencode : ngx.escape_uri
urldecode : ngx.unescape_uri
local act = ngx.var.arg_act and
ngx.unescape_uri(ngx.var.arg_act) or ngx.var.act
过滤/指定 remote_addr
ngx.var.remote_addr == "132.5.72.3"
获取用户ip
ClienIP = ngx.req.get_headers()["X-Real-IP"]
if ClientIP == nil then
ClientIP = ngx.req.get_headers()["x_forworded_for"]
end
if ClientIP == nil then
ClientIP = ngx.var.remote_addr
end
Sql Injection
local name = ngx.unescape_uri(ngx.var.arg_name)
local quoted_name = ngx.quote_sql_str(name)
local sql = "select * from users where name = " ..
quoted_name
htmlspecialchars的实现
function htmlspecialchars(str)
local rs = str or nil
if rs and type(rs) == 'string' then
rs = string.gsub(rs, '&', '&')
rs = string.gsub(rs, '"', '"')
rs = string.gsub(rs, "'", ''')
rs = string.gsub(rs, '<', '<')
rs = string.gsub(rs, '>', '>')
end
return rs
end
htmlentities 实现
过滤特殊字符
local illegal = {
["-"] = true, ["_"] = false, ["."] = false, ["!"] = true, [":"] = true, ["@"] = true,
["&"] = true, ["~"] = true, ["*"] = true, ["'"] = true, ['"'] = true, ["="] = false,
["("] = true, [")"] = true, ["["] = true, ["]"] = true, ["{"] = true, ["}"] = true,
["+"] = true, ["$"] = false, [","] = false, [";"] = true, ["?"] = true, ["%"] = true,
["^"] = true, ["/"] = true, ["|"] = true, ["#"] = true, ['-'] = true, ["_"] = false, ["。"] = true,
['!'] = true, [':'] = true, ['@'] = true, ['&'] = true, ['~'] = true, ['*'] = true, ['‘'] = true,
['’'] = true, ['“'] = true, ['”'] = true, ['('] = true, [')'] = true, ['['] = true, [']'] = true,
['{'] = true, ['}'] = true, ['+'] = true, ['¥'] = true, [','] = true, [';'] = true, ['?'] = true,
['%'] = true, ['^'] = true, ['/'] = true, ['……'] = true,
}
local filter = function(c)
if illegal[c] then
return ' '
end
return c
end
cnt = string.gsub(cnt, '([^a-zA-Z0-9])', filter)
Magics Convert
function escapeMagic(s)
local rs
if type(s) == 'string' then
rs = (s:gsub(‘[%-%.%+%[%]%(%)%$%^%%%?%*]',
'%%%1'):gsub('%z', '%%z'))
end
return rs
end
过滤 v 中的疑似网址特征
local spos = find(v, 'www') or find(v, '%.')
or find(v, '。') or find(v, '点')
local substring = ''
if spos then
substring = string.sub(s, spos, epos)
substring = g.escapeMagic(substring)
v = string.gsub(s, substring, '')
end
Build Queries
bind['so_refer_key'] = g.trim(vinfo['so_refer_key'])
bind['request_from'] = 'info_relate'
bind['wt'] = 'json'
bind['qt'] = 'standard'
bind['56_version'] = ngx.var.arg_rvc
bind['so_refer_key'] = ngx.var.arg_so_refer_key or ''
local param = neturl.buildQuery(bind)
local host = 'related_video.solr.56.com' --
local port = '49715'
local uri = '/solrRelateVideo/select?' .. param
if ngx.var.arg_dg == 'ml' then
print(uri)
end
xssfilter
— Filter the XSS attack
local xssfilter = require("lib.xssfilter")
local xss_filter = xssfilter.new()
data['title'] = g.htmlentities(xss_filter:filter(data['title']))
Sandbox
—使用 closure 创建 sandbox (安全运行环境),比如为 io.open 提供权限控制的功能:
do
local oldOpen = io.open
local checkAccess = function (file, mode)
-- check if current user with 'mode' can access the 'file'
end
io.open = function (file, mode)
if checkAccess(file, mode) then
return oldOpen(file, mode)
else
return nil, "access, denied"
end
end
end
Table
Metatable
t = {}
print(getmetatable(t))
-- 输出为 nil,table 创建时默认没有元表
--任何 table 都可以作为任何值的元表,在Lua代码中,只能设置table的元表
Table Sorting
t = {5,1,3,6,3,2}
t2 = table.sort(t, function(a1, a2)
return (a1 > a2) end) for i, v in pairs(t) do print(v)
end
unpack(1)
— 该函数将接收数组作为参数,并从下标1开始返回该数组的所有元素:
string.find(unpack{"hello","ll"}) --等同于string.find("hello","ll")
unpack(2)
— 经典应用 redis 的 hmget :
local tmpVids = {}
for i,v in ipairs(res) do
table.insert(tmpVids, tostring(v['video_id']))
end
local vextinfos = {}
local tbTimes = rds:hmget(rhkey, unpack(tmpVids))
unpack(3)
— redis 添加多个有序集元素的泛型调用:
rds:zadd(rzkey, unpack(zsetData))
Table Size(real)
function length(tbl)
local count = 0
if type(tbl) == 'table' then
for _ in pairs(tbl) do count = count + 1 end
end
return count
end
Table Flip
function array_flip(tbl)
local rs = tbl
if type(tbl) == 'table' then
rs = {}
for k,v in pairs(tbl) do
rs[v] = k
end
end
return rs
end
Random Pick
function array_rand(tbl, m)
local rs
if type(tbl) == 'table' and next(tbl) ~= nil then
rs = {}
local order = {}
local n = #tbl
for i = 1, n do
order[i] = {rnd = math.random(), idx = i}
end
table.sort(order, function(a,b) return a.rnd < b.rnd end)
for i = 1, m do
if order[i] then rs[i] = order[i].idx end
end
end
return rs
end
Intersect by Key
function array_intersect_key(t1, t2)
local rs = t1
if type(t1) == 'table' and type(t2) == 'table' then
rs = {}
for k,v in pairs(t1) do
if t2[k] ~= nil then rs[k] = v end
end
end
return rs
end
Deep Compare
function deepcompare(t1, t2)
local ty1 = type(t1)
local ty2 = type(t2)
if ty1 ~= ty2 then return false end
-- non-table types can be directly compared
if ty1 ~= 'table' and ty2 ~= 'table' then return t1 == t2 end
-- as well as tables which have the metamethod __eq
for k1,v1 in pairs(t1) do
local v2 = t2[k1]
if v2 == nil or not deepcompare(v1,v2) then return false end
end
for k2,v2 in pairs(t2) do
local v1 = t1[k2]
if v1 == nil or not deepcompare(v1,v2) then return false end
end
return true
end
模块初始化与 __index
—定义模块实例化的方法:
--[[ init module ]]
module(...)
_VERSION = '1.0.0'
--[[ indexed by current module env. ]]
local mt = {__index = _M};
--[[
instantiation
@return table
]]
function new(self)
return setmetatable({}, mt);
end
Default Value
function setDefault(table, default)
local mt = {__index = function() return default end }
setmetatable(table,mt)
end
tab = {x = 10, y = 20}
setDefault(tab,0)
print(tab.x,tab.z) --10 0
Write Prohibited
setmetatable(_M, {
__newindex = function (table, key, val)
ngx.log(ngx.EMERG, 'attempt to write to undeclared variable "' ..
key .. '" in ' .. table._NAME);
end
})
_M.unexists = "hello" -- 报错:’attempt to write to undeclared variable
"unexists" in $module'
Table Traversal
for k,v in g.pairsByKeys(idDel) do
print(k .. ':' .. v .. "\n")
end
function pairsByKeys(tb, f)
local a = {}
for n in pairs(tb) do table.insert(a, n) end
table.sort(a, f)
local i = 0 -- iterator variable
local iter = function() -- iterator function
i = i + 1
if a[i] == nil then return nil
else return a[i], tb[a[i]]
end
end
return iter
end
Sort by Value
function sortAssoc(tb, order, limit)
local rs = nil
if type(tb) == 'table' then
rs = {}
local tmp = {}
for k,v in pairs(tb) do
table.insert(tmp, {key = k, val = tonumber(v)})
end
if next(tmp) ~= nil then
if order and order == 'DESC' then
table.sort(tmp, function(a, b) return b.val < a.val end)
else
table.sort(tmp, function(a, b) return b.val > a.val end)
end
end
for i,v in ipairs(tmp) do
table.insert(rs, {v['key'], v['val']})
if limit and (i >= limit) then break end
end
end
return rs
end
Multi-Array Sorting
local data = {
{id=3, data=421}, {id=23, data=321}, {id=3, data=422}, {id=5, data=321},
{id=1, data=4214}, {id=3, data=44}
}
table.sort(data, function(a, b)
if a['id'] < b['id'] then
return true
elseif a['id'] == b['id'] then
if a['data'] < b['data'] then
return true
else
return false
end
else
return false
end
end)
Metatable Prohibited
mt = {}
mt.__metatable = "can not read/write"
setmetatable(s1, mt)
print(getmetatable(s1)) -- "cannot read/write"
setmetatable(s1, {}) -- cannot change protected metatable
Metamethod
Lua中提供的元表是用于帮助 Lua 数据变量完成某些非预定义功能的个性化行为,如两个table的相加。
假设a和b都是table,通过元表可以定义如何计算表达式a+b。
当Lua试图将两个table相加时,它会先检查两者之一是否有元表,然后检查该元表中是否存在__add字段,如果有,就调用该字段对应的值。
这个值就是所谓的“元方法”,这个函数用于计算table的和。
Function
Essence
Lua 将所有独立的程序块视为一个匿名函数的函数体,并且该匿名函数还具有可变长实参
Returning Values
1. 模块、函数可以考虑 “有效值|nil,错误信息” 这样的双值返回,resty 中的大部分模块都是这样写的
2. function 尽量返回单值,特别是业务逻辑的函数,多值返回虽然是 lua 的特色,但是不利于移植
3. 空值或者无效值尽量用 nil 来表示,而不要用空table
、''、0、false 等等来表示,只有 nil 才是真正意义上的“
无效值”
Proper Tail Calls
在Lua中支持这样一种函数调用的优化,即不耗费任何栈空间的“尾调用消除”。我们可以将这种函数调用方式视为goto语句,如:
function f(x) return g(x) end
形参 vs 实参
1. 实参多于形参,多出的部分被忽略
2. 形参多于实参,没被初始化的形参的缺省值为nil
function foo(a, b, c)
print(a, b, c)
end
foo('a', 'b', 'c', 'd') -- 'd' 被忽略
foo('a') -- 这时形参 b 和 c 都是 nil
Performace
Local Sharings
http {
lua_shared_dict lcache 64m;
...
}
dict.lua 模块介绍
http://wiki.nginx.org/HttpLuaModule#ngx.shared.DICT
Fastest Trim
function trim(s)
if type(s) == 'string' then
local match = string.match
return match(s,'^()%s*$') and '' or match(s,'^%s*(.*%S)')
end
return s
end
Memcached
1. https://github.com/agentzh/lua-resty-memcached
2. 自封装 memd.lua 模块介绍
Mysql
1. https://github.com/agentzh/lua-resty-mysql
2. 自封装 mysql.lua 模块介绍
Redis
https://github.com/agentzh/lua-resty-redis
自封装 redis.lua 模块介绍:
1)让 lua-resty-redis 的返回值变得规范
2)lazy connecting
3)添加 debug 功能和 log 功能
4)实现一些特殊的功能,比如 hclear
5)close 方法默认设置 set_keepalive
Block
不需要在程序起始处声明所有局部(local)变量,在需要的时候才声明变量其实是好习惯,缩短变量的作用域在任何时候都是好的。
List
list = nil
for v in values do
list = {next = list, value = v}
end
local l = list
while l do
print(l.value)
l = l.next
end
跳转实例
=== TEST 13: rewrite args (not break cycle by default)
--- config
location /bar {
echo "bar: $uri?$args";
}
location /foo {
#set $args 'hello';
rewrite_by_lua '
ngx.req.set_uri_args("hello")
ngx.req.set_uri("/bar", true)
';
echo "foo: $uri?$args";
}
--- request
GET /foo?world
--- response_body
bar: /bar?hello
How to “continue”?
local bContinue
for k, v in pairs(tbl) do
bContinue = false
......
if not bContinue then
......
if needToContinue then
bContinue = true
end
end
if not bContinue then
-- continue to do things
end
end
elseif vs else if
if ... then
else
if ... then
end
end
if ... then
elseif ... then
end
“select”
count = select(2, string.gsub(str," "," "))
-- string.gsub 第二个返回值是空格替换次数,select则选择该返回值,即 str 中空格的数量
文件/IO
1. io.write("hello","world") --写出的内容为helloworld
2. io.read("*all")会读取当前输入文件的所有内容
3. io.read(0)是一种特殊的情况,用于检查是否到达了文件的末尾。如果没有到达,返回空字符串,否则nil
Serialize
function serialize(o)
if type(o) == "number" then
io.write(o)
elseif type(o) == "string" then
--string.format函数的"%q"参数可以转义字符串中的元字符。
io.write(string.format("%q",o))
elseif type(o) == "table" then
io.write("{\n")
--迭代table中的各个元素,同时递归的写出各个字段的value。
--由此可以看出,这个简单例子可以支持嵌套的table。
for k,v in pairs(o) do
--这样做是为了防止k中包含非法的Lua标识符。
io.write(" ["); serialize(k); io.write("] = ")
serialize(v)
io.write(",\n")
end
io.write("}\n")
else
error("cannot serialize a " .. type(o))
end
end
Size and Seek
1. local f = assert(io.open(filename,"r"))
2. local current = f:seek() --获取当前位置
3. local size = f:seek("end") --获取文件大小
4. f:seek("set",current) --恢复原有的当前位置
读取文件优化
下面是Shell中wc命令的一个简单实现:
local BUFSIZE = 8192
local f = io.input(arg[1]) --打开输入文件
local cc, lc, wc, = 0, 0, 0 --分别计数字符、行和单词
while true do
local lines,rest = f:read(BUFSIZE,"*line")
if not lines then
break
end
if rest then
lines = lines .. rest .. "\n"
end
cc = cc + #lines
--计算单词数量
local _, t = string.gsub(lines."%S+","")
wc = wc + t
--计算行数
_,t = string.gsub(line,"\n","\n")
lc = lc + t
end
print(lc,wc,cc)
Socket
Socket Connect
--用 ngx.socket.tcp 才能百分百支持 nonblocking
local sock = ngx.socket.tcp()
sock:settimeout(1000) -- one second
local ok, err = sock:connect("127.0.0.1", 11211)
local bytes, err = sock:send("flush_all\r\n")
if not bytes then
ngx.say("failed to send query: ", err)
return
end
local line, err = sock:receive()
if not line then
ngx.say("failed to receive a line: ", err)
return
end
ngx.say("result: ", line)
local ok, err = sock:setkeepalive(60000, 500)
if not ok then
ngx.say("failed to put the connection into pool with pool capacity 500 "
.. "and maximal idle time 60 sec")
return
end
Settimeout
sock:settimeout(1000)
ok, err = tcpsock:connect(host, port, options_table?)
sock:settimeout(1000)
bytes, err = tcpsock:send(data)
sock:settimeout(1000)
data, err, partial = tcpsock:receive(size) —读timeout是不会close掉连接的
local reader = sock:receiveuntil("\r\n--abcedhb")
sock:settimeout(1000)
while true do
……
end
ok, err = tcpsock:close()
Coroutine
协程示例
协程的经典示例:“生产者-消费者”问题:
--消费者
function receive()
local status, value = coroutine.resume(producer) --消费
return value
end
--生产者
function send(x)
coroutine.yield(x) --挂起
end
--协程
producer = coroutine.create(
function()
while true do
local x = io.read() --产生新值
send(x)
end
end)
cosocket 读取大数据
local sock, err = ngx.req.socket()
if not sock then
ngx.say("failed to get request socket: ", err)
return
end
sock:settimeout(10000) -- 10 sec timeout
while true do
local chunk, err = sock:receive(4096)
if not chunk then
if err == "closed" then
break
end
ngx.say("faile to read: ", err)
return
end
process_chunk(chunk)
end
Some Modules
Serialize Module
—-使用 serialize 模块的函数对 PHP序列化的字符串进行反序列化,或者对lua
的 table 进行PHP序列化
require("lib.serialize")
local srlz = serialize
local unsrlz = unserialize
local luatable = unsrlz(php_serialized_string)
local php_serialized_string = serialize(luatable)
luarocks
查看 luajit 版本:
ls /home/openresty/luajit/bin/luajit-2.0.2
wget http://luarocks.org/releases/luarocks-2.0.13.tar.gz
tar -xzvf luarocks-2.0.13.tar.gz
cd luarocks-2.0.13/
./configure --prefix=/usr/local/openresty/luajit \
--with-lua=/usr/local/openresty/luajit/ \
--lua-suffix=jit-2.0.2 \
--with-lua-include=/usr/local/openresty/luajit/include/luajit-2.0/
make build
sudo make install
PS:--lua-suffix 就是上面 luajit 的后缀
lua-snappy
1. 从 https://github.com/forhappy/lua-snappy 下载并解压。
2. CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/home/openresty/luajit/include/luajit-2.0
export CPLUS_INCLUDE_PATH
3. 进入 lua-snappy-master 里面:
cd /lua-snappy-master ./sysconfig
vi Makefile
4. 加上 luajit 路径并加入到编译命令中:
CPPFLAGS=-fPIC -shared -Wall -g -O2 -I/home/openresty/luajit/include/luajit-2.0/
$(CC) $(CPPFLAGS) -o $@ -c $^
5. 编译安装:
make
6. 把 so 放到 lib 里面: cp snappy.so /home/openresty/lualib/
FAQ