🔧 build: 调整 web 后端 库
parent
7540100b4b
commit
49fb2abd85
@ -0,0 +1,808 @@
|
|||||||
|
-------------------------------------------------------------------------------
|
||||||
|
-- tinyyaml - YAML subset parser
|
||||||
|
-- https://github.com/peposso/lua-tinyyaml
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
local table = table
|
||||||
|
local string = string
|
||||||
|
local schar = string.char
|
||||||
|
local ssub, gsub = string.sub, string.gsub
|
||||||
|
local sfind, smatch = string.find, string.match
|
||||||
|
local tinsert, tremove = table.insert, table.remove
|
||||||
|
local setmetatable = setmetatable
|
||||||
|
local pairs = pairs
|
||||||
|
local type = type
|
||||||
|
local tonumber = tonumber
|
||||||
|
local math = math
|
||||||
|
local getmetatable = getmetatable
|
||||||
|
local error = error
|
||||||
|
|
||||||
|
local UNESCAPES = {
|
||||||
|
['0'] = "\x00",
|
||||||
|
z = "\x00",
|
||||||
|
N = "\x85",
|
||||||
|
a = "\x07",
|
||||||
|
b = "\x08",
|
||||||
|
t = "\x09",
|
||||||
|
n = "\x0a",
|
||||||
|
v = "\x0b",
|
||||||
|
f = "\x0c",
|
||||||
|
r = "\x0d",
|
||||||
|
e = "\x1b",
|
||||||
|
['\\'] = '\\'
|
||||||
|
};
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
-- utils
|
||||||
|
local function select(list, pred)
|
||||||
|
local selected = {}
|
||||||
|
for i = 0, #list do
|
||||||
|
local v = list[i]
|
||||||
|
if v and pred(v, i) then
|
||||||
|
tinsert(selected, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return selected
|
||||||
|
end
|
||||||
|
|
||||||
|
local function startswith(haystack, needle)
|
||||||
|
return ssub(haystack, 1, #needle) == needle
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ltrim(str)
|
||||||
|
return smatch(str, "^%s*(.-)$")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function rtrim(str)
|
||||||
|
return smatch(str, "^(.-)%s*$")
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
-- Implementation.
|
||||||
|
--
|
||||||
|
local class = {
|
||||||
|
__meta = {}
|
||||||
|
}
|
||||||
|
function class.__meta.__call(cls, ...)
|
||||||
|
local self = setmetatable({}, cls)
|
||||||
|
if cls.__init then
|
||||||
|
cls.__init(self, ...)
|
||||||
|
end
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function class.def(base, typ, cls)
|
||||||
|
base = base or class
|
||||||
|
local mt = {
|
||||||
|
__metatable = base,
|
||||||
|
__index = base
|
||||||
|
}
|
||||||
|
for k, v in pairs(base.__meta) do
|
||||||
|
mt[k] = v
|
||||||
|
end
|
||||||
|
cls = setmetatable(cls or {}, mt)
|
||||||
|
cls.__index = cls
|
||||||
|
cls.__metatable = cls
|
||||||
|
cls.__type = typ
|
||||||
|
cls.__meta = mt
|
||||||
|
return cls
|
||||||
|
end
|
||||||
|
|
||||||
|
local types = {
|
||||||
|
null = class:def('null'),
|
||||||
|
map = class:def('map'),
|
||||||
|
omap = class:def('omap'),
|
||||||
|
pairs = class:def('pairs'),
|
||||||
|
set = class:def('set'),
|
||||||
|
seq = class:def('seq'),
|
||||||
|
timestamp = class:def('timestamp')
|
||||||
|
}
|
||||||
|
|
||||||
|
local Null = types.null
|
||||||
|
function Null.__tostring()
|
||||||
|
return 'yaml.null'
|
||||||
|
end
|
||||||
|
function Null.isnull(v)
|
||||||
|
if v == nil then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if type(v) == 'table' and getmetatable(v) == Null then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local null = Null()
|
||||||
|
|
||||||
|
function types.timestamp:__init(y, m, d, h, i, s, f, z)
|
||||||
|
self.year = tonumber(y)
|
||||||
|
self.month = tonumber(m)
|
||||||
|
self.day = tonumber(d)
|
||||||
|
self.hour = tonumber(h or 0)
|
||||||
|
self.minute = tonumber(i or 0)
|
||||||
|
self.second = tonumber(s or 0)
|
||||||
|
if type(f) == 'string' and sfind(f, '^%d+$') then
|
||||||
|
self.fraction = tonumber(f) * math.pow(10, 3 - #f)
|
||||||
|
elseif f then
|
||||||
|
self.fraction = f
|
||||||
|
else
|
||||||
|
self.fraction = 0
|
||||||
|
end
|
||||||
|
self.timezone = z
|
||||||
|
end
|
||||||
|
|
||||||
|
function types.timestamp:__tostring()
|
||||||
|
return string.format('%04d-%02d-%02dT%02d:%02d:%02d.%03d%s', self.year, self.month, self.day, self.hour,
|
||||||
|
self.minute, self.second, self.fraction, self:gettz())
|
||||||
|
end
|
||||||
|
|
||||||
|
function types.timestamp:gettz()
|
||||||
|
if not self.timezone then
|
||||||
|
return ''
|
||||||
|
end
|
||||||
|
if self.timezone == 0 then
|
||||||
|
return 'Z'
|
||||||
|
end
|
||||||
|
local sign = self.timezone > 0
|
||||||
|
local z = sign and self.timezone or -self.timezone
|
||||||
|
local zh = math.floor(z)
|
||||||
|
local zi = (z - zh) * 60
|
||||||
|
return string.format('%s%02d:%02d', sign and '+' or '-', zh, zi)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function countindent(line)
|
||||||
|
local _, j = sfind(line, '^%s+')
|
||||||
|
if not j then
|
||||||
|
return 0, line
|
||||||
|
end
|
||||||
|
return j, ssub(line, j + 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parsestring(line, stopper)
|
||||||
|
stopper = stopper or ''
|
||||||
|
local q = ssub(line, 1, 1)
|
||||||
|
if q == ' ' or q == '\t' then
|
||||||
|
return parsestring(ssub(line, 2))
|
||||||
|
end
|
||||||
|
if q == "'" then
|
||||||
|
local i = sfind(line, "'", 2, true)
|
||||||
|
if not i then
|
||||||
|
return nil, line
|
||||||
|
end
|
||||||
|
return ssub(line, 2, i - 1), ssub(line, i + 1)
|
||||||
|
end
|
||||||
|
if q == '"' then
|
||||||
|
local i, buf = 2, ''
|
||||||
|
while i < #line do
|
||||||
|
local c = ssub(line, i, i)
|
||||||
|
if c == '\\' then
|
||||||
|
local n = ssub(line, i + 1, i + 1)
|
||||||
|
if UNESCAPES[n] ~= nil then
|
||||||
|
buf = buf .. UNESCAPES[n]
|
||||||
|
elseif n == 'x' then
|
||||||
|
local h = ssub(i + 2, i + 3)
|
||||||
|
if sfind(h, '^[0-9a-fA-F]$') then
|
||||||
|
buf = buf .. schar(tonumber(h, 16))
|
||||||
|
i = i + 2
|
||||||
|
else
|
||||||
|
buf = buf .. 'x'
|
||||||
|
end
|
||||||
|
else
|
||||||
|
buf = buf .. n
|
||||||
|
end
|
||||||
|
i = i + 1
|
||||||
|
elseif c == q then
|
||||||
|
break
|
||||||
|
else
|
||||||
|
buf = buf .. c
|
||||||
|
end
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
return buf, ssub(line, i + 1)
|
||||||
|
end
|
||||||
|
if q == '{' or q == '[' then -- flow style
|
||||||
|
return nil, line
|
||||||
|
end
|
||||||
|
if q == '|' or q == '>' then -- block
|
||||||
|
return nil, line
|
||||||
|
end
|
||||||
|
if q == '-' or q == ':' then
|
||||||
|
if ssub(line, 2, 2) == ' ' or #line == 1 then
|
||||||
|
return nil, line
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local buf = ''
|
||||||
|
while #line > 0 do
|
||||||
|
local c = ssub(line, 1, 1)
|
||||||
|
if sfind(stopper, c, 1, true) then
|
||||||
|
break
|
||||||
|
elseif c == ':' and (ssub(line, 2, 2) == ' ' or #line == 1) then
|
||||||
|
break
|
||||||
|
elseif c == '#' and (ssub(buf, #buf, #buf) == ' ') then
|
||||||
|
break
|
||||||
|
else
|
||||||
|
buf = buf .. c
|
||||||
|
end
|
||||||
|
line = ssub(line, 2)
|
||||||
|
end
|
||||||
|
return rtrim(buf), line
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isemptyline(line)
|
||||||
|
return line == '' or sfind(line, '^%s*$') or sfind(line, '^%s*#')
|
||||||
|
end
|
||||||
|
|
||||||
|
local function equalsline(line, needle)
|
||||||
|
return startswith(line, needle) and isemptyline(ssub(line, #needle + 1))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function checkdupekey(map, key)
|
||||||
|
if map[key] ~= nil then
|
||||||
|
-- print("found a duplicate key '"..key.."' in line: "..line)
|
||||||
|
local suffix = 1
|
||||||
|
while map[key .. '_' .. suffix] do
|
||||||
|
suffix = suffix + 1
|
||||||
|
end
|
||||||
|
key = key .. '_' .. suffix
|
||||||
|
end
|
||||||
|
return key
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parseflowstyle(line, lines)
|
||||||
|
local stack = {}
|
||||||
|
while true do
|
||||||
|
if #line == 0 then
|
||||||
|
if #lines == 0 then
|
||||||
|
break
|
||||||
|
else
|
||||||
|
line = tremove(lines, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local c = ssub(line, 1, 1)
|
||||||
|
if c == '#' then
|
||||||
|
line = ''
|
||||||
|
elseif c == ' ' or c == '\t' or c == '\r' or c == '\n' then
|
||||||
|
line = ssub(line, 2)
|
||||||
|
elseif c == '{' or c == '[' then
|
||||||
|
tinsert(stack, {
|
||||||
|
v = {},
|
||||||
|
t = c
|
||||||
|
})
|
||||||
|
line = ssub(line, 2)
|
||||||
|
elseif c == ':' then
|
||||||
|
local s = tremove(stack)
|
||||||
|
tinsert(stack, {
|
||||||
|
v = s.v,
|
||||||
|
t = ':'
|
||||||
|
})
|
||||||
|
line = ssub(line, 2)
|
||||||
|
elseif c == ',' then
|
||||||
|
local value = tremove(stack)
|
||||||
|
if value.t == ':' or value.t == '{' or value.t == '[' then
|
||||||
|
error()
|
||||||
|
end
|
||||||
|
if stack[#stack].t == ':' then
|
||||||
|
-- map
|
||||||
|
local key = tremove(stack)
|
||||||
|
key.v = checkdupekey(stack[#stack].v, key.v)
|
||||||
|
stack[#stack].v[key.v] = value.v
|
||||||
|
elseif stack[#stack].t == '{' then
|
||||||
|
-- set
|
||||||
|
stack[#stack].v[value.v] = true
|
||||||
|
elseif stack[#stack].t == '[' then
|
||||||
|
-- seq
|
||||||
|
tinsert(stack[#stack].v, value.v)
|
||||||
|
end
|
||||||
|
line = ssub(line, 2)
|
||||||
|
elseif c == '}' then
|
||||||
|
if stack[#stack].t == '{' then
|
||||||
|
if #stack == 1 then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
stack[#stack].t = '}'
|
||||||
|
line = ssub(line, 2)
|
||||||
|
else
|
||||||
|
line = ',' .. line
|
||||||
|
end
|
||||||
|
elseif c == ']' then
|
||||||
|
if stack[#stack].t == '[' then
|
||||||
|
if #stack == 1 then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
stack[#stack].t = ']'
|
||||||
|
line = ssub(line, 2)
|
||||||
|
else
|
||||||
|
line = ',' .. line
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local s, rest = parsestring(line, ',{}[]')
|
||||||
|
if not s then
|
||||||
|
error('invalid flowstyle line: ' .. line)
|
||||||
|
end
|
||||||
|
tinsert(stack, {
|
||||||
|
v = s,
|
||||||
|
t = 's'
|
||||||
|
})
|
||||||
|
line = rest
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return stack[1].v, line
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parseblockstylestring(line, lines, indent)
|
||||||
|
if #lines == 0 then
|
||||||
|
error("failed to find multi-line scalar content")
|
||||||
|
end
|
||||||
|
local s = {}
|
||||||
|
local firstindent = -1
|
||||||
|
local endline = -1
|
||||||
|
for i = 1, #lines do
|
||||||
|
local ln = lines[i]
|
||||||
|
local idt = countindent(ln)
|
||||||
|
if idt <= indent then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
if ln == '' then
|
||||||
|
tinsert(s, '')
|
||||||
|
else
|
||||||
|
if firstindent == -1 then
|
||||||
|
firstindent = idt
|
||||||
|
elseif idt < firstindent then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
tinsert(s, ssub(ln, firstindent + 1))
|
||||||
|
end
|
||||||
|
endline = i
|
||||||
|
end
|
||||||
|
|
||||||
|
local striptrailing = true
|
||||||
|
local sep = '\n'
|
||||||
|
local newlineatend = true
|
||||||
|
if line == '|' then
|
||||||
|
striptrailing = true
|
||||||
|
sep = '\n'
|
||||||
|
newlineatend = true
|
||||||
|
elseif line == '|+' then
|
||||||
|
striptrailing = false
|
||||||
|
sep = '\n'
|
||||||
|
newlineatend = true
|
||||||
|
elseif line == '|-' then
|
||||||
|
striptrailing = true
|
||||||
|
sep = '\n'
|
||||||
|
newlineatend = false
|
||||||
|
elseif line == '>' then
|
||||||
|
striptrailing = true
|
||||||
|
sep = ' '
|
||||||
|
newlineatend = true
|
||||||
|
elseif line == '>+' then
|
||||||
|
striptrailing = false
|
||||||
|
sep = ' '
|
||||||
|
newlineatend = true
|
||||||
|
elseif line == '>-' then
|
||||||
|
striptrailing = true
|
||||||
|
sep = ' '
|
||||||
|
newlineatend = false
|
||||||
|
else
|
||||||
|
error('invalid blockstyle string:' .. line)
|
||||||
|
end
|
||||||
|
local eonl = 0
|
||||||
|
for i = #s, 1, -1 do
|
||||||
|
if s[i] == '' then
|
||||||
|
tremove(s, i)
|
||||||
|
eonl = eonl + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if striptrailing then
|
||||||
|
eonl = 0
|
||||||
|
end
|
||||||
|
if newlineatend then
|
||||||
|
eonl = eonl + 1
|
||||||
|
end
|
||||||
|
for i = endline, 1, -1 do
|
||||||
|
tremove(lines, i)
|
||||||
|
end
|
||||||
|
return table.concat(s, sep) .. string.rep('\n', eonl)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parsetimestamp(line)
|
||||||
|
local _, p1, y, m, d = sfind(line, '^(%d%d%d%d)%-(%d%d)%-(%d%d)')
|
||||||
|
if not p1 then
|
||||||
|
return nil, line
|
||||||
|
end
|
||||||
|
if p1 == #line then
|
||||||
|
return types.timestamp(y, m, d), ''
|
||||||
|
end
|
||||||
|
local _, p2, h, i, s = sfind(line, '^[Tt ](%d+):(%d+):(%d+)', p1 + 1)
|
||||||
|
if not p2 then
|
||||||
|
return types.timestamp(y, m, d), ssub(line, p1 + 1)
|
||||||
|
end
|
||||||
|
if p2 == #line then
|
||||||
|
return types.timestamp(y, m, d, h, i, s), ''
|
||||||
|
end
|
||||||
|
local _, p3, f = sfind(line, '^%.(%d+)', p2 + 1)
|
||||||
|
if not p3 then
|
||||||
|
p3 = p2
|
||||||
|
f = 0
|
||||||
|
end
|
||||||
|
local zc = ssub(line, p3 + 1, p3 + 1)
|
||||||
|
local _, p4, zs, z = sfind(line, '^ ?([%+%-])(%d+)', p3 + 1)
|
||||||
|
if p4 then
|
||||||
|
z = tonumber(z)
|
||||||
|
local _, p5, zi = sfind(line, '^:(%d+)', p4 + 1)
|
||||||
|
if p5 then
|
||||||
|
z = z + tonumber(zi) / 60
|
||||||
|
end
|
||||||
|
z = zs == '-' and -tonumber(z) or tonumber(z)
|
||||||
|
elseif zc == 'Z' then
|
||||||
|
p4 = p3 + 1
|
||||||
|
z = 0
|
||||||
|
else
|
||||||
|
p4 = p3
|
||||||
|
z = false
|
||||||
|
end
|
||||||
|
return types.timestamp(y, m, d, h, i, s, f, z), ssub(line, p4 + 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parsescalar(line, lines, indent)
|
||||||
|
line = ltrim(line)
|
||||||
|
line = gsub(line, '^%s*#.*$', '') -- comment only -> ''
|
||||||
|
line = gsub(line, '^%s*', '') -- trim head spaces
|
||||||
|
|
||||||
|
if line == '' or line == '~' then
|
||||||
|
return null
|
||||||
|
end
|
||||||
|
|
||||||
|
local ts, _ = parsetimestamp(line)
|
||||||
|
if ts then
|
||||||
|
return ts
|
||||||
|
end
|
||||||
|
|
||||||
|
local s, _ = parsestring(line)
|
||||||
|
-- startswith quote ... string
|
||||||
|
-- not startswith quote ... maybe string
|
||||||
|
if s and (startswith(line, '"') or startswith(line, "'")) then
|
||||||
|
return s
|
||||||
|
end
|
||||||
|
|
||||||
|
if startswith('!', line) then -- unexpected tagchar
|
||||||
|
error('unsupported line: ' .. line)
|
||||||
|
end
|
||||||
|
|
||||||
|
if equalsline(line, '{}') then
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
if equalsline(line, '[]') then
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
if startswith(line, '{') or startswith(line, '[') then
|
||||||
|
return parseflowstyle(line, lines)
|
||||||
|
end
|
||||||
|
|
||||||
|
if startswith(line, '|') or startswith(line, '>') then
|
||||||
|
return parseblockstylestring(line, lines, indent)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Regular unquoted string
|
||||||
|
line = gsub(line, '%s*#.*$', '') -- trim tail comment
|
||||||
|
local v = line
|
||||||
|
if v == 'null' or v == 'Null' or v == 'NULL' then
|
||||||
|
return null
|
||||||
|
elseif v == 'true' or v == 'True' or v == 'TRUE' then
|
||||||
|
return true
|
||||||
|
elseif v == 'false' or v == 'False' or v == 'FALSE' then
|
||||||
|
return false
|
||||||
|
elseif v == '.inf' or v == '.Inf' or v == '.INF' then
|
||||||
|
return math.huge
|
||||||
|
elseif v == '+.inf' or v == '+.Inf' or v == '+.INF' then
|
||||||
|
return math.huge
|
||||||
|
elseif v == '-.inf' or v == '-.Inf' or v == '-.INF' then
|
||||||
|
return -math.huge
|
||||||
|
elseif v == '.nan' or v == '.NaN' or v == '.NAN' then
|
||||||
|
return 0 / 0
|
||||||
|
elseif sfind(v, '^[%+%-]?[0-9]+$') or sfind(v, '^[%+%-]?[0-9]+%.$') then
|
||||||
|
return tonumber(v) -- : int
|
||||||
|
elseif sfind(v, '^[%+%-]?[0-9]+%.[0-9]+$') then
|
||||||
|
return tonumber(v)
|
||||||
|
end
|
||||||
|
return s or v
|
||||||
|
end
|
||||||
|
|
||||||
|
local parsemap; -- : func
|
||||||
|
|
||||||
|
local function parseseq(line, lines, indent)
|
||||||
|
local seq = setmetatable({}, types.seq)
|
||||||
|
if line ~= '' then
|
||||||
|
error()
|
||||||
|
end
|
||||||
|
while #lines > 0 do
|
||||||
|
-- Check for a new document
|
||||||
|
line = lines[1]
|
||||||
|
if startswith(line, '---') then
|
||||||
|
while #lines > 0 and not startswith(lines, '---') do
|
||||||
|
tremove(lines, 1)
|
||||||
|
end
|
||||||
|
return seq
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check the indent level
|
||||||
|
local level = countindent(line)
|
||||||
|
if level < indent then
|
||||||
|
return seq
|
||||||
|
elseif level > indent then
|
||||||
|
error("found bad indenting in line: " .. line)
|
||||||
|
end
|
||||||
|
|
||||||
|
local i, j = sfind(line, '%-%s+')
|
||||||
|
if not i then
|
||||||
|
i, j = sfind(line, '%-$')
|
||||||
|
if not i then
|
||||||
|
return seq
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local rest = ssub(line, j + 1)
|
||||||
|
|
||||||
|
if sfind(rest, '^[^\'\"%s]*:') then
|
||||||
|
-- Inline nested hash
|
||||||
|
local indent2 = j
|
||||||
|
lines[1] = string.rep(' ', indent2) .. rest
|
||||||
|
tinsert(seq, parsemap('', lines, indent2))
|
||||||
|
elseif sfind(rest, '^%-%s+') then
|
||||||
|
-- Inline nested seq
|
||||||
|
local indent2 = j
|
||||||
|
lines[1] = string.rep(' ', indent2) .. rest
|
||||||
|
tinsert(seq, parseseq('', lines, indent2))
|
||||||
|
elseif isemptyline(rest) then
|
||||||
|
tremove(lines, 1)
|
||||||
|
if #lines == 0 then
|
||||||
|
tinsert(seq, null)
|
||||||
|
return seq
|
||||||
|
end
|
||||||
|
if sfind(lines[1], '^%s*%-') then
|
||||||
|
local nextline = lines[1]
|
||||||
|
local indent2 = countindent(nextline)
|
||||||
|
if indent2 == indent then
|
||||||
|
-- Null seqay entry
|
||||||
|
tinsert(seq, null)
|
||||||
|
else
|
||||||
|
tinsert(seq, parseseq('', lines, indent2))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- - # comment
|
||||||
|
-- key: value
|
||||||
|
local nextline = lines[1]
|
||||||
|
local indent2 = countindent(nextline)
|
||||||
|
tinsert(seq, parsemap('', lines, indent2))
|
||||||
|
end
|
||||||
|
elseif rest then
|
||||||
|
-- Array entry with a value
|
||||||
|
tremove(lines, 1)
|
||||||
|
tinsert(seq, parsescalar(rest, lines))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return seq
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parseset(line, lines, indent)
|
||||||
|
if not isemptyline(line) then
|
||||||
|
error('not seq line: ' .. line)
|
||||||
|
end
|
||||||
|
local set = setmetatable({}, types.set)
|
||||||
|
while #lines > 0 do
|
||||||
|
-- Check for a new document
|
||||||
|
line = lines[1]
|
||||||
|
if startswith(line, '---') then
|
||||||
|
while #lines > 0 and not startswith(lines, '---') do
|
||||||
|
tremove(lines, 1)
|
||||||
|
end
|
||||||
|
return set
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check the indent level
|
||||||
|
local level = countindent(line)
|
||||||
|
if level < indent then
|
||||||
|
return set
|
||||||
|
elseif level > indent then
|
||||||
|
error("found bad indenting in line: " .. line)
|
||||||
|
end
|
||||||
|
|
||||||
|
local i, j = sfind(line, '%?%s+')
|
||||||
|
if not i then
|
||||||
|
i, j = sfind(line, '%?$')
|
||||||
|
if not i then
|
||||||
|
return set
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local rest = ssub(line, j + 1)
|
||||||
|
|
||||||
|
if sfind(rest, '^[^\'\"%s]*:') then
|
||||||
|
-- Inline nested hash
|
||||||
|
local indent2 = j
|
||||||
|
lines[1] = string.rep(' ', indent2) .. rest
|
||||||
|
set[parsemap('', lines, indent2)] = true
|
||||||
|
elseif sfind(rest, '^%s+$') then
|
||||||
|
tremove(lines, 1)
|
||||||
|
if #lines == 0 then
|
||||||
|
tinsert(set, null)
|
||||||
|
return set
|
||||||
|
end
|
||||||
|
if sfind(lines[1], '^%s*%?') then
|
||||||
|
local indent2 = countindent(lines[1])
|
||||||
|
if indent2 == indent then
|
||||||
|
-- Null array entry
|
||||||
|
set[null] = true
|
||||||
|
else
|
||||||
|
set[parseseq('', lines, indent2)] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif rest then
|
||||||
|
tremove(lines, 1)
|
||||||
|
set[parsescalar(rest, lines)] = true
|
||||||
|
else
|
||||||
|
error("failed to classify line: " .. line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return set
|
||||||
|
end
|
||||||
|
|
||||||
|
function parsemap(line, lines, indent)
|
||||||
|
if not isemptyline(line) then
|
||||||
|
error('not map line: ' .. line)
|
||||||
|
end
|
||||||
|
local map = setmetatable({}, types.map)
|
||||||
|
while #lines > 0 do
|
||||||
|
-- Check for a new document
|
||||||
|
line = lines[1]
|
||||||
|
if startswith(line, '---') then
|
||||||
|
while #lines > 0 and not startswith(lines, '---') do
|
||||||
|
tremove(lines, 1)
|
||||||
|
end
|
||||||
|
return map
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check the indent level
|
||||||
|
local level, _ = countindent(line)
|
||||||
|
if level < indent then
|
||||||
|
return map
|
||||||
|
elseif level > indent then
|
||||||
|
error("found bad indenting in line: " .. line)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Find the key
|
||||||
|
local key
|
||||||
|
local s, rest = parsestring(line)
|
||||||
|
|
||||||
|
-- Quoted keys
|
||||||
|
if s and startswith(rest, ':') then
|
||||||
|
local sc = parsescalar(s, {}, 0)
|
||||||
|
if sc and type(sc) ~= 'string' then
|
||||||
|
key = sc
|
||||||
|
else
|
||||||
|
key = s
|
||||||
|
end
|
||||||
|
line = ssub(rest, 2)
|
||||||
|
else
|
||||||
|
error("failed to classify line: " .. line)
|
||||||
|
end
|
||||||
|
|
||||||
|
key = checkdupekey(map, key)
|
||||||
|
line = ltrim(line)
|
||||||
|
|
||||||
|
if ssub(line, 1, 1) == '!' then
|
||||||
|
-- ignore type
|
||||||
|
local rh = ltrim(ssub(line, 3))
|
||||||
|
local typename = smatch(rh, '^!?[^%s]+')
|
||||||
|
line = ltrim(ssub(rh, #typename + 1))
|
||||||
|
end
|
||||||
|
|
||||||
|
if not isemptyline(line) then
|
||||||
|
tremove(lines, 1)
|
||||||
|
line = ltrim(line)
|
||||||
|
map[key] = parsescalar(line, lines, indent)
|
||||||
|
else
|
||||||
|
-- An indent
|
||||||
|
tremove(lines, 1)
|
||||||
|
if #lines == 0 then
|
||||||
|
map[key] = null
|
||||||
|
return map;
|
||||||
|
end
|
||||||
|
if sfind(lines[1], '^%s*%-') then
|
||||||
|
local indent2 = countindent(lines[1])
|
||||||
|
map[key] = parseseq('', lines, indent2)
|
||||||
|
elseif sfind(lines[1], '^%s*%?') then
|
||||||
|
local indent2 = countindent(lines[1])
|
||||||
|
map[key] = parseset('', lines, indent2)
|
||||||
|
else
|
||||||
|
local indent2 = countindent(lines[1])
|
||||||
|
if indent >= indent2 then
|
||||||
|
-- Null hash entry
|
||||||
|
map[key] = null
|
||||||
|
else
|
||||||
|
map[key] = parsemap('', lines, indent2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return map
|
||||||
|
end
|
||||||
|
|
||||||
|
-- : (list<str>)->dict
|
||||||
|
local function parsedocuments(lines)
|
||||||
|
lines = select(lines, function(s)
|
||||||
|
return not isemptyline(s)
|
||||||
|
end)
|
||||||
|
|
||||||
|
if sfind(lines[1], '^%%YAML') then
|
||||||
|
tremove(lines, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local root = {}
|
||||||
|
local in_document = false
|
||||||
|
while #lines > 0 do
|
||||||
|
local line = lines[1]
|
||||||
|
-- Do we have a document header?
|
||||||
|
local docright;
|
||||||
|
if sfind(line, '^%-%-%-') then
|
||||||
|
-- Handle scalar documents
|
||||||
|
docright = ssub(line, 4)
|
||||||
|
tremove(lines, 1)
|
||||||
|
in_document = true
|
||||||
|
end
|
||||||
|
if docright then
|
||||||
|
if (not sfind(docright, '^%s+$') and not sfind(docright, '^%s+#')) then
|
||||||
|
tinsert(root, parsescalar(docright, lines))
|
||||||
|
end
|
||||||
|
elseif #lines == 0 or startswith(line, '---') then
|
||||||
|
-- A naked document
|
||||||
|
tinsert(root, null)
|
||||||
|
while #lines > 0 and not sfind(lines[1], '---') do
|
||||||
|
tremove(lines, 1)
|
||||||
|
end
|
||||||
|
in_document = false
|
||||||
|
-- XXX The final '-+$' is to look for -- which ends up being an
|
||||||
|
-- error later.
|
||||||
|
elseif not in_document and #root > 0 then
|
||||||
|
-- only the first document can be explicit
|
||||||
|
error('parse error: ' .. line)
|
||||||
|
elseif sfind(line, '^%s*%-') then
|
||||||
|
-- An array at the root
|
||||||
|
tinsert(root, parseseq('', lines, 0))
|
||||||
|
elseif sfind(line, '^%s*[^%s]') then
|
||||||
|
-- A hash at the root
|
||||||
|
local level = countindent(line)
|
||||||
|
tinsert(root, parsemap('', lines, level))
|
||||||
|
else
|
||||||
|
-- Shouldn't get here. @lines have whitespace-only lines
|
||||||
|
-- stripped, and previous match is a line with any
|
||||||
|
-- non-whitespace. So this clause should only be reachable via
|
||||||
|
-- a perlbug where \s is not symmetric with \S
|
||||||
|
|
||||||
|
-- uncoverable statement
|
||||||
|
error('parse error: ' .. line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #root > 1 and Null.isnull(root[1]) then
|
||||||
|
tremove(root, 1)
|
||||||
|
return root
|
||||||
|
end
|
||||||
|
return root
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Parse yaml string into table.
|
||||||
|
local function parse(source)
|
||||||
|
local lines = {}
|
||||||
|
for line in string.gmatch(source .. '\n', '(.-)\r?\n') do
|
||||||
|
tinsert(lines, line)
|
||||||
|
end
|
||||||
|
|
||||||
|
local docs = parsedocuments(lines)
|
||||||
|
if #docs == 1 then
|
||||||
|
return docs[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
return docs
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
version = 0.1,
|
||||||
|
parse = parse
|
||||||
|
}
|
||||||
@ -1,50 +0,0 @@
|
|||||||
# lor
|
|
||||||
client_body_temp
|
|
||||||
fastcgi_temp
|
|
||||||
logs
|
|
||||||
proxy_temp
|
|
||||||
tmp
|
|
||||||
uwsgi_temp
|
|
||||||
|
|
||||||
# Compiled Lua sources
|
|
||||||
luac.out
|
|
||||||
|
|
||||||
# luarocks build files
|
|
||||||
*.src.rock
|
|
||||||
*.zip
|
|
||||||
*.tar.gz
|
|
||||||
|
|
||||||
# Object files
|
|
||||||
*.o
|
|
||||||
*.os
|
|
||||||
*.ko
|
|
||||||
*.obj
|
|
||||||
*.elf
|
|
||||||
|
|
||||||
# Precompiled Headers
|
|
||||||
*.gch
|
|
||||||
*.pch
|
|
||||||
|
|
||||||
# Libraries
|
|
||||||
*.lib
|
|
||||||
*.a
|
|
||||||
*.la
|
|
||||||
*.lo
|
|
||||||
*.def
|
|
||||||
*.exp
|
|
||||||
|
|
||||||
# Shared objects (inc. Windows DLLs)
|
|
||||||
*.dll
|
|
||||||
*.so
|
|
||||||
*.so.*
|
|
||||||
*.dylib
|
|
||||||
|
|
||||||
# Executables
|
|
||||||
*.exe
|
|
||||||
*.out
|
|
||||||
*.app
|
|
||||||
*.i*86
|
|
||||||
*.x86_64
|
|
||||||
*.hex
|
|
||||||
|
|
||||||
www/admin/
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
|
|
||||||
1 安装 [openresty](https://openresty.org)
|
|
||||||
|
|
||||||
2 了解 [lor](https://github.com/sumory/lor)
|
|
||||||
|
|
||||||
3 修改 [相关配置](https://github.com/cloudfreexiao/RillAdmin/issues/1)
|
|
||||||
|
|
||||||
4 把 frontend 工程打包好的 资源 copy 到 [www 目录](https://github.com/cloudfreexiao/RillAdmin/tree/master/backend-or/www)
|
|
||||||
|
|
||||||
5 执行 start shell
|
|
||||||
|
|
||||||
6 访问 [http://localhost:9527](http://localhost:9527).
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
local errors = {}
|
|
||||||
|
|
||||||
function system_error_msg(ec)
|
|
||||||
if not ec then
|
|
||||||
return "nil"
|
|
||||||
end
|
|
||||||
return errors[ec].desc
|
|
||||||
end
|
|
||||||
|
|
||||||
local function add(err)
|
|
||||||
assert(errors[err.code] == nil, string.format("have the same error code[%x], msg[%s]", err.code, err.message))
|
|
||||||
errors[err.code] = {code = err.code, desc = err.desc}
|
|
||||||
return err.code
|
|
||||||
end
|
|
||||||
|
|
||||||
SYSTEM_ERROR = {
|
|
||||||
success = add{code = 0x0000, desc = "请求成功"},
|
|
||||||
failed = add{code = 0x0001, desc = "操作失败"},
|
|
||||||
}
|
|
||||||
|
|
||||||
AUTH_ERROR = {
|
|
||||||
account_error = add{code = 0x0101, desc = "用户名或密码错误,请检查!"},
|
|
||||||
account_nil = add{code = 0x0102, desc = "用户名和密码不得为空!"},
|
|
||||||
account_login = add{code = 0x0103, desc = "该操作需要先登录!"},
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return errors
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
local utils = require("app.libs.utils")
|
|
||||||
-- local jwt = require("app.libs.jwt.jwt")
|
|
||||||
|
|
||||||
local pwd_secret = require("app.config.config").pwd_secret
|
|
||||||
local jwt_secret = require("app.config.config").jwt_secret
|
|
||||||
|
|
||||||
local user_model = require("app.model.user")
|
|
||||||
|
|
||||||
return function (req, username, password)
|
|
||||||
if not username or not password or username == "" or password == "" then
|
|
||||||
return {
|
|
||||||
code = AUTH_ERROR.account_nil,
|
|
||||||
message = system_error_msg(AUTH_ERROR.account_nil),
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
local isExist = false
|
|
||||||
local userid = 0
|
|
||||||
|
|
||||||
password = utils.encode(password .. "#" .. pwd_secret)
|
|
||||||
local result, err = user_model:query(username, password)
|
|
||||||
|
|
||||||
local user = {}
|
|
||||||
if result and not err then
|
|
||||||
if result and #result == 1 then
|
|
||||||
isExist = true
|
|
||||||
user = result[1]
|
|
||||||
userid = user.id
|
|
||||||
end
|
|
||||||
else
|
|
||||||
isExist = false
|
|
||||||
end
|
|
||||||
|
|
||||||
-- 生成 token 的有效期
|
|
||||||
local now = ngx.now()
|
|
||||||
local exp = now + 1200
|
|
||||||
|
|
||||||
if isExist == true then
|
|
||||||
-- local jwt_token = jwt:sign(jwt_secret, {
|
|
||||||
-- header = { typ = "JWT", alg = "HS256" },
|
|
||||||
-- payload = { foo = "bar", id = 1, name = "mind029", exp = exp }
|
|
||||||
-- })
|
|
||||||
|
|
||||||
local token = ngx.md5(username .. password .. os.time() .. "fishadminapi")
|
|
||||||
req.session.set("user", {
|
|
||||||
username = username,
|
|
||||||
userid = userid,
|
|
||||||
create_time = user.create_time or "",
|
|
||||||
token = token
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
code = SYSTEM_ERROR.success,
|
|
||||||
message = system_error_msg(SYSTEM_ERROR.success),
|
|
||||||
data = {
|
|
||||||
token = token
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return {
|
|
||||||
code = AUTH_ERROR.account_error,
|
|
||||||
message = system_error_msg(AUTH_ERROR.account_error),
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
@ -1,78 +0,0 @@
|
|||||||
local pairs = pairs
|
|
||||||
local ipairs = ipairs
|
|
||||||
local smatch = string.match
|
|
||||||
local slen = string.len
|
|
||||||
local ssub = string.sub
|
|
||||||
local slower = string.lower
|
|
||||||
|
|
||||||
local utils = require("app.libs.utils")
|
|
||||||
local pwd_secret = require("app.config.config").pwd_secret
|
|
||||||
|
|
||||||
local user_model = require("app.model.user")
|
|
||||||
|
|
||||||
local role_lv = 1 --角色等级 用来 鉴权操作
|
|
||||||
|
|
||||||
return function(username, password)
|
|
||||||
local pattern = "^[a-zA-Z][0-9a-zA-Z_]+$"
|
|
||||||
local match, err = smatch(username, pattern)
|
|
||||||
|
|
||||||
if not username or not password or username == "" or password == "" then
|
|
||||||
return {
|
|
||||||
code = system_error_msg(AUTH_ERROR.account_nil),
|
|
||||||
message = system_error_msg(AUTH_ERROR.account_nil),
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
local username_len = slen(username)
|
|
||||||
local password_len = slen(password)
|
|
||||||
|
|
||||||
if username_len<4 or username_len>50 then
|
|
||||||
return {
|
|
||||||
success = false,
|
|
||||||
msg = "用户名长度应为4~50位."
|
|
||||||
}
|
|
||||||
end
|
|
||||||
if password_len<6 or password_len>50 then
|
|
||||||
return {
|
|
||||||
success = false,
|
|
||||||
msg = "密码长度应为6~50位."
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
if not match then
|
|
||||||
return {
|
|
||||||
success = false,
|
|
||||||
msg = "用户名只能输入字母、下划线、数字,必须以字母开头."
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
local result, err = user_model:query_by_username(username)
|
|
||||||
local isExist = false
|
|
||||||
if result and not err then
|
|
||||||
isExist = true
|
|
||||||
end
|
|
||||||
|
|
||||||
if isExist == true then
|
|
||||||
return {
|
|
||||||
success = false,
|
|
||||||
msg = "用户名已被占用,请修改."
|
|
||||||
}
|
|
||||||
else
|
|
||||||
password = utils.encode(password .. "#" .. pwd_secret)
|
|
||||||
local avatar = ssub(username, 1, 1) .. ".png" --取首字母作为默认头像名
|
|
||||||
avatar = slower(avatar)
|
|
||||||
local result, err = user_model:new(username, password, avatar, role_lv)
|
|
||||||
if result and not err then
|
|
||||||
return {
|
|
||||||
success = true,
|
|
||||||
msg = "注册成功."
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return {
|
|
||||||
success = false,
|
|
||||||
msg = "注册失败."
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
|
|
||||||
local function is_login(req)
|
|
||||||
local user
|
|
||||||
if req.session then
|
|
||||||
user = req.session.get("user")
|
|
||||||
if user and user.username and user.userid then
|
|
||||||
return true, user
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return false, nil
|
|
||||||
end
|
|
||||||
|
|
||||||
return function (req, res)
|
|
||||||
local user = is_login(req)
|
|
||||||
assert(user)
|
|
||||||
local msg = {
|
|
||||||
code = SYSTEM_ERROR.success,
|
|
||||||
message = system_error_msg(SYSTEM_ERROR.success),
|
|
||||||
data = {
|
|
||||||
name = "goodname",
|
|
||||||
avatar = "333333",
|
|
||||||
roles = {[1] = "/api/getRoles",
|
|
||||||
[2] = "/api/role",},
|
|
||||||
permissions = {[1]="/api/permissions/"},
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return msg
|
|
||||||
end
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
local cors_whitelist = require("app.config.config").cors_whitelist
|
|
||||||
|
|
||||||
--跨域访问 头 设置
|
|
||||||
return function (res)
|
|
||||||
res:set_header("X-Powered-By", "Lor framework")
|
|
||||||
res:set_header("Access-Control-Allow-Origin", cors_whitelist)
|
|
||||||
|
|
||||||
res:set_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
|
||||||
res:set_header("Access-Control-Allow-Credentials", "true")
|
|
||||||
res:set_header("Access-Control-Allow-Headers", "Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since,X-Msys-Subaccount,X-Sparky")
|
|
||||||
end
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
|
|
||||||
### lua c so 存放目录()
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
-- @Author: detailyang
|
|
||||||
-- @Date: 2016-10-10 15:45:33
|
|
||||||
-- @Last Modified by: detailyang
|
|
||||||
-- @Last Modified time: 2017-02-19 13:29:41
|
|
||||||
|
|
||||||
-- https://www.w3.org/TR/cors/
|
|
||||||
|
|
||||||
local re_match = ngx.re.match
|
|
||||||
|
|
||||||
local _M = { _VERSION = '0.1.0'}
|
|
||||||
|
|
||||||
local Origin = 'Origin'
|
|
||||||
local AccessControlAllowOrigin = 'Access-Control-Allow-Origin'
|
|
||||||
local AccessControlExposeHeaders = 'Access-Control-Expose-Headers'
|
|
||||||
local AccessControlMaxAge = 'Access-Control-Max-Age'
|
|
||||||
local AccessControlAllowCredentials = 'Access-Control-Allow-Credentials'
|
|
||||||
local AccessControlAllowMethods = 'Access-Control-Allow-Methods'
|
|
||||||
local AccessControlAllowHeaders = 'Access-Control-Allow-Headers'
|
|
||||||
|
|
||||||
local mt = { __index = _M }
|
|
||||||
|
|
||||||
local allow_hosts = {}
|
|
||||||
local allow_headers = {}
|
|
||||||
local allow_methods = {}
|
|
||||||
local expose_headers = {}
|
|
||||||
local max_age = 3600
|
|
||||||
local allow_credentials = true
|
|
||||||
local join = table.concat
|
|
||||||
|
|
||||||
|
|
||||||
function _M.allow_host(host)
|
|
||||||
allow_hosts[#allow_hosts + 1] = host
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.allow_method(method)
|
|
||||||
allow_methods[#allow_methods + 1] = method
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.allow_header(header)
|
|
||||||
allow_headers[#allow_headers + 1] = header
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.expose_header(header)
|
|
||||||
expose_headers[#expose_headers + 1] = header
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.max_age(age)
|
|
||||||
max_age = age
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.allow_credentials(credentials)
|
|
||||||
allow_credentials = credentials
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.run()
|
|
||||||
local origin = ngx.req.get_headers()[Origin]
|
|
||||||
if not origin then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local matched = false
|
|
||||||
for k, v in pairs(allow_hosts) do
|
|
||||||
local from, to, err = ngx.re.find(origin, v, "jo")
|
|
||||||
if from then
|
|
||||||
matched = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if matched == false then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
ngx.header[AccessControlAllowOrigin] = origin
|
|
||||||
ngx.header[AccessControlMaxAge] = max_age
|
|
||||||
|
|
||||||
if #expose_headers >= 0 then
|
|
||||||
ngx.header[AccessControlExposeHeaders] = join(expose_headers, ',')
|
|
||||||
end
|
|
||||||
|
|
||||||
if #allow_headers >= 0 then
|
|
||||||
ngx.header[AccessControlAllowHeaders] = join(allow_headers, ',')
|
|
||||||
end
|
|
||||||
|
|
||||||
if #allow_methods >= 0 then
|
|
||||||
ngx.header[AccessControlAllowMethods] = join(allow_methods, ',')
|
|
||||||
end
|
|
||||||
|
|
||||||
if allow_credentials == true then
|
|
||||||
ngx.header[AccessControlAllowCredentials] = "true"
|
|
||||||
else
|
|
||||||
ngx.header[AccessControlAllowCredentials] = "false"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return _M
|
|
||||||
@ -1,747 +0,0 @@
|
|||||||
-- https://github.com/Tieske/date
|
|
||||||
---------------------------------------------------------------------------------------
|
|
||||||
-- Module for date and time calculations
|
|
||||||
--
|
|
||||||
-- Version 2.1.1
|
|
||||||
-- Copyright (C) 2006, by Jas Latrix (jastejada@yahoo.com)
|
|
||||||
-- Copyright (C) 2013-2014, by Thijs Schreijer
|
|
||||||
-- Licensed under MIT, http://opensource.org/licenses/MIT
|
|
||||||
|
|
||||||
--[[ CONSTANTS ]]--
|
|
||||||
local HOURPERDAY = 24
|
|
||||||
local MINPERHOUR = 60
|
|
||||||
local MINPERDAY = 1440 -- 24*60
|
|
||||||
local SECPERMIN = 60
|
|
||||||
local SECPERHOUR = 3600 -- 60*60
|
|
||||||
local SECPERDAY = 86400 -- 24*60*60
|
|
||||||
local TICKSPERSEC = 1000000
|
|
||||||
local TICKSPERDAY = 86400000000
|
|
||||||
local TICKSPERHOUR = 3600000000
|
|
||||||
local TICKSPERMIN = 60000000
|
|
||||||
local DAYNUM_MAX = 365242500 -- Sat Jan 01 1000000 00:00:00
|
|
||||||
local DAYNUM_MIN = -365242500 -- Mon Jan 01 1000000 BCE 00:00:00
|
|
||||||
local DAYNUM_DEF = 0 -- Mon Jan 01 0001 00:00:00
|
|
||||||
local _;
|
|
||||||
--[[ LOCAL ARE FASTER ]]--
|
|
||||||
local type = type
|
|
||||||
local pairs = pairs
|
|
||||||
local error = error
|
|
||||||
local assert = assert
|
|
||||||
local tonumber = tonumber
|
|
||||||
local tostring = tostring
|
|
||||||
local string = string
|
|
||||||
local math = math
|
|
||||||
local os = os
|
|
||||||
local unpack = unpack or table.unpack
|
|
||||||
local pack = table.pack or function(...) return { n = select('#', ...), ... } end
|
|
||||||
local setmetatable = setmetatable
|
|
||||||
local getmetatable = getmetatable
|
|
||||||
--[[ EXTRA FUNCTIONS ]]--
|
|
||||||
local fmt = string.format
|
|
||||||
local lwr = string.lower
|
|
||||||
local upr = string.upper
|
|
||||||
local rep = string.rep
|
|
||||||
local len = string.len
|
|
||||||
local sub = string.sub
|
|
||||||
local gsub = string.gsub
|
|
||||||
local gmatch = string.gmatch or string.gfind
|
|
||||||
local find = string.find
|
|
||||||
local ostime = os.time
|
|
||||||
local osdate = os.date
|
|
||||||
local floor = math.floor
|
|
||||||
local ceil = math.ceil
|
|
||||||
local abs = math.abs
|
|
||||||
-- removes the decimal part of a number
|
|
||||||
local function fix(n) n = tonumber(n) return n and ((n > 0 and floor or ceil)(n)) end
|
|
||||||
-- returns the modulo n % d;
|
|
||||||
local function mod(n,d) return n - d*floor(n/d) end
|
|
||||||
-- rounds a number;
|
|
||||||
local function round(n, d) d=d^10 return floor((n*d)+.5)/d end
|
|
||||||
-- rounds a number to whole;
|
|
||||||
local function whole(n)return floor(n+.5)end
|
|
||||||
-- is `str` in string list `tbl`, `ml` is the minimun len
|
|
||||||
local function inlist(str, tbl, ml, tn)
|
|
||||||
local sl = len(str)
|
|
||||||
if sl < (ml or 0) then return nil end
|
|
||||||
str = lwr(str)
|
|
||||||
for k, v in pairs(tbl) do
|
|
||||||
if str == lwr(sub(v, 1, sl)) then
|
|
||||||
if tn then tn[0] = k end
|
|
||||||
return k
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local function fnil() end
|
|
||||||
local function fret(x)return x;end
|
|
||||||
--[[ DATE FUNCTIONS ]]--
|
|
||||||
local DATE_EPOCH -- to be set later
|
|
||||||
local sl_weekdays = {
|
|
||||||
[0]="Sunday",[1]="Monday",[2]="Tuesday",[3]="Wednesday",[4]="Thursday",[5]="Friday",[6]="Saturday",
|
|
||||||
[7]="Sun",[8]="Mon",[9]="Tue",[10]="Wed",[11]="Thu",[12]="Fri",[13]="Sat",
|
|
||||||
}
|
|
||||||
local sl_meridian = {[-1]="AM", [1]="PM"}
|
|
||||||
local sl_months = {
|
|
||||||
[00]="January", [01]="February", [02]="March",
|
|
||||||
[03]="April", [04]="May", [05]="June",
|
|
||||||
[06]="July", [07]="August", [08]="September",
|
|
||||||
[09]="October", [10]="November", [11]="December",
|
|
||||||
[12]="Jan", [13]="Feb", [14]="Mar",
|
|
||||||
[15]="Apr", [16]="May", [17]="Jun",
|
|
||||||
[18]="Jul", [19]="Aug", [20]="Sep",
|
|
||||||
[21]="Oct", [22]="Nov", [23]="Dec",
|
|
||||||
}
|
|
||||||
-- added the '.2' to avoid collision, use `fix` to remove
|
|
||||||
local sl_timezone = {
|
|
||||||
[000]="utc", [0.2]="gmt",
|
|
||||||
[300]="est", [240]="edt",
|
|
||||||
[360]="cst", [300.2]="cdt",
|
|
||||||
[420]="mst", [360.2]="mdt",
|
|
||||||
[480]="pst", [420.2]="pdt",
|
|
||||||
}
|
|
||||||
-- set the day fraction resolution
|
|
||||||
local function setticks(t)
|
|
||||||
TICKSPERSEC = t;
|
|
||||||
TICKSPERDAY = SECPERDAY*TICKSPERSEC
|
|
||||||
TICKSPERHOUR= SECPERHOUR*TICKSPERSEC
|
|
||||||
TICKSPERMIN = SECPERMIN*TICKSPERSEC
|
|
||||||
end
|
|
||||||
-- is year y leap year?
|
|
||||||
local function isleapyear(y) -- y must be int!
|
|
||||||
return (mod(y, 4) == 0 and (mod(y, 100) ~= 0 or mod(y, 400) == 0))
|
|
||||||
end
|
|
||||||
-- day since year 0
|
|
||||||
local function dayfromyear(y) -- y must be int!
|
|
||||||
return 365*y + floor(y/4) - floor(y/100) + floor(y/400)
|
|
||||||
end
|
|
||||||
-- day number from date, month is zero base
|
|
||||||
local function makedaynum(y, m, d)
|
|
||||||
local mm = mod(mod(m,12) + 10, 12)
|
|
||||||
return dayfromyear(y + floor(m/12) - floor(mm/10)) + floor((mm*306 + 5)/10) + d - 307
|
|
||||||
--local yy = y + floor(m/12) - floor(mm/10)
|
|
||||||
--return dayfromyear(yy) + floor((mm*306 + 5)/10) + (d - 1)
|
|
||||||
end
|
|
||||||
-- date from day number, month is zero base
|
|
||||||
local function breakdaynum(g)
|
|
||||||
local g = g + 306
|
|
||||||
local y = floor((10000*g + 14780)/3652425)
|
|
||||||
local d = g - dayfromyear(y)
|
|
||||||
if d < 0 then y = y - 1; d = g - dayfromyear(y) end
|
|
||||||
local mi = floor((100*d + 52)/3060)
|
|
||||||
return (floor((mi + 2)/12) + y), mod(mi + 2,12), (d - floor((mi*306 + 5)/10) + 1)
|
|
||||||
end
|
|
||||||
--[[ for floats or int32 Lua Number data type
|
|
||||||
local function breakdaynum2(g)
|
|
||||||
local g, n = g + 306;
|
|
||||||
local n400 = floor(g/DI400Y);n = mod(g,DI400Y);
|
|
||||||
local n100 = floor(n/DI100Y);n = mod(n,DI100Y);
|
|
||||||
local n004 = floor(n/DI4Y); n = mod(n,DI4Y);
|
|
||||||
local n001 = floor(n/365); n = mod(n,365);
|
|
||||||
local y = (n400*400) + (n100*100) + (n004*4) + n001 - ((n001 == 4 or n100 == 4) and 1 or 0)
|
|
||||||
local d = g - dayfromyear(y)
|
|
||||||
local mi = floor((100*d + 52)/3060)
|
|
||||||
return (floor((mi + 2)/12) + y), mod(mi + 2,12), (d - floor((mi*306 + 5)/10) + 1)
|
|
||||||
end
|
|
||||||
]]
|
|
||||||
-- day fraction from time
|
|
||||||
local function makedayfrc(h,r,s,t)
|
|
||||||
return ((h*60 + r)*60 + s)*TICKSPERSEC + t
|
|
||||||
end
|
|
||||||
-- time from day fraction
|
|
||||||
local function breakdayfrc(df)
|
|
||||||
return
|
|
||||||
mod(floor(df/TICKSPERHOUR),HOURPERDAY),
|
|
||||||
mod(floor(df/TICKSPERMIN ),MINPERHOUR),
|
|
||||||
mod(floor(df/TICKSPERSEC ),SECPERMIN),
|
|
||||||
mod(df,TICKSPERSEC)
|
|
||||||
end
|
|
||||||
-- weekday sunday = 0, monday = 1 ...
|
|
||||||
local function weekday(dn) return mod(dn + 1, 7) end
|
|
||||||
-- yearday 0 based ...
|
|
||||||
local function yearday(dn)
|
|
||||||
return dn - dayfromyear((breakdaynum(dn))-1)
|
|
||||||
end
|
|
||||||
-- parse v as a month
|
|
||||||
local function getmontharg(v)
|
|
||||||
local m = tonumber(v);
|
|
||||||
return (m and fix(m - 1)) or inlist(tostring(v) or "", sl_months, 2)
|
|
||||||
end
|
|
||||||
-- get daynum of isoweek one of year y
|
|
||||||
local function isow1(y)
|
|
||||||
local f = makedaynum(y, 0, 4) -- get the date for the 4-Jan of year `y`
|
|
||||||
local d = weekday(f)
|
|
||||||
d = d == 0 and 7 or d -- get the ISO day number, 1 == Monday, 7 == Sunday
|
|
||||||
return f + (1 - d)
|
|
||||||
end
|
|
||||||
local function isowy(dn)
|
|
||||||
local w1;
|
|
||||||
local y = (breakdaynum(dn))
|
|
||||||
if dn >= makedaynum(y, 11, 29) then
|
|
||||||
w1 = isow1(y + 1);
|
|
||||||
if dn < w1 then
|
|
||||||
w1 = isow1(y);
|
|
||||||
else
|
|
||||||
y = y + 1;
|
|
||||||
end
|
|
||||||
else
|
|
||||||
w1 = isow1(y);
|
|
||||||
if dn < w1 then
|
|
||||||
w1 = isow1(y-1)
|
|
||||||
y = y - 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return floor((dn-w1)/7)+1, y
|
|
||||||
end
|
|
||||||
local function isoy(dn)
|
|
||||||
local y = (breakdaynum(dn))
|
|
||||||
return y + (((dn >= makedaynum(y, 11, 29)) and (dn >= isow1(y + 1))) and 1 or (dn < isow1(y) and -1 or 0))
|
|
||||||
end
|
|
||||||
local function makedaynum_isoywd(y,w,d)
|
|
||||||
return isow1(y) + 7*w + d - 8 -- simplified: isow1(y) + ((w-1)*7) + (d-1)
|
|
||||||
end
|
|
||||||
--[[ THE DATE MODULE ]]--
|
|
||||||
local fmtstr = "%x %X";
|
|
||||||
--#if not DATE_OBJECT_AFX then
|
|
||||||
local date = {}
|
|
||||||
setmetatable(date, date)
|
|
||||||
-- Version: VMMMRRRR; V-Major, M-Minor, R-Revision; e.g. 5.45.321 == 50450321
|
|
||||||
date.version = 20010001 -- 2.1.1
|
|
||||||
--#end -- not DATE_OBJECT_AFX
|
|
||||||
--[[ THE DATE OBJECT ]]--
|
|
||||||
local dobj = {}
|
|
||||||
dobj.__index = dobj
|
|
||||||
dobj.__metatable = dobj
|
|
||||||
-- shout invalid arg
|
|
||||||
local function date_error_arg() return error("invalid argument(s)",0) end
|
|
||||||
-- create new date object
|
|
||||||
local function date_new(dn, df)
|
|
||||||
return setmetatable({daynum=dn, dayfrc=df}, dobj)
|
|
||||||
end
|
|
||||||
-- is `v` a date object?
|
|
||||||
local function date_isdobj(v)
|
|
||||||
return (type(v) == 'table' and getmetatable(v) == dobj) and v
|
|
||||||
end
|
|
||||||
|
|
||||||
--#if not NO_LOCAL_TIME_SUPPORT then
|
|
||||||
-- magic year table
|
|
||||||
local date_epoch, yt;
|
|
||||||
local function getequivyear(y)
|
|
||||||
assert(not yt)
|
|
||||||
yt = {}
|
|
||||||
local de, dw, dy = date_epoch:copy()
|
|
||||||
for i = 0, 3000 do
|
|
||||||
de:setyear(de:getyear() + 1, 1, 1)
|
|
||||||
dy = de:getyear()
|
|
||||||
dw = de:getweekday() * (isleapyear(dy) and -1 or 1)
|
|
||||||
if not yt[dw] then yt[dw] = dy end --print(de)
|
|
||||||
if yt[1] and yt[2] and yt[3] and yt[4] and yt[5] and yt[6] and yt[7] and yt[-1] and yt[-2] and yt[-3] and yt[-4] and yt[-5] and yt[-6] and yt[-7] then
|
|
||||||
getequivyear = function(y) return yt[ (weekday(makedaynum(y, 0, 1)) + 1) * (isleapyear(y) and -1 or 1) ] end
|
|
||||||
return getequivyear(y)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- TimeValue from daynum and dayfrc
|
|
||||||
local function dvtotv(dn, df)
|
|
||||||
return fix(dn - DATE_EPOCH) * SECPERDAY + (df/1000)
|
|
||||||
end
|
|
||||||
-- TimeValue from date and time
|
|
||||||
local function totv(y,m,d,h,r,s)
|
|
||||||
return (makedaynum(y, m, d) - DATE_EPOCH) * SECPERDAY + ((h*60 + r)*60 + s)
|
|
||||||
end
|
|
||||||
-- TimeValue from TimeTable
|
|
||||||
local function tmtotv(tm)
|
|
||||||
return tm and totv(tm.year, tm.month - 1, tm.day, tm.hour, tm.min, tm.sec)
|
|
||||||
end
|
|
||||||
-- Returns the bias in seconds of utc time daynum and dayfrc
|
|
||||||
local function getbiasutc2(self)
|
|
||||||
local y,m,d = breakdaynum(self.daynum)
|
|
||||||
local h,r,s = breakdayfrc(self.dayfrc)
|
|
||||||
local tvu = totv(y,m,d,h,r,s) -- get the utc TimeValue of date and time
|
|
||||||
local tml = osdate("*t", tvu) -- get the local TimeTable of tvu
|
|
||||||
if (not tml) or (tml.year > (y+1) or tml.year < (y-1)) then -- failed try the magic
|
|
||||||
y = getequivyear(y)
|
|
||||||
tvu = totv(y,m,d,h,r,s)
|
|
||||||
tml = osdate("*t", tvu)
|
|
||||||
end
|
|
||||||
local tvl = tmtotv(tml)
|
|
||||||
if tvu and tvl then
|
|
||||||
return tvu - tvl, tvu, tvl
|
|
||||||
else
|
|
||||||
return error("failed to get bias from utc time")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- Returns the bias in seconds of local time daynum and dayfrc
|
|
||||||
local function getbiasloc2(daynum, dayfrc)
|
|
||||||
local tvu
|
|
||||||
-- extract date and time
|
|
||||||
local y,m,d = breakdaynum(daynum)
|
|
||||||
local h,r,s = breakdayfrc(dayfrc)
|
|
||||||
-- get equivalent TimeTable
|
|
||||||
local tml = {year=y, month=m+1, day=d, hour=h, min=r, sec=s}
|
|
||||||
-- get equivalent TimeValue
|
|
||||||
local tvl = tmtotv(tml)
|
|
||||||
|
|
||||||
local function chkutc()
|
|
||||||
tml.isdst = nil; local tvug = ostime(tml) if tvug and (tvl == tmtotv(osdate("*t", tvug))) then tvu = tvug return end
|
|
||||||
tml.isdst = true; local tvud = ostime(tml) if tvud and (tvl == tmtotv(osdate("*t", tvud))) then tvu = tvud return end
|
|
||||||
tvu = tvud or tvug
|
|
||||||
end
|
|
||||||
chkutc()
|
|
||||||
if not tvu then
|
|
||||||
tml.year = getequivyear(y)
|
|
||||||
tvl = tmtotv(tml)
|
|
||||||
chkutc()
|
|
||||||
end
|
|
||||||
return ((tvu and tvl) and (tvu - tvl)) or error("failed to get bias from local time"), tvu, tvl
|
|
||||||
end
|
|
||||||
--#end -- not NO_LOCAL_TIME_SUPPORT
|
|
||||||
|
|
||||||
--#if not DATE_OBJECT_AFX then
|
|
||||||
-- the date parser
|
|
||||||
local strwalker = {} -- ^Lua regular expression is not as powerful as Perl$
|
|
||||||
strwalker.__index = strwalker
|
|
||||||
local function newstrwalker(s)return setmetatable({s=s, i=1, e=1, c=len(s)}, strwalker) end
|
|
||||||
function strwalker:aimchr() return "\n" .. self.s .. "\n" .. rep(".",self.e-1) .. "^" end
|
|
||||||
function strwalker:finish() return self.i > self.c end
|
|
||||||
function strwalker:back() self.i = self.e return self end
|
|
||||||
function strwalker:restart() self.i, self.e = 1, 1 return self end
|
|
||||||
function strwalker:match(s) return (find(self.s, s, self.i)) end
|
|
||||||
function strwalker:__call(s, f)-- print("strwalker:__call "..s..self:aimchr())
|
|
||||||
local is, ie; is, ie, self[1], self[2], self[3], self[4], self[5] = find(self.s, s, self.i)
|
|
||||||
if is then self.e, self.i = self.i, 1+ie; if f then f(unpack(self)) end return self end
|
|
||||||
end
|
|
||||||
local function date_parse(str)
|
|
||||||
local y,m,d, h,r,s, z, w,u, j, e, k, x,v,c, chkfin, dn,df;
|
|
||||||
local sw = newstrwalker(gsub(gsub(str, "(%b())", ""),"^(%s*)","")) -- remove comment, trim leading space
|
|
||||||
--local function error_out() print(y,m,d,h,r,s) end
|
|
||||||
local function error_dup(q) --[[error_out()]] error("duplicate value: " .. (q or "") .. sw:aimchr()) end
|
|
||||||
local function error_syn(q) --[[error_out()]] error("syntax error: " .. (q or "") .. sw:aimchr()) end
|
|
||||||
local function error_inv(q) --[[error_out()]] error("invalid date: " .. (q or "") .. sw:aimchr()) end
|
|
||||||
local function sety(q) y = y and error_dup() or tonumber(q); end
|
|
||||||
local function setm(q) m = (m or w or j) and error_dup(m or w or j) or tonumber(q) end
|
|
||||||
local function setd(q) d = d and error_dup() or tonumber(q) end
|
|
||||||
local function seth(q) h = h and error_dup() or tonumber(q) end
|
|
||||||
local function setr(q) r = r and error_dup() or tonumber(q) end
|
|
||||||
local function sets(q) s = s and error_dup() or tonumber(q) end
|
|
||||||
local function adds(q) s = s + tonumber(q) end
|
|
||||||
local function setj(q) j = (m or w or j) and error_dup() or tonumber(q); end
|
|
||||||
local function setz(q) z = (z ~= 0 and z) and error_dup() or q end
|
|
||||||
local function setzn(zs,zn) zn = tonumber(zn); setz( ((zn<24) and (zn*60) or (mod(zn,100) + floor(zn/100) * 60))*( zs=='+' and -1 or 1) ) end
|
|
||||||
local function setzc(zs,zh,zm) setz( ((tonumber(zh)*60) + tonumber(zm))*( zs=='+' and -1 or 1) ) end
|
|
||||||
|
|
||||||
if not (sw("^(%d%d%d%d)",sety) and (sw("^(%-?)(%d%d)%1(%d%d)",function(_,a,b) setm(tonumber(a)); setd(tonumber(b)) end) or sw("^(%-?)[Ww](%d%d)%1(%d?)",function(_,a,b) w, u = tonumber(a), tonumber(b or 1) end) or sw("^%-?(%d%d%d)",setj) or sw("^%-?(%d%d)",function(a) setm(a);setd(1) end))
|
|
||||||
and ((sw("^%s*[Tt]?(%d%d):?",seth) and sw("^(%d%d):?",setr) and sw("^(%d%d)",sets) and sw("^(%.%d+)",adds))
|
|
||||||
or sw:finish() or (sw"^%s*$" or sw"^%s*[Zz]%s*$" or sw("^%s-([%+%-])(%d%d):?(%d%d)%s*$",setzc) or sw("^%s*([%+%-])(%d%d)%s*$",setzn))
|
|
||||||
) )
|
|
||||||
then --print(y,m,d,h,r,s,z,w,u,j)
|
|
||||||
sw:restart(); y,m,d,h,r,s,z,w,u,j = nil;
|
|
||||||
repeat -- print(sw:aimchr())
|
|
||||||
if sw("^[tT:]?%s*(%d%d?):",seth) then --print("$Time")
|
|
||||||
_ = sw("^%s*(%d%d?)",setr) and sw("^%s*:%s*(%d%d?)",sets) and sw("^(%.%d+)",adds)
|
|
||||||
elseif sw("^(%d+)[/\\%s,-]?%s*") then --print("$Digits")
|
|
||||||
x, c = tonumber(sw[1]), len(sw[1])
|
|
||||||
if (x >= 70) or (m and d and (not y)) or (c > 3) then
|
|
||||||
sety( x + ((x >= 100 or c>3)and 0 or 1900) )
|
|
||||||
else
|
|
||||||
if m then setd(x) else m = x end
|
|
||||||
end
|
|
||||||
elseif sw("^(%a+)[/\\%s,-]?%s*") then --print("$Words")
|
|
||||||
x = sw[1]
|
|
||||||
if inlist(x, sl_months, 2, sw) then
|
|
||||||
if m and (not d) and (not y) then d, m = m, false end
|
|
||||||
setm(mod(sw[0],12)+1)
|
|
||||||
elseif inlist(x, sl_timezone, 2, sw) then
|
|
||||||
c = fix(sw[0]) -- ignore gmt and utc
|
|
||||||
if c ~= 0 then setz(c, x) end
|
|
||||||
elseif inlist(x, sl_weekdays, 2, sw) then
|
|
||||||
k = sw[0]
|
|
||||||
else
|
|
||||||
sw:back()
|
|
||||||
-- am pm bce ad ce bc
|
|
||||||
if sw("^([bB])%s*(%.?)%s*[Cc]%s*(%2)%s*[Ee]%s*(%2)%s*") or sw("^([bB])%s*(%.?)%s*[Cc]%s*(%2)%s*") then
|
|
||||||
e = e and error_dup() or -1
|
|
||||||
elseif sw("^([aA])%s*(%.?)%s*[Dd]%s*(%2)%s*") or sw("^([cC])%s*(%.?)%s*[Ee]%s*(%2)%s*") then
|
|
||||||
e = e and error_dup() or 1
|
|
||||||
elseif sw("^([PApa])%s*(%.?)%s*[Mm]?%s*(%2)%s*") then
|
|
||||||
x = lwr(sw[1]) -- there should be hour and it must be correct
|
|
||||||
if (not h) or (h > 12) or (h < 0) then return error_inv() end
|
|
||||||
if x == 'a' and h == 12 then h = 0 end -- am
|
|
||||||
if x == 'p' and h ~= 12 then h = h + 12 end -- pm
|
|
||||||
else error_syn() end
|
|
||||||
end
|
|
||||||
elseif not(sw("^([+-])(%d%d?):(%d%d)",setzc) or sw("^([+-])(%d+)",setzn) or sw("^[Zz]%s*$")) then -- sw{"([+-])",{"(%d%d?):(%d%d)","(%d+)"}}
|
|
||||||
error_syn("?")
|
|
||||||
end
|
|
||||||
sw("^%s*") until sw:finish()
|
|
||||||
--else print("$Iso(Date|Time|Zone)")
|
|
||||||
end
|
|
||||||
-- if date is given, it must be complete year, month & day
|
|
||||||
if (not y and not h) or ((m and not d) or (d and not m)) or ((m and w) or (m and j) or (j and w)) then return error_inv("!") end
|
|
||||||
-- fix month
|
|
||||||
if m then m = m - 1 end
|
|
||||||
-- fix year if we are on BCE
|
|
||||||
if e and e < 0 and y > 0 then y = 1 - y end
|
|
||||||
-- create date object
|
|
||||||
dn = (y and ((w and makedaynum_isoywd(y,w,u)) or (j and makedaynum(y, 0, j)) or makedaynum(y, m, d))) or DAYNUM_DEF
|
|
||||||
df = makedayfrc(h or 0, r or 0, s or 0, 0) + ((z or 0)*TICKSPERMIN)
|
|
||||||
--print("Zone",h,r,s,z,m,d,y,df)
|
|
||||||
return date_new(dn, df) -- no need to :normalize();
|
|
||||||
end
|
|
||||||
local function date_fromtable(v)
|
|
||||||
local y, m, d = fix(v.year), getmontharg(v.month), fix(v.day)
|
|
||||||
local h, r, s, t = tonumber(v.hour), tonumber(v.min), tonumber(v.sec), tonumber(v.ticks)
|
|
||||||
-- atleast there is time or complete date
|
|
||||||
if (y or m or d) and (not(y and m and d)) then return error("incomplete table") end
|
|
||||||
return (y or h or r or s or t) and date_new(y and makedaynum(y, m, d) or DAYNUM_DEF, makedayfrc(h or 0, r or 0, s or 0, t or 0))
|
|
||||||
end
|
|
||||||
local tmap = {
|
|
||||||
['number'] = function(v) return date_epoch:copy():addseconds(v) end,
|
|
||||||
['string'] = function(v) return date_parse(v) end,
|
|
||||||
['boolean']= function(v) return date_fromtable(osdate(v and "!*t" or "*t")) end,
|
|
||||||
['table'] = function(v) local ref = getmetatable(v) == dobj; return ref and v or date_fromtable(v), ref end
|
|
||||||
}
|
|
||||||
local function date_getdobj(v)
|
|
||||||
local o, r = (tmap[type(v)] or fnil)(v);
|
|
||||||
return (o and o:normalize() or error"invalid date time value"), r -- if r is true then o is a reference to a date obj
|
|
||||||
end
|
|
||||||
--#end -- not DATE_OBJECT_AFX
|
|
||||||
local function date_from(...)
|
|
||||||
local arg = pack(...)
|
|
||||||
local y, m, d = fix(arg[1]), getmontharg(arg[2]), fix(arg[3])
|
|
||||||
local h, r, s, t = tonumber(arg[4] or 0), tonumber(arg[5] or 0), tonumber(arg[6] or 0), tonumber(arg[7] or 0)
|
|
||||||
if y and m and d and h and r and s and t then
|
|
||||||
return date_new(makedaynum(y, m, d), makedayfrc(h, r, s, t)):normalize()
|
|
||||||
else
|
|
||||||
return date_error_arg()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[ THE DATE OBJECT METHODS ]]--
|
|
||||||
function dobj:normalize()
|
|
||||||
local dn, df = fix(self.daynum), self.dayfrc
|
|
||||||
self.daynum, self.dayfrc = dn + floor(df/TICKSPERDAY), mod(df, TICKSPERDAY)
|
|
||||||
return (dn >= DAYNUM_MIN and dn <= DAYNUM_MAX) and self or error("date beyond imposed limits:"..self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function dobj:getdate() local y, m, d = breakdaynum(self.daynum) return y, m+1, d end
|
|
||||||
function dobj:gettime() return breakdayfrc(self.dayfrc) end
|
|
||||||
|
|
||||||
function dobj:getclockhour() local h = self:gethours() return h>12 and mod(h,12) or (h==0 and 12 or h) end
|
|
||||||
|
|
||||||
function dobj:getyearday() return yearday(self.daynum) + 1 end
|
|
||||||
function dobj:getweekday() return weekday(self.daynum) + 1 end -- in lua weekday is sunday = 1, monday = 2 ...
|
|
||||||
|
|
||||||
function dobj:getyear() local r,_,_ = breakdaynum(self.daynum) return r end
|
|
||||||
function dobj:getmonth() local _,r,_ = breakdaynum(self.daynum) return r+1 end-- in lua month is 1 base
|
|
||||||
function dobj:getday() local _,_,r = breakdaynum(self.daynum) return r end
|
|
||||||
function dobj:gethours() return mod(floor(self.dayfrc/TICKSPERHOUR),HOURPERDAY) end
|
|
||||||
function dobj:getminutes() return mod(floor(self.dayfrc/TICKSPERMIN), MINPERHOUR) end
|
|
||||||
function dobj:getseconds() return mod(floor(self.dayfrc/TICKSPERSEC ),SECPERMIN) end
|
|
||||||
function dobj:getfracsec() return mod(floor(self.dayfrc/TICKSPERSEC ),SECPERMIN)+(mod(self.dayfrc,TICKSPERSEC)/TICKSPERSEC) end
|
|
||||||
function dobj:getticks(u) local x = mod(self.dayfrc,TICKSPERSEC) return u and ((x*u)/TICKSPERSEC) or x end
|
|
||||||
|
|
||||||
function dobj:getweeknumber(wdb)
|
|
||||||
local wd, yd = weekday(self.daynum), yearday(self.daynum)
|
|
||||||
if wdb then
|
|
||||||
wdb = tonumber(wdb)
|
|
||||||
if wdb then
|
|
||||||
wd = mod(wd-(wdb-1),7)-- shift the week day base
|
|
||||||
else
|
|
||||||
return date_error_arg()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return (yd < wd and 0) or (floor(yd/7) + ((mod(yd, 7)>=wd) and 1 or 0))
|
|
||||||
end
|
|
||||||
|
|
||||||
function dobj:getisoweekday() return mod(weekday(self.daynum)-1,7)+1 end -- sunday = 7, monday = 1 ...
|
|
||||||
function dobj:getisoweeknumber() return (isowy(self.daynum)) end
|
|
||||||
function dobj:getisoyear() return isoy(self.daynum) end
|
|
||||||
function dobj:getisodate()
|
|
||||||
local w, y = isowy(self.daynum)
|
|
||||||
return y, w, self:getisoweekday()
|
|
||||||
end
|
|
||||||
function dobj:setisoyear(y, w, d)
|
|
||||||
local cy, cw, cd = self:getisodate()
|
|
||||||
if y then cy = fix(tonumber(y))end
|
|
||||||
if w then cw = fix(tonumber(w))end
|
|
||||||
if d then cd = fix(tonumber(d))end
|
|
||||||
if cy and cw and cd then
|
|
||||||
self.daynum = makedaynum_isoywd(cy, cw, cd)
|
|
||||||
return self:normalize()
|
|
||||||
else
|
|
||||||
return date_error_arg()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function dobj:setisoweekday(d) return self:setisoyear(nil, nil, d) end
|
|
||||||
function dobj:setisoweeknumber(w,d) return self:setisoyear(nil, w, d) end
|
|
||||||
|
|
||||||
function dobj:setyear(y, m, d)
|
|
||||||
local cy, cm, cd = breakdaynum(self.daynum)
|
|
||||||
if y then cy = fix(tonumber(y))end
|
|
||||||
if m then cm = getmontharg(m) end
|
|
||||||
if d then cd = fix(tonumber(d))end
|
|
||||||
if cy and cm and cd then
|
|
||||||
self.daynum = makedaynum(cy, cm, cd)
|
|
||||||
return self:normalize()
|
|
||||||
else
|
|
||||||
return date_error_arg()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function dobj:setmonth(m, d)return self:setyear(nil, m, d) end
|
|
||||||
function dobj:setday(d) return self:setyear(nil, nil, d) end
|
|
||||||
|
|
||||||
function dobj:sethours(h, m, s, t)
|
|
||||||
local ch,cm,cs,ck = breakdayfrc(self.dayfrc)
|
|
||||||
ch, cm, cs, ck = tonumber(h or ch), tonumber(m or cm), tonumber(s or cs), tonumber(t or ck)
|
|
||||||
if ch and cm and cs and ck then
|
|
||||||
self.dayfrc = makedayfrc(ch, cm, cs, ck)
|
|
||||||
return self:normalize()
|
|
||||||
else
|
|
||||||
return date_error_arg()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function dobj:setminutes(m,s,t) return self:sethours(nil, m, s, t) end
|
|
||||||
function dobj:setseconds(s, t) return self:sethours(nil, nil, s, t) end
|
|
||||||
function dobj:setticks(t) return self:sethours(nil, nil, nil, t) end
|
|
||||||
|
|
||||||
function dobj:spanticks() return (self.daynum*TICKSPERDAY + self.dayfrc) end
|
|
||||||
function dobj:spanseconds() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERSEC end
|
|
||||||
function dobj:spanminutes() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERMIN end
|
|
||||||
function dobj:spanhours() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERHOUR end
|
|
||||||
function dobj:spandays() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERDAY end
|
|
||||||
|
|
||||||
function dobj:addyears(y, m, d)
|
|
||||||
local cy, cm, cd = breakdaynum(self.daynum)
|
|
||||||
if y then y = fix(tonumber(y))else y = 0 end
|
|
||||||
if m then m = fix(tonumber(m))else m = 0 end
|
|
||||||
if d then d = fix(tonumber(d))else d = 0 end
|
|
||||||
if y and m and d then
|
|
||||||
self.daynum = makedaynum(cy+y, cm+m, cd+d)
|
|
||||||
return self:normalize()
|
|
||||||
else
|
|
||||||
return date_error_arg()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function dobj:addmonths(m, d)
|
|
||||||
return self:addyears(nil, m, d)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function dobj_adddayfrc(self,n,pt,pd)
|
|
||||||
n = tonumber(n)
|
|
||||||
if n then
|
|
||||||
local x = floor(n/pd);
|
|
||||||
self.daynum = self.daynum + x;
|
|
||||||
self.dayfrc = self.dayfrc + (n-x*pd)*pt;
|
|
||||||
return self:normalize()
|
|
||||||
else
|
|
||||||
return date_error_arg()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
function dobj:adddays(n) return dobj_adddayfrc(self,n,TICKSPERDAY,1) end
|
|
||||||
function dobj:addhours(n) return dobj_adddayfrc(self,n,TICKSPERHOUR,HOURPERDAY) end
|
|
||||||
function dobj:addminutes(n) return dobj_adddayfrc(self,n,TICKSPERMIN,MINPERDAY) end
|
|
||||||
function dobj:addseconds(n) return dobj_adddayfrc(self,n,TICKSPERSEC,SECPERDAY) end
|
|
||||||
function dobj:addticks(n) return dobj_adddayfrc(self,n,1,TICKSPERDAY) end
|
|
||||||
local tvspec = {
|
|
||||||
-- Abbreviated weekday name (Sun)
|
|
||||||
['%a']=function(self) return sl_weekdays[weekday(self.daynum) + 7] end,
|
|
||||||
-- Full weekday name (Sunday)
|
|
||||||
['%A']=function(self) return sl_weekdays[weekday(self.daynum)] end,
|
|
||||||
-- Abbreviated month name (Dec)
|
|
||||||
['%b']=function(self) return sl_months[self:getmonth() - 1 + 12] end,
|
|
||||||
-- Full month name (December)
|
|
||||||
['%B']=function(self) return sl_months[self:getmonth() - 1] end,
|
|
||||||
-- Year/100 (19, 20, 30)
|
|
||||||
['%C']=function(self) return fmt("%.2d", fix(self:getyear()/100)) end,
|
|
||||||
-- The day of the month as a number (range 1 - 31)
|
|
||||||
['%d']=function(self) return fmt("%.2d", self:getday()) end,
|
|
||||||
-- year for ISO 8601 week, from 00 (79)
|
|
||||||
['%g']=function(self) return fmt("%.2d", mod(self:getisoyear() ,100)) end,
|
|
||||||
-- year for ISO 8601 week, from 0000 (1979)
|
|
||||||
['%G']=function(self) return fmt("%.4d", self:getisoyear()) end,
|
|
||||||
-- same as %b
|
|
||||||
['%h']=function(self) return self:fmt0("%b") end,
|
|
||||||
-- hour of the 24-hour day, from 00 (06)
|
|
||||||
['%H']=function(self) return fmt("%.2d", self:gethours()) end,
|
|
||||||
-- The hour as a number using a 12-hour clock (01 - 12)
|
|
||||||
['%I']=function(self) return fmt("%.2d", self:getclockhour()) end,
|
|
||||||
-- The day of the year as a number (001 - 366)
|
|
||||||
['%j']=function(self) return fmt("%.3d", self:getyearday()) end,
|
|
||||||
-- Month of the year, from 01 to 12
|
|
||||||
['%m']=function(self) return fmt("%.2d", self:getmonth()) end,
|
|
||||||
-- Minutes after the hour 55
|
|
||||||
['%M']=function(self) return fmt("%.2d", self:getminutes())end,
|
|
||||||
-- AM/PM indicator (AM)
|
|
||||||
['%p']=function(self) return sl_meridian[self:gethours() > 11 and 1 or -1] end, --AM/PM indicator (AM)
|
|
||||||
-- The second as a number (59, 20 , 01)
|
|
||||||
['%S']=function(self) return fmt("%.2d", self:getseconds()) end,
|
|
||||||
-- ISO 8601 day of the week, to 7 for Sunday (7, 1)
|
|
||||||
['%u']=function(self) return self:getisoweekday() end,
|
|
||||||
-- Sunday week of the year, from 00 (48)
|
|
||||||
['%U']=function(self) return fmt("%.2d", self:getweeknumber()) end,
|
|
||||||
-- ISO 8601 week of the year, from 01 (48)
|
|
||||||
['%V']=function(self) return fmt("%.2d", self:getisoweeknumber()) end,
|
|
||||||
-- The day of the week as a decimal, Sunday being 0
|
|
||||||
['%w']=function(self) return self:getweekday() - 1 end,
|
|
||||||
-- Monday week of the year, from 00 (48)
|
|
||||||
['%W']=function(self) return fmt("%.2d", self:getweeknumber(2)) end,
|
|
||||||
-- The year as a number without a century (range 00 to 99)
|
|
||||||
['%y']=function(self) return fmt("%.2d", mod(self:getyear() ,100)) end,
|
|
||||||
-- Year with century (2000, 1914, 0325, 0001)
|
|
||||||
['%Y']=function(self) return fmt("%.4d", self:getyear()) end,
|
|
||||||
-- Time zone offset, the date object is assumed local time (+1000, -0230)
|
|
||||||
['%z']=function(self) local b = -self:getbias(); local x = abs(b); return fmt("%s%.4d", b < 0 and "-" or "+", fix(x/60)*100 + floor(mod(x,60))) end,
|
|
||||||
-- Time zone name, the date object is assumed local time
|
|
||||||
['%Z']=function(self) return self:gettzname() end,
|
|
||||||
-- Misc --
|
|
||||||
-- Year, if year is in BCE, prints the BCE Year representation, otherwise result is similar to "%Y" (1 BCE, 40 BCE)
|
|
||||||
['%\b']=function(self) local x = self:getyear() return fmt("%.4d%s", x>0 and x or (-x+1), x>0 and "" or " BCE") end,
|
|
||||||
-- Seconds including fraction (59.998, 01.123)
|
|
||||||
['%\f']=function(self) local x = self:getfracsec() return fmt("%s%.9f",x >= 10 and "" or "0", x) end,
|
|
||||||
-- percent character %
|
|
||||||
['%%']=function(self) return "%" end,
|
|
||||||
-- Group Spec --
|
|
||||||
-- 12-hour time, from 01:00:00 AM (06:55:15 AM); same as "%I:%M:%S %p"
|
|
||||||
['%r']=function(self) return self:fmt0("%I:%M:%S %p") end,
|
|
||||||
-- hour:minute, from 01:00 (06:55); same as "%I:%M"
|
|
||||||
['%R']=function(self) return self:fmt0("%I:%M") end,
|
|
||||||
-- 24-hour time, from 00:00:00 (06:55:15); same as "%H:%M:%S"
|
|
||||||
['%T']=function(self) return self:fmt0("%H:%M:%S") end,
|
|
||||||
-- month/day/year from 01/01/00 (12/02/79); same as "%m/%d/%y"
|
|
||||||
['%D']=function(self) return self:fmt0("%m/%d/%y") end,
|
|
||||||
-- year-month-day (1979-12-02); same as "%Y-%m-%d"
|
|
||||||
['%F']=function(self) return self:fmt0("%Y-%m-%d") end,
|
|
||||||
-- The preferred date and time representation; same as "%x %X"
|
|
||||||
['%c']=function(self) return self:fmt0("%x %X") end,
|
|
||||||
-- The preferred date representation, same as "%a %b %d %\b"
|
|
||||||
['%x']=function(self) return self:fmt0("%a %b %d %\b") end,
|
|
||||||
-- The preferred time representation, same as "%H:%M:%\f"
|
|
||||||
['%X']=function(self) return self:fmt0("%H:%M:%\f") end,
|
|
||||||
-- GroupSpec --
|
|
||||||
-- Iso format, same as "%Y-%m-%dT%T"
|
|
||||||
['${iso}'] = function(self) return self:fmt0("%Y-%m-%dT%T") end,
|
|
||||||
-- http format, same as "%a, %d %b %Y %T GMT"
|
|
||||||
['${http}'] = function(self) return self:fmt0("%a, %d %b %Y %T GMT") end,
|
|
||||||
-- ctime format, same as "%a %b %d %T GMT %Y"
|
|
||||||
['${ctime}'] = function(self) return self:fmt0("%a %b %d %T GMT %Y") end,
|
|
||||||
-- RFC850 format, same as "%A, %d-%b-%y %T GMT"
|
|
||||||
['${rfc850}'] = function(self) return self:fmt0("%A, %d-%b-%y %T GMT") end,
|
|
||||||
-- RFC1123 format, same as "%a, %d %b %Y %T GMT"
|
|
||||||
['${rfc1123}'] = function(self) return self:fmt0("%a, %d %b %Y %T GMT") end,
|
|
||||||
-- asctime format, same as "%a %b %d %T %Y"
|
|
||||||
['${asctime}'] = function(self) return self:fmt0("%a %b %d %T %Y") end,
|
|
||||||
}
|
|
||||||
function dobj:fmt0(str) return (gsub(str, "%%[%a%%\b\f]", function(x) local f = tvspec[x];return (f and f(self)) or x end)) end
|
|
||||||
function dobj:fmt(str)
|
|
||||||
str = str or self.fmtstr or fmtstr
|
|
||||||
return self:fmt0((gmatch(str, "${%w+}")) and (gsub(str, "${%w+}", function(x)local f=tvspec[x];return (f and f(self)) or x end)) or str)
|
|
||||||
end
|
|
||||||
|
|
||||||
function dobj.__lt(a, b) if (a.daynum == b.daynum) then return (a.dayfrc < b.dayfrc) else return (a.daynum < b.daynum) end end
|
|
||||||
function dobj.__le(a, b) if (a.daynum == b.daynum) then return (a.dayfrc <= b.dayfrc) else return (a.daynum <= b.daynum) end end
|
|
||||||
function dobj.__eq(a, b)return (a.daynum == b.daynum) and (a.dayfrc == b.dayfrc) end
|
|
||||||
function dobj.__sub(a,b)
|
|
||||||
local d1, d2 = date_getdobj(a), date_getdobj(b)
|
|
||||||
local d0 = d1 and d2 and date_new(d1.daynum - d2.daynum, d1.dayfrc - d2.dayfrc)
|
|
||||||
return d0 and d0:normalize()
|
|
||||||
end
|
|
||||||
function dobj.__add(a,b)
|
|
||||||
local d1, d2 = date_getdobj(a), date_getdobj(b)
|
|
||||||
local d0 = d1 and d2 and date_new(d1.daynum + d2.daynum, d1.dayfrc + d2.dayfrc)
|
|
||||||
return d0 and d0:normalize()
|
|
||||||
end
|
|
||||||
function dobj.__concat(a, b) return tostring(a) .. tostring(b) end
|
|
||||||
function dobj:__tostring() return self:fmt() end
|
|
||||||
|
|
||||||
function dobj:copy() return date_new(self.daynum, self.dayfrc) end
|
|
||||||
|
|
||||||
--[[ THE LOCAL DATE OBJECT METHODS ]]--
|
|
||||||
function dobj:tolocal()
|
|
||||||
local dn,df = self.daynum, self.dayfrc
|
|
||||||
local bias = getbiasutc2(self)
|
|
||||||
if bias then
|
|
||||||
-- utc = local + bias; local = utc - bias
|
|
||||||
self.daynum = dn
|
|
||||||
self.dayfrc = df - bias*TICKSPERSEC
|
|
||||||
return self:normalize()
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function dobj:toutc()
|
|
||||||
local dn,df = self.daynum, self.dayfrc
|
|
||||||
local bias = getbiasloc2(dn, df)
|
|
||||||
if bias then
|
|
||||||
-- utc = local + bias;
|
|
||||||
self.daynum = dn
|
|
||||||
self.dayfrc = df + bias*TICKSPERSEC
|
|
||||||
return self:normalize()
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function dobj:getbias() return (getbiasloc2(self.daynum, self.dayfrc))/SECPERMIN end
|
|
||||||
|
|
||||||
function dobj:gettzname()
|
|
||||||
local _, tvu, _ = getbiasloc2(self.daynum, self.dayfrc)
|
|
||||||
return tvu and osdate("%Z",tvu) or ""
|
|
||||||
end
|
|
||||||
|
|
||||||
--#if not DATE_OBJECT_AFX then
|
|
||||||
function date.time(h, r, s, t)
|
|
||||||
h, r, s, t = tonumber(h or 0), tonumber(r or 0), tonumber(s or 0), tonumber(t or 0)
|
|
||||||
if h and r and s and t then
|
|
||||||
return date_new(DAYNUM_DEF, makedayfrc(h, r, s, t))
|
|
||||||
else
|
|
||||||
return date_error_arg()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function date:__call(...)
|
|
||||||
local arg = pack(...)
|
|
||||||
if arg.n > 1 then return (date_from(...))
|
|
||||||
elseif arg.n == 0 then return (date_getdobj(false))
|
|
||||||
else local o, r = date_getdobj(arg[1]); return r and o:copy() or o end
|
|
||||||
end
|
|
||||||
|
|
||||||
date.diff = dobj.__sub
|
|
||||||
|
|
||||||
function date.isleapyear(v)
|
|
||||||
local y = fix(v);
|
|
||||||
if not y then
|
|
||||||
y = date_getdobj(v)
|
|
||||||
y = y and y:getyear()
|
|
||||||
end
|
|
||||||
return isleapyear(y+0)
|
|
||||||
end
|
|
||||||
|
|
||||||
function date.epoch() return date_epoch:copy() end
|
|
||||||
|
|
||||||
function date.isodate(y,w,d) return date_new(makedaynum_isoywd(y + 0, w and (w+0) or 1, d and (d+0) or 1), 0) end
|
|
||||||
|
|
||||||
-- Internal functions
|
|
||||||
function date.fmt(str) if str then fmtstr = str end; return fmtstr end
|
|
||||||
function date.daynummin(n) DAYNUM_MIN = (n and n < DAYNUM_MAX) and n or DAYNUM_MIN return n and DAYNUM_MIN or date_new(DAYNUM_MIN, 0):normalize()end
|
|
||||||
function date.daynummax(n) DAYNUM_MAX = (n and n > DAYNUM_MIN) and n or DAYNUM_MAX return n and DAYNUM_MAX or date_new(DAYNUM_MAX, 0):normalize()end
|
|
||||||
function date.ticks(t) if t then setticks(t) end return TICKSPERSEC end
|
|
||||||
--#end -- not DATE_OBJECT_AFX
|
|
||||||
|
|
||||||
local tm = osdate("!*t", 0);
|
|
||||||
if tm then
|
|
||||||
date_epoch = date_new(makedaynum(tm.year, tm.month - 1, tm.day), makedayfrc(tm.hour, tm.min, tm.sec, 0))
|
|
||||||
-- the distance from our epoch to os epoch in daynum
|
|
||||||
DATE_EPOCH = date_epoch and date_epoch:spandays()
|
|
||||||
else -- error will be raise only if called!
|
|
||||||
date_epoch = setmetatable({},{__index = function() error("failed to get the epoch date") end})
|
|
||||||
end
|
|
||||||
|
|
||||||
--#if not DATE_OBJECT_AFX then
|
|
||||||
return date
|
|
||||||
--#else
|
|
||||||
--$return date_from
|
|
||||||
--#end
|
|
||||||
@ -1,135 +0,0 @@
|
|||||||
local sgsub = string.gsub
|
|
||||||
local tinsert = table.insert
|
|
||||||
local type = type
|
|
||||||
local ipairs = ipairs
|
|
||||||
local pairs = pairs
|
|
||||||
local mysql = require("resty.mysql")
|
|
||||||
local cjson = require("cjson")
|
|
||||||
local utils = require("app.libs.utils")
|
|
||||||
local config = require("app.config.config")
|
|
||||||
local DB = {}
|
|
||||||
|
|
||||||
function DB:new(conf)
|
|
||||||
conf = conf or config.mysql
|
|
||||||
local instance = {}
|
|
||||||
instance.conf = conf
|
|
||||||
setmetatable(instance, { __index = self})
|
|
||||||
return instance
|
|
||||||
end
|
|
||||||
|
|
||||||
function DB:exec(sql)
|
|
||||||
if not sql then
|
|
||||||
ngx.log(ngx.ERR, "sql parse error! please check")
|
|
||||||
return nil, "sql parse error! please check"
|
|
||||||
end
|
|
||||||
|
|
||||||
local conf = self.conf
|
|
||||||
local db, err = mysql:new()
|
|
||||||
if not db then
|
|
||||||
ngx.say("failed to instantiate mysql: ", err)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
db:set_timeout(conf.timeout) -- 1 sec
|
|
||||||
|
|
||||||
local ok, err, errno, sqlstate = db:connect(conf.connect_config)
|
|
||||||
if not ok then
|
|
||||||
ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
ngx.log(ngx.ERR, "connected to mysql, reused_times:", db:get_reused_times(), " sql:", sql)
|
|
||||||
|
|
||||||
db:query("SET NAMES utf8")
|
|
||||||
local res, err, errno, sqlstate = db:query(sql)
|
|
||||||
if not res then
|
|
||||||
ngx.log(ngx.ERR, "bad result: ", err, ": ", errno, ": ", sqlstate, ".")
|
|
||||||
end
|
|
||||||
|
|
||||||
local ok, err = db:set_keepalive(conf.pool_config.max_idle_timeout, conf.pool_config.pool_size)
|
|
||||||
if not ok then
|
|
||||||
ngx.say("failed to set keepalive: ", err)
|
|
||||||
end
|
|
||||||
|
|
||||||
return res, err, errno, sqlstate
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function DB:query(sql, params)
|
|
||||||
sql = self:parse_sql(sql, params)
|
|
||||||
return self:exec(sql)
|
|
||||||
end
|
|
||||||
|
|
||||||
function DB:select(sql, params)
|
|
||||||
return self:query(sql, params)
|
|
||||||
end
|
|
||||||
|
|
||||||
function DB:insert(sql, params)
|
|
||||||
local res, err, errno, sqlstate = self:query(sql, params)
|
|
||||||
if res and not err then
|
|
||||||
return res.insert_id, err
|
|
||||||
else
|
|
||||||
return res, err
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function DB:update(sql, params)
|
|
||||||
return self:query(sql, params)
|
|
||||||
end
|
|
||||||
|
|
||||||
function DB:delete(sql, params)
|
|
||||||
local res, err, errno, sqlstate = self:query(sql, params)
|
|
||||||
if res and not err then
|
|
||||||
return res.affected_rows, err
|
|
||||||
else
|
|
||||||
return res, err
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function split(str, delimiter)
|
|
||||||
if str==nil or str=='' or delimiter==nil then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local result = {}
|
|
||||||
for match in (str..delimiter):gmatch("(.-)"..delimiter) do
|
|
||||||
tinsert(result, match)
|
|
||||||
end
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function compose(t, params)
|
|
||||||
if t==nil or params==nil or type(t)~="table" or type(params)~="table" or #t~=#params+1 or #t==0 then
|
|
||||||
return nil
|
|
||||||
else
|
|
||||||
local result = t[1]
|
|
||||||
for i=1, #params do
|
|
||||||
result = result .. params[i].. t[i+1]
|
|
||||||
end
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function DB:parse_sql(sql, params)
|
|
||||||
if not params or not utils.table_is_array(params) or #params == 0 then
|
|
||||||
return sql
|
|
||||||
end
|
|
||||||
|
|
||||||
local new_params = {}
|
|
||||||
for i, v in ipairs(params) do
|
|
||||||
if v and type(v) == "string" then
|
|
||||||
v = ngx.quote_sql_str(v)
|
|
||||||
end
|
|
||||||
|
|
||||||
tinsert(new_params, v)
|
|
||||||
end
|
|
||||||
|
|
||||||
local t = split(sql,"?")
|
|
||||||
local sql = compose(t, new_params)
|
|
||||||
|
|
||||||
return sql
|
|
||||||
end
|
|
||||||
|
|
||||||
return DB
|
|
||||||
@ -1,334 +0,0 @@
|
|||||||
local inspect ={
|
|
||||||
_VERSION = 'inspect.lua 3.1.0',
|
|
||||||
_URL = 'http://github.com/kikito/inspect.lua',
|
|
||||||
_DESCRIPTION = 'human-readable representations of tables',
|
|
||||||
_LICENSE = [[
|
|
||||||
MIT LICENSE
|
|
||||||
|
|
||||||
Copyright (c) 2013 Enrique García Cota
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included
|
|
||||||
in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
||||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
||||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
||||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
]]
|
|
||||||
}
|
|
||||||
|
|
||||||
local tostring = tostring
|
|
||||||
|
|
||||||
inspect.KEY = setmetatable({}, {__tostring = function() return 'inspect.KEY' end})
|
|
||||||
inspect.METATABLE = setmetatable({}, {__tostring = function() return 'inspect.METATABLE' end})
|
|
||||||
|
|
||||||
local function rawpairs(t)
|
|
||||||
return next, t, nil
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Apostrophizes the string if it has quotes, but not aphostrophes
|
|
||||||
-- Otherwise, it returns a regular quoted string
|
|
||||||
local function smartQuote(str)
|
|
||||||
if str:match('"') and not str:match("'") then
|
|
||||||
return "'" .. str .. "'"
|
|
||||||
end
|
|
||||||
return '"' .. str:gsub('"', '\\"') .. '"'
|
|
||||||
end
|
|
||||||
|
|
||||||
-- \a => '\\a', \0 => '\\0', 31 => '\31'
|
|
||||||
local shortControlCharEscapes = {
|
|
||||||
["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n",
|
|
||||||
["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v"
|
|
||||||
}
|
|
||||||
local longControlCharEscapes = {} -- \a => nil, \0 => \000, 31 => \031
|
|
||||||
for i=0, 31 do
|
|
||||||
local ch = string.char(i)
|
|
||||||
if not shortControlCharEscapes[ch] then
|
|
||||||
shortControlCharEscapes[ch] = "\\"..i
|
|
||||||
longControlCharEscapes[ch] = string.format("\\%03d", i)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function escape(str)
|
|
||||||
return (str:gsub("\\", "\\\\")
|
|
||||||
:gsub("(%c)%f[0-9]", longControlCharEscapes)
|
|
||||||
:gsub("%c", shortControlCharEscapes))
|
|
||||||
end
|
|
||||||
|
|
||||||
local function isIdentifier(str)
|
|
||||||
return type(str) == 'string' and str:match( "^[_%a][_%a%d]*$" )
|
|
||||||
end
|
|
||||||
|
|
||||||
local function isSequenceKey(k, sequenceLength)
|
|
||||||
return type(k) == 'number'
|
|
||||||
and 1 <= k
|
|
||||||
and k <= sequenceLength
|
|
||||||
and math.floor(k) == k
|
|
||||||
end
|
|
||||||
|
|
||||||
local defaultTypeOrders = {
|
|
||||||
['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4,
|
|
||||||
['function'] = 5, ['userdata'] = 6, ['thread'] = 7
|
|
||||||
}
|
|
||||||
|
|
||||||
local function sortKeys(a, b)
|
|
||||||
local ta, tb = type(a), type(b)
|
|
||||||
|
|
||||||
-- strings and numbers are sorted numerically/alphabetically
|
|
||||||
if ta == tb and (ta == 'string' or ta == 'number') then return a < b end
|
|
||||||
|
|
||||||
local dta, dtb = defaultTypeOrders[ta], defaultTypeOrders[tb]
|
|
||||||
-- Two default types are compared according to the defaultTypeOrders table
|
|
||||||
if dta and dtb then return defaultTypeOrders[ta] < defaultTypeOrders[tb]
|
|
||||||
elseif dta then return true -- default types before custom ones
|
|
||||||
elseif dtb then return false -- custom types after default ones
|
|
||||||
end
|
|
||||||
|
|
||||||
-- custom types are sorted out alphabetically
|
|
||||||
return ta < tb
|
|
||||||
end
|
|
||||||
|
|
||||||
-- For implementation reasons, the behavior of rawlen & # is "undefined" when
|
|
||||||
-- tables aren't pure sequences. So we implement our own # operator.
|
|
||||||
local function getSequenceLength(t)
|
|
||||||
local len = 1
|
|
||||||
local v = rawget(t,len)
|
|
||||||
while v ~= nil do
|
|
||||||
len = len + 1
|
|
||||||
v = rawget(t,len)
|
|
||||||
end
|
|
||||||
return len - 1
|
|
||||||
end
|
|
||||||
|
|
||||||
local function getNonSequentialKeys(t)
|
|
||||||
local keys, keysLength = {}, 0
|
|
||||||
local sequenceLength = getSequenceLength(t)
|
|
||||||
for k,_ in rawpairs(t) do
|
|
||||||
if not isSequenceKey(k, sequenceLength) then
|
|
||||||
keysLength = keysLength + 1
|
|
||||||
keys[keysLength] = k
|
|
||||||
end
|
|
||||||
end
|
|
||||||
table.sort(keys, sortKeys)
|
|
||||||
return keys, keysLength, sequenceLength
|
|
||||||
end
|
|
||||||
|
|
||||||
local function countTableAppearances(t, tableAppearances)
|
|
||||||
tableAppearances = tableAppearances or {}
|
|
||||||
|
|
||||||
if type(t) == 'table' then
|
|
||||||
if not tableAppearances[t] then
|
|
||||||
tableAppearances[t] = 1
|
|
||||||
for k,v in rawpairs(t) do
|
|
||||||
countTableAppearances(k, tableAppearances)
|
|
||||||
countTableAppearances(v, tableAppearances)
|
|
||||||
end
|
|
||||||
countTableAppearances(getmetatable(t), tableAppearances)
|
|
||||||
else
|
|
||||||
tableAppearances[t] = tableAppearances[t] + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return tableAppearances
|
|
||||||
end
|
|
||||||
|
|
||||||
local copySequence = function(s)
|
|
||||||
local copy, len = {}, #s
|
|
||||||
for i=1, len do copy[i] = s[i] end
|
|
||||||
return copy, len
|
|
||||||
end
|
|
||||||
|
|
||||||
local function makePath(path, ...)
|
|
||||||
local keys = {...}
|
|
||||||
local newPath, len = copySequence(path)
|
|
||||||
for i=1, #keys do
|
|
||||||
newPath[len + i] = keys[i]
|
|
||||||
end
|
|
||||||
return newPath
|
|
||||||
end
|
|
||||||
|
|
||||||
local function processRecursive(process, item, path, visited)
|
|
||||||
if item == nil then return nil end
|
|
||||||
if visited[item] then return visited[item] end
|
|
||||||
|
|
||||||
local processed = process(item, path)
|
|
||||||
if type(processed) == 'table' then
|
|
||||||
local processedCopy = {}
|
|
||||||
visited[item] = processedCopy
|
|
||||||
local processedKey
|
|
||||||
|
|
||||||
for k,v in rawpairs(processed) do
|
|
||||||
processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited)
|
|
||||||
if processedKey ~= nil then
|
|
||||||
processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited)
|
|
||||||
if type(mt) ~= 'table' then mt = nil end -- ignore not nil/table __metatable field
|
|
||||||
setmetatable(processedCopy, mt)
|
|
||||||
processed = processedCopy
|
|
||||||
end
|
|
||||||
return processed
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
|
||||||
|
|
||||||
local Inspector = {}
|
|
||||||
local Inspector_mt = {__index = Inspector}
|
|
||||||
|
|
||||||
function Inspector:puts(...)
|
|
||||||
local args = {...}
|
|
||||||
local buffer = self.buffer
|
|
||||||
local len = #buffer
|
|
||||||
for i=1, #args do
|
|
||||||
len = len + 1
|
|
||||||
buffer[len] = args[i]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Inspector:down(f)
|
|
||||||
self.level = self.level + 1
|
|
||||||
f()
|
|
||||||
self.level = self.level - 1
|
|
||||||
end
|
|
||||||
|
|
||||||
function Inspector:tabify()
|
|
||||||
self:puts(self.newline, string.rep(self.indent, self.level))
|
|
||||||
end
|
|
||||||
|
|
||||||
function Inspector:alreadyVisited(v)
|
|
||||||
return self.ids[v] ~= nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function Inspector:getId(v)
|
|
||||||
local id = self.ids[v]
|
|
||||||
if not id then
|
|
||||||
local tv = type(v)
|
|
||||||
id = (self.maxIds[tv] or 0) + 1
|
|
||||||
self.maxIds[tv] = id
|
|
||||||
self.ids[v] = id
|
|
||||||
end
|
|
||||||
return tostring(id)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Inspector:putKey(k)
|
|
||||||
if isIdentifier(k) then return self:puts(k) end
|
|
||||||
self:puts("[")
|
|
||||||
self:putValue(k)
|
|
||||||
self:puts("]")
|
|
||||||
end
|
|
||||||
|
|
||||||
function Inspector:putTable(t)
|
|
||||||
if t == inspect.KEY or t == inspect.METATABLE then
|
|
||||||
self:puts(tostring(t))
|
|
||||||
elseif self:alreadyVisited(t) then
|
|
||||||
self:puts('<table ', self:getId(t), '>')
|
|
||||||
elseif self.level >= self.depth then
|
|
||||||
self:puts('{...}')
|
|
||||||
else
|
|
||||||
if self.tableAppearances[t] > 1 then self:puts('<', self:getId(t), '>') end
|
|
||||||
|
|
||||||
local nonSequentialKeys, nonSequentialKeysLength, sequenceLength = getNonSequentialKeys(t)
|
|
||||||
local mt = getmetatable(t)
|
|
||||||
|
|
||||||
self:puts('{')
|
|
||||||
self:down(function()
|
|
||||||
local count = 0
|
|
||||||
for i=1, sequenceLength do
|
|
||||||
if count > 0 then self:puts(',') end
|
|
||||||
self:puts(' ')
|
|
||||||
self:putValue(t[i])
|
|
||||||
count = count + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
for i=1, nonSequentialKeysLength do
|
|
||||||
local k = nonSequentialKeys[i]
|
|
||||||
if count > 0 then self:puts(',') end
|
|
||||||
self:tabify()
|
|
||||||
self:putKey(k)
|
|
||||||
self:puts(' = ')
|
|
||||||
self:putValue(t[k])
|
|
||||||
count = count + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if type(mt) == 'table' then
|
|
||||||
if count > 0 then self:puts(',') end
|
|
||||||
self:tabify()
|
|
||||||
self:puts('<metatable> = ')
|
|
||||||
self:putValue(mt)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
if nonSequentialKeysLength > 0 or type(mt) == 'table' then -- result is multi-lined. Justify closing }
|
|
||||||
self:tabify()
|
|
||||||
elseif sequenceLength > 0 then -- array tables have one extra space before closing }
|
|
||||||
self:puts(' ')
|
|
||||||
end
|
|
||||||
|
|
||||||
self:puts('}')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Inspector:putValue(v)
|
|
||||||
local tv = type(v)
|
|
||||||
|
|
||||||
if tv == 'string' then
|
|
||||||
self:puts(smartQuote(escape(v)))
|
|
||||||
elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or
|
|
||||||
tv == 'cdata' or tv == 'ctype' then
|
|
||||||
self:puts(tostring(v))
|
|
||||||
elseif tv == 'table' then
|
|
||||||
self:putTable(v)
|
|
||||||
else
|
|
||||||
self:puts('<', tv, ' ', self:getId(v), '>')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
|
||||||
|
|
||||||
function inspect.inspect(root, options)
|
|
||||||
options = options or {}
|
|
||||||
|
|
||||||
local depth = options.depth or math.huge
|
|
||||||
local newline = options.newline or '\n'
|
|
||||||
local indent = options.indent or ' '
|
|
||||||
local process = options.process
|
|
||||||
|
|
||||||
if process then
|
|
||||||
root = processRecursive(process, root, {}, {})
|
|
||||||
end
|
|
||||||
|
|
||||||
local inspector = setmetatable({
|
|
||||||
depth = depth,
|
|
||||||
level = 0,
|
|
||||||
buffer = {},
|
|
||||||
ids = {},
|
|
||||||
maxIds = {},
|
|
||||||
newline = newline,
|
|
||||||
indent = indent,
|
|
||||||
tableAppearances = countTableAppearances(root)
|
|
||||||
}, Inspector_mt)
|
|
||||||
|
|
||||||
inspector:putValue(root)
|
|
||||||
|
|
||||||
return table.concat(inspector.buffer)
|
|
||||||
end
|
|
||||||
|
|
||||||
setmetatable(inspect, { __call = function(_, ...) return inspect.inspect(...) end })
|
|
||||||
|
|
||||||
return inspect
|
|
||||||
|
|
||||||
@ -1,281 +0,0 @@
|
|||||||
#!/usr/bin/env lua
|
|
||||||
local ngx = require('ngx')
|
|
||||||
|
|
||||||
local setmetatable = setmetatable
|
|
||||||
local byte = string.byte
|
|
||||||
local match = string.match
|
|
||||||
local rawget = rawget
|
|
||||||
local cjson = require "cjson"
|
|
||||||
|
|
||||||
local ok, new_tab = pcall(require, "table.new")
|
|
||||||
if not ok or type(new_tab) ~= "function" then
|
|
||||||
new_tab = function(narr, nrec) return {} end
|
|
||||||
end
|
|
||||||
|
|
||||||
local _M = new_tab(0, 54)
|
|
||||||
|
|
||||||
local mt = { __index = _M }
|
|
||||||
|
|
||||||
_M._VERSION = '0.01'
|
|
||||||
|
|
||||||
-- 测试TOKEN 及 地址
|
|
||||||
local token = "cc87f3c77747bccbaaee35006da1ebb65e0bad57"
|
|
||||||
local ipIpUrl = "http://freeapi.ipip.net/"
|
|
||||||
|
|
||||||
-- 切割字符串
|
|
||||||
local function split(s, p)
|
|
||||||
local rt = {}
|
|
||||||
string.gsub(s, '[^' .. p .. ']+', function(w) table.insert(rt, w) end)
|
|
||||||
return rt
|
|
||||||
end
|
|
||||||
|
|
||||||
-- 拓展
|
|
||||||
string.split = function(str, pattern)
|
|
||||||
pattern = pattern or "[^%s]+"
|
|
||||||
if pattern:len() == 0 then pattern = "[^%s]+" end
|
|
||||||
local parts = { __index = table.insert }
|
|
||||||
setmetatable(parts, parts)
|
|
||||||
str:gsub(pattern, parts)
|
|
||||||
setmetatable(parts, nil)
|
|
||||||
parts.__index = nil
|
|
||||||
return parts
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- 转成整型
|
|
||||||
local function bit32lshift(b, disp)
|
|
||||||
return (b * 2 ^ disp) % 2 ^ 32
|
|
||||||
end
|
|
||||||
|
|
||||||
-- 转换ip
|
|
||||||
local function byteToUint32(a, b, c, d)
|
|
||||||
local _int = 0
|
|
||||||
if a then
|
|
||||||
_int = _int + bit32lshift(a, 24)
|
|
||||||
end
|
|
||||||
_int = _int + bit32lshift(b, 16)
|
|
||||||
_int = _int + bit32lshift(c, 8)
|
|
||||||
_int = _int + d
|
|
||||||
if _int >= 0 then
|
|
||||||
return _int
|
|
||||||
else
|
|
||||||
return _int + math.pow(2, 32)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- 返回数据模版
|
|
||||||
local response_template = {
|
|
||||||
"country", -- // 国家
|
|
||||||
"city", -- // 省会或直辖市(国内)
|
|
||||||
"region", -- // 地区或城市 (国内)
|
|
||||||
"place", -- // 学校或单位 (国内)
|
|
||||||
"operator", -- // 运营商字段(只有购买了带有运营商版本的数据库才会有)
|
|
||||||
"latitude", -- // 纬度 (每日版本提供)
|
|
||||||
"longitude", -- // 经度 (每日版本提供)
|
|
||||||
"timeZone", -- // 时区一, 可能不存在 (每日版本提供)
|
|
||||||
"timeZoneCode", -- // 时区二, 可能不存在 (每日版本提供)
|
|
||||||
"administrativeAreaCode", -- // 中国行政区划代码 (每日版本提供)
|
|
||||||
"internationalPhoneCode", -- // 国际电话代码 (每日版本提供)
|
|
||||||
"countryTwoDigitCode", -- // 国家二位代码 (每日版本提供)
|
|
||||||
"worldContinentCode" -- // 世界大洲代码 (每日版本提供)
|
|
||||||
}
|
|
||||||
|
|
||||||
-- 转成 table类型
|
|
||||||
local function toTable(location)
|
|
||||||
local response = {}
|
|
||||||
for k, v in ipairs(location) do
|
|
||||||
response[response_template[k]] = v
|
|
||||||
end
|
|
||||||
|
|
||||||
return response
|
|
||||||
end
|
|
||||||
|
|
||||||
-- 发送请求
|
|
||||||
local function sendRequest(url, method, body, headers)
|
|
||||||
local http = require "resty.http"
|
|
||||||
local httpc = http.new()
|
|
||||||
local res, err = httpc:request_uri(url, {
|
|
||||||
method = method,
|
|
||||||
body = body,
|
|
||||||
headers = headers
|
|
||||||
})
|
|
||||||
|
|
||||||
if not res then
|
|
||||||
ngx.log(ngx.ERR, "failed to request: " .. err .. url)
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
if 200 ~= res.status then
|
|
||||||
ngx.log(ngx.ERR, res.status)
|
|
||||||
return nil, res.status
|
|
||||||
end
|
|
||||||
|
|
||||||
return res.body
|
|
||||||
end
|
|
||||||
|
|
||||||
-- 初始化
|
|
||||||
function _M.new(self, address, token)
|
|
||||||
return setmetatable({ _ipAddress = address, _token = token, _ipBinaryFilePath = require("config.app").ip_binary_file_path }, mt)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- 从文件获取地区信息
|
|
||||||
function _M.ipLocation(self, ipstr)
|
|
||||||
local ipBinaryFilePath = rawget(self, "_ipBinaryFilePath")
|
|
||||||
if not ipBinaryFilePath then
|
|
||||||
ngx.log(ngx.ERR, ipBinaryFilePath)
|
|
||||||
return nil, " file ptah not initialized"
|
|
||||||
end
|
|
||||||
|
|
||||||
local ip1, ip2, ip3, ip4 = match(ipstr, "(%d+).(%d+).(%d+).(%d+)")
|
|
||||||
local ip_uint32 = byteToUint32(ip1, ip2, ip3, ip4)
|
|
||||||
local file = io.open(ipBinaryFilePath)
|
|
||||||
if file == nil then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local str = file:read(4)
|
|
||||||
local offset_len = byteToUint32(byte(str, 1), byte(str, 2), byte(str, 3), byte(str, 4))
|
|
||||||
|
|
||||||
local indexBuffer = file:read(offset_len - 4)
|
|
||||||
|
|
||||||
local tmp_offset = ip1 * 4
|
|
||||||
local start_len = byteToUint32(byte(indexBuffer, tmp_offset + 4), byte(indexBuffer, tmp_offset + 3), byte(indexBuffer, tmp_offset + 2), byte(indexBuffer, tmp_offset + 1))
|
|
||||||
|
|
||||||
local max_comp_len = offset_len - 1028
|
|
||||||
local start = start_len * 8 + 1024 + 1
|
|
||||||
local index_offset = -1
|
|
||||||
local index_length = -1
|
|
||||||
while start < max_comp_len do
|
|
||||||
local find_uint32 = byteToUint32(byte(indexBuffer, start), byte(indexBuffer, start + 1), byte(indexBuffer, start + 2), byte(indexBuffer, start + 3))
|
|
||||||
if ip_uint32 <= find_uint32 then
|
|
||||||
index_offset = byteToUint32(0, byte(indexBuffer, start + 6), byte(indexBuffer, start + 5), byte(indexBuffer, start + 4))
|
|
||||||
index_length = byte(indexBuffer, start + 7)
|
|
||||||
break
|
|
||||||
end
|
|
||||||
start = start + 8
|
|
||||||
end
|
|
||||||
|
|
||||||
if index_offset == -1 or index_length == -1 then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local offset = offset_len + index_offset - 1024
|
|
||||||
|
|
||||||
file:seek("set", offset)
|
|
||||||
|
|
||||||
return file:read(index_length)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- 获取所有信息
|
|
||||||
function _M.location(self)
|
|
||||||
local ipAddress = rawget(self, "_ipAddress")
|
|
||||||
if not ipAddress then
|
|
||||||
return nil, "not initialized"
|
|
||||||
end
|
|
||||||
|
|
||||||
local address = self:ipLocation(ipAddress)
|
|
||||||
if not address then
|
|
||||||
ngx.log(ngx.ERR, { "ip address data nil" })
|
|
||||||
return nil, "ip address data nil"
|
|
||||||
end
|
|
||||||
|
|
||||||
if type(address) == "string" then
|
|
||||||
return toTable(split(address, "%s+"))
|
|
||||||
end
|
|
||||||
|
|
||||||
return address
|
|
||||||
end
|
|
||||||
|
|
||||||
-- 通过api获取
|
|
||||||
function _M.locationApi(self, sid, uid)
|
|
||||||
local ipAddress = rawget(self, "_ipAddress")
|
|
||||||
if not ipAddress then
|
|
||||||
return nil, "not initialized"
|
|
||||||
end
|
|
||||||
|
|
||||||
local _token = rawget(self, "_token")
|
|
||||||
|
|
||||||
local myToken = (_token and _token) or token
|
|
||||||
|
|
||||||
local sign, err = ngx.md5("addr=" .. ipAddress .. "&token=" .. myToken)
|
|
||||||
|
|
||||||
if not sign then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
local url = ipIpUrl .. "find"
|
|
||||||
|
|
||||||
local headers = {
|
|
||||||
["Token"] = myToken
|
|
||||||
}
|
|
||||||
|
|
||||||
local params = "addr=" .. ipAddress .. "&sid=" .. sid .. "&uid=" .. uid .. "&sig=" .. sign
|
|
||||||
|
|
||||||
local body, err = sendRequest(url, "GET", params, headers)
|
|
||||||
|
|
||||||
if not body or #body < 1 then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
-- local body = [[{"ret":"ok","data":["中国","天津","天津","","鹏博士","39.128399","117.185112","Asia/Shanghai","UTC+8","120000","86","CN","AP"]}]]
|
|
||||||
local response = cjson.decode(body)
|
|
||||||
|
|
||||||
if not response.data then
|
|
||||||
return response
|
|
||||||
end
|
|
||||||
|
|
||||||
return toTable(response.data)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- 通过免费的api获取
|
|
||||||
function _M.locationApiFree(self)
|
|
||||||
local ipAddress = rawget(self, "_ipAddress")
|
|
||||||
if not ipAddress then
|
|
||||||
return nil, "not initialized"
|
|
||||||
end
|
|
||||||
|
|
||||||
local url = ipIpUrl .. ipAddress
|
|
||||||
|
|
||||||
local headers = {
|
|
||||||
["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
|
||||||
["Cache-Control"] = "no-cache",
|
|
||||||
["Connection"] = "keep-alive",
|
|
||||||
["User-Agent"] = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
|
|
||||||
["Content-Type"] = "application/x-www-form-urlencoded",
|
|
||||||
}
|
|
||||||
|
|
||||||
local body, err = sendRequest(url, "GET", "", headers)
|
|
||||||
|
|
||||||
if not body then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
return toTable(cjson.decode(body))
|
|
||||||
end
|
|
||||||
|
|
||||||
-- 获取当前可访问状态
|
|
||||||
function _M.apiStatus(self, token)
|
|
||||||
if not token then
|
|
||||||
local token = rawget(self, "_token")
|
|
||||||
if not token then
|
|
||||||
return nil, "not initialized"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local url = ipIpUrl .. "find_status"
|
|
||||||
|
|
||||||
local headers = {
|
|
||||||
["Token"] = token
|
|
||||||
}
|
|
||||||
|
|
||||||
local body, err = sendRequest(url, "GET", "", headers)
|
|
||||||
|
|
||||||
if not body then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
return cjson.decode(body)
|
|
||||||
end
|
|
||||||
|
|
||||||
return _M
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
local table = table
|
|
||||||
|
|
||||||
-- 日志级别
|
|
||||||
local log_level = {
|
|
||||||
LOG_DEFAULT = 1,
|
|
||||||
LOG_TRACE = 1,
|
|
||||||
LOG_DEBUG = 2,
|
|
||||||
LOG_INFO = 3,
|
|
||||||
LOG_WARN = 4,
|
|
||||||
LOG_ERROR = 5,
|
|
||||||
LOG_FATAL = 6,
|
|
||||||
}
|
|
||||||
|
|
||||||
local defaultLevel = log_level.LOG_DEBUG
|
|
||||||
|
|
||||||
-- 错误日志 --
|
|
||||||
local function logger(str, level, color)
|
|
||||||
return function (...)
|
|
||||||
if level >= defaultLevel then
|
|
||||||
local info = table.pack(...)
|
|
||||||
info[#info+1] = "\n"
|
|
||||||
info[#info+1] = "\x1b[0m"
|
|
||||||
ngx.log(ngx.ERR, string.format("%s%s", color, str), table.unpack(info))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local M = {
|
|
||||||
TRACE = logger("[trace]", log_level.LOG_TRACE, "\x1b[35m"),
|
|
||||||
DEBUG = logger("[debug]", log_level.LOG_DEBUG, "\x1b[32m"),
|
|
||||||
INFO = logger("[info]", log_level.LOG_INFO, "\x1b[34m"),
|
|
||||||
WARN = logger("[warning]", log_level.LOG_WARN, "\x1b[33m"),
|
|
||||||
ERROR = logger("[error]", log_level.LOG_ERROR, "\x1b[31m"),
|
|
||||||
FATAL = logger("[fatal]", log_level.LOG_FATAL,"\x1b[31m")
|
|
||||||
}
|
|
||||||
|
|
||||||
-- 错误日志 --
|
|
||||||
|
|
||||||
setmetatable(M, {
|
|
||||||
__call = function(t)
|
|
||||||
for k, v in pairs(t) do
|
|
||||||
_G[k] = v
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
|
|
||||||
M()
|
|
||||||
|
|
||||||
return M
|
|
||||||
@ -1,86 +0,0 @@
|
|||||||
local require = require
|
|
||||||
local ffi = require "ffi"
|
|
||||||
local ffi_cdef = ffi.cdef
|
|
||||||
local ffi_new = ffi.new
|
|
||||||
local ffi_str = ffi.string
|
|
||||||
local ffi_typeof = ffi.typeof
|
|
||||||
local C = ffi.C
|
|
||||||
local type = type
|
|
||||||
local random = math.random
|
|
||||||
local randomseed = math.randomseed
|
|
||||||
local concat = table.concat
|
|
||||||
local tostring = tostring
|
|
||||||
local pcall = pcall
|
|
||||||
|
|
||||||
ffi_cdef[[
|
|
||||||
typedef unsigned char u_char;
|
|
||||||
u_char * ngx_hex_dump(u_char *dst, const u_char *src, size_t len);
|
|
||||||
int RAND_bytes(u_char *buf, int num);
|
|
||||||
]]
|
|
||||||
|
|
||||||
local ok, new_tab = pcall(require, "table.new")
|
|
||||||
if not ok then
|
|
||||||
new_tab = function () return {} end
|
|
||||||
end
|
|
||||||
|
|
||||||
local alnum = {
|
|
||||||
'A','B','C','D','E','F','G','H','I','J','K','L','M',
|
|
||||||
'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
|
|
||||||
'a','b','c','d','e','f','g','h','i','j','k','l','m',
|
|
||||||
'n','o','p','q','r','s','t','u','v','w','x','y','z',
|
|
||||||
'0','1','2','3','4','5','6','7','8','9'
|
|
||||||
}
|
|
||||||
|
|
||||||
local t = ffi_typeof "uint8_t[?]"
|
|
||||||
|
|
||||||
local function bytes(len, format)
|
|
||||||
local s = ffi_new(t, len)
|
|
||||||
C.RAND_bytes(s, len)
|
|
||||||
if not s then return nil end
|
|
||||||
if format == "hex" then
|
|
||||||
local b = ffi_new(t, len * 2)
|
|
||||||
C.ngx_hex_dump(b, s, len)
|
|
||||||
return ffi_str(b, len * 2), true
|
|
||||||
else
|
|
||||||
return ffi_str(s, len), true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function seed()
|
|
||||||
local a,b,c,d = bytes(4):byte(1, 4)
|
|
||||||
return randomseed(a * 0x1000000 + b * 0x10000 + c * 0x100 + d)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function number(min, max, reseed)
|
|
||||||
if reseed then seed() end
|
|
||||||
if min and max then return random(min, max)
|
|
||||||
elseif min then return random(min)
|
|
||||||
else return random() end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function token(len, chars, sep)
|
|
||||||
chars = chars or alnum
|
|
||||||
local count
|
|
||||||
local token = new_tab(len, 0)
|
|
||||||
if type(chars) ~= "table" then
|
|
||||||
chars = tostring(chars)
|
|
||||||
count = #chars
|
|
||||||
local n
|
|
||||||
for i=1,len do
|
|
||||||
n = number(1, count)
|
|
||||||
token[i] = chars:sub(n, n)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
count = #chars
|
|
||||||
for i=1,len do token[i] = chars[number(1, count)] end
|
|
||||||
end
|
|
||||||
return concat(token, sep)
|
|
||||||
end
|
|
||||||
|
|
||||||
seed()
|
|
||||||
|
|
||||||
return {
|
|
||||||
bytes = bytes,
|
|
||||||
number = number,
|
|
||||||
token = token
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,44 +0,0 @@
|
|||||||
local rawget, rawset, setmetatable =
|
|
||||||
rawget, rawset, setmetatable
|
|
||||||
|
|
||||||
local str_lower = string.lower
|
|
||||||
|
|
||||||
local _M = {
|
|
||||||
_VERSION = '0.12',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
-- Returns an empty headers table with internalised case normalisation.
|
|
||||||
function _M.new()
|
|
||||||
local mt = {
|
|
||||||
normalised = {},
|
|
||||||
}
|
|
||||||
|
|
||||||
mt.__index = function(t, k)
|
|
||||||
return rawget(t, mt.normalised[str_lower(k)])
|
|
||||||
end
|
|
||||||
|
|
||||||
mt.__newindex = function(t, k, v)
|
|
||||||
local k_normalised = str_lower(k)
|
|
||||||
|
|
||||||
-- First time seeing this header field?
|
|
||||||
if not mt.normalised[k_normalised] then
|
|
||||||
-- Create a lowercased entry in the metatable proxy, with the value
|
|
||||||
-- of the given field case
|
|
||||||
mt.normalised[k_normalised] = k
|
|
||||||
|
|
||||||
-- Set the header using the given field case
|
|
||||||
rawset(t, k, v)
|
|
||||||
else
|
|
||||||
-- We're being updated just with a different field case. Use the
|
|
||||||
-- normalised metatable proxy to give us the original key case, and
|
|
||||||
-- perorm a rawset() to update the value.
|
|
||||||
rawset(t, mt.normalised[k_normalised], v)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return setmetatable({}, mt)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
return _M
|
|
||||||
@ -1,146 +0,0 @@
|
|||||||
local cbson = require("cbson")
|
|
||||||
local connection = require("resty.moongoo.connection")
|
|
||||||
local database = require("resty.moongoo.database")
|
|
||||||
local parse_uri = require("resty.moongoo.utils").parse_uri
|
|
||||||
local auth_scram = require("resty.moongoo.auth.scram")
|
|
||||||
local auth_cr = require("resty.moongoo.auth.cr")
|
|
||||||
|
|
||||||
|
|
||||||
local _M = {}
|
|
||||||
|
|
||||||
_M._VERSION = '0.1'
|
|
||||||
_M.NAME = 'Moongoo'
|
|
||||||
|
|
||||||
|
|
||||||
local mt = { __index = _M }
|
|
||||||
|
|
||||||
function _M.new(uri)
|
|
||||||
local conninfo = parse_uri(uri)
|
|
||||||
|
|
||||||
if not conninfo.scheme or conninfo.scheme ~= "mongodb" then
|
|
||||||
return nil, "Wrong scheme in connection uri"
|
|
||||||
end
|
|
||||||
|
|
||||||
local auth_algo = conninfo.query and conninfo.query.authMechanism or "SCRAM-SHA-1"
|
|
||||||
local w = conninfo.query and conninfo.query.w or 1
|
|
||||||
local wtimeout = conninfo.query and conninfo.query.wtimeoutMS or 1000
|
|
||||||
local journal = conninfo.query and conninfo.query.journal or false
|
|
||||||
local ssl = conninfo.query and conninfo.query.ssl or false
|
|
||||||
|
|
||||||
local stimeout = conninfo.query.socketTimeoutMS and conninfo.query.socketTimeoutMS or nil
|
|
||||||
|
|
||||||
return setmetatable({
|
|
||||||
connection = nil;
|
|
||||||
w = w;
|
|
||||||
wtimeout = wtimeout;
|
|
||||||
journal = journal;
|
|
||||||
stimeout = stimeout;
|
|
||||||
hosts = conninfo.hosts;
|
|
||||||
default_db = conninfo.database;
|
|
||||||
user = conninfo.user or nil;
|
|
||||||
password = conninfo.password or "";
|
|
||||||
auth_algo = auth_algo,
|
|
||||||
ssl = ssl,
|
|
||||||
version = nil
|
|
||||||
}, mt)
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M._auth(self, protocol)
|
|
||||||
if not self.user then return 1 end
|
|
||||||
|
|
||||||
if not protocol or protocol < cbson.int(3) or self.auth_algo == "MONGODB-CR" then
|
|
||||||
return auth_cr(self:db(self.default_db), self.user, self.password)
|
|
||||||
else
|
|
||||||
return auth_scram(self:db(self.default_db), self.user, self.password)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.connect(self)
|
|
||||||
if self.connection then return self end
|
|
||||||
|
|
||||||
-- foreach host
|
|
||||||
for k, v in ipairs(self.hosts) do
|
|
||||||
-- connect
|
|
||||||
self.connection, err = connection.new(v.host, v.port, self.stimeout)
|
|
||||||
if not self.connection then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
local status, err = self.connection:connect()
|
|
||||||
if status then
|
|
||||||
if self.ssl then
|
|
||||||
self.connection:handshake()
|
|
||||||
end
|
|
||||||
if not self.version then
|
|
||||||
query = self:db(self.default_db):_cmd({ buildInfo = 1 })
|
|
||||||
if query then
|
|
||||||
self.version = query.version
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local ismaster = self:db("admin"):_cmd("ismaster")
|
|
||||||
if ismaster and ismaster.ismaster then
|
|
||||||
-- auth
|
|
||||||
local r, err = self:_auth(ismaster.maxWireVersion)
|
|
||||||
if not r then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
return self
|
|
||||||
else
|
|
||||||
-- try to connect to master
|
|
||||||
if ismaster.primary then
|
|
||||||
local mhost, mport
|
|
||||||
string.gsub(ismaster.primary, "([^:]+):([^:]+)", function(host,port) mhost=host; mport=port end)
|
|
||||||
self.connection:close()
|
|
||||||
self.connection = nil
|
|
||||||
self.connection, err = connection.new(mhost, mport, self.stimeout)
|
|
||||||
if not self.connection then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
local status, err = self.connection:connect()
|
|
||||||
if not status then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
if self.ssl then
|
|
||||||
self.connection:handshake()
|
|
||||||
end
|
|
||||||
if not self.version then
|
|
||||||
query = self:db(self.default_db):_cmd({ buildInfo = 1 })
|
|
||||||
if query then
|
|
||||||
self.version = query.version
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local ismaster = self:db("admin"):_cmd("ismaster")
|
|
||||||
if ismaster and ismaster.ismaster then
|
|
||||||
-- auth
|
|
||||||
local r, err = self:_auth(ismaster.maxWireVersion)
|
|
||||||
if not r then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
return self
|
|
||||||
else
|
|
||||||
return nil, "Can't connect to master server"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return nil, "Can't connect to any of servers"
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.close(self)
|
|
||||||
if self.connection then
|
|
||||||
self.connection:close()
|
|
||||||
self.connection = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.get_reused_times(self)
|
|
||||||
return self.connection:get_reused_times()
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.db(self, dbname)
|
|
||||||
return database.new(dbname, self)
|
|
||||||
end
|
|
||||||
|
|
||||||
return _M
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
local pass_digest = require("resty.moongoo.utils").pass_digest
|
|
||||||
|
|
||||||
local b64 = ngx and ngx.encode_base64 or require("mime").b64
|
|
||||||
local unb64 = ngx and ngx.decode_base64 or require("mime").unb64
|
|
||||||
|
|
||||||
local md5 = ngx and ngx.md5 or function(str) return require("crypto").digest("md5", str) end
|
|
||||||
|
|
||||||
local cbson = require("cbson")
|
|
||||||
|
|
||||||
|
|
||||||
local function auth(db, username, password)
|
|
||||||
local r, err = db:_cmd("getnonce", {})
|
|
||||||
if not r then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
local digest = md5( r.nonce .. username .. pass_digest ( username , password ) )
|
|
||||||
|
|
||||||
r, err = db:_cmd("authenticate", {
|
|
||||||
user = username ;
|
|
||||||
nonce = r.nonce ;
|
|
||||||
key = digest ;
|
|
||||||
})
|
|
||||||
|
|
||||||
if not r then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
return auth
|
|
||||||
@ -1,103 +0,0 @@
|
|||||||
local Hi = require("resty.moongoo.utils").pbkdf2_hmac_sha1
|
|
||||||
local saslprep = require("resty.moongoo.utils").saslprep
|
|
||||||
local pass_digest = require("resty.moongoo.utils").pass_digest
|
|
||||||
local xor_bytestr = require("resty.moongoo.utils").xor_bytestr
|
|
||||||
|
|
||||||
local b64 = ngx and ngx.encode_base64 or require("mime").b64
|
|
||||||
local unb64 = ngx and ngx.decode_base64 or require("mime").unb64
|
|
||||||
|
|
||||||
local hmac_sha1 = ngx and ngx.hmac_sha1 or function(str, key) return require("crypto").hmac.digest("sha1", key, str, true) end
|
|
||||||
local sha1_bin = ngx and ngx.sha1_bin or function(str) return require("crypto").digest("sha1", str, true) end
|
|
||||||
|
|
||||||
local cbson = require("cbson")
|
|
||||||
|
|
||||||
|
|
||||||
local function auth(db, username, password)
|
|
||||||
local username = saslprep(username)
|
|
||||||
local c_nonce = b64(string.sub(tostring(math.random()), 3 , 14))
|
|
||||||
|
|
||||||
local first_bare = "n=" .. username .. ",r=" .. c_nonce
|
|
||||||
|
|
||||||
local sasl_start_payload = b64("n,," .. first_bare)
|
|
||||||
|
|
||||||
r, err = db:_cmd("saslStart", {
|
|
||||||
mechanism = "SCRAM-SHA-1" ;
|
|
||||||
autoAuthorize = 1 ;
|
|
||||||
payload = cbson.binary(sasl_start_payload);
|
|
||||||
})
|
|
||||||
|
|
||||||
if not r then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local conversationId = r['conversationId']
|
|
||||||
local server_first = r['payload']:raw()
|
|
||||||
|
|
||||||
local parsed_t = {}
|
|
||||||
for k, v in string.gmatch(server_first, "(%w+)=([^,]*)") do
|
|
||||||
parsed_t[k] = v
|
|
||||||
end
|
|
||||||
|
|
||||||
local iterations = tonumber(parsed_t['i'])
|
|
||||||
local salt = parsed_t['s']
|
|
||||||
local s_nonce = parsed_t['r']
|
|
||||||
|
|
||||||
if not string.sub(s_nonce, 1, 12) == c_nonce then
|
|
||||||
return nil, 'Server returned an invalid nonce.'
|
|
||||||
end
|
|
||||||
|
|
||||||
local without_proof = "c=biws,r=" .. s_nonce
|
|
||||||
|
|
||||||
local pbkdf2_key = pass_digest ( username , password )
|
|
||||||
local salted_pass = Hi(pbkdf2_key, iterations, unb64(salt), 20)
|
|
||||||
|
|
||||||
local client_key = hmac_sha1(salted_pass, "Client Key")
|
|
||||||
local stored_key = sha1_bin(client_key)
|
|
||||||
local auth_msg = first_bare .. ',' .. server_first .. ',' .. without_proof
|
|
||||||
local client_sig = hmac_sha1(stored_key, auth_msg)
|
|
||||||
local client_key_xor_sig = xor_bytestr(client_key, client_sig)
|
|
||||||
local client_proof = "p=" .. b64(client_key_xor_sig)
|
|
||||||
local client_final = b64(without_proof .. ',' .. client_proof)
|
|
||||||
local server_key = hmac_sha1(salted_pass, "Server Key")
|
|
||||||
local server_sig = b64(hmac_sha1(server_key, auth_msg))
|
|
||||||
|
|
||||||
r, err = db:_cmd("saslContinue",{
|
|
||||||
conversationId = conversationId ;
|
|
||||||
payload = cbson.binary(client_final);
|
|
||||||
})
|
|
||||||
|
|
||||||
if not r then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
local parsed_s = r['payload']:raw()
|
|
||||||
parsed_t = {}
|
|
||||||
for k, v in string.gmatch(parsed_s, "(%w+)=([^,]*)") do
|
|
||||||
parsed_t[k] = v
|
|
||||||
end
|
|
||||||
if parsed_t['v'] ~= server_sig then
|
|
||||||
return nil, "Server returned an invalid signature."
|
|
||||||
end
|
|
||||||
|
|
||||||
if not r['done'] then
|
|
||||||
r, err = db:_cmd("saslContinue", {
|
|
||||||
conversationId = conversationId ;
|
|
||||||
payload = cbson.binary("") ;
|
|
||||||
})
|
|
||||||
|
|
||||||
if not r then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
if not r['done'] then
|
|
||||||
return nil, 'SASL conversation failed to complete.'
|
|
||||||
end
|
|
||||||
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
return auth
|
|
||||||
@ -1,377 +0,0 @@
|
|||||||
local cbson = require("cbson")
|
|
||||||
local generate_oid = require("resty.moongoo.utils").generate_oid
|
|
||||||
local cursor = require("resty.moongoo.cursor")
|
|
||||||
|
|
||||||
local _M = {}
|
|
||||||
|
|
||||||
local mt = { __index = _M }
|
|
||||||
|
|
||||||
function _M.new(name, db)
|
|
||||||
return setmetatable({_db = db, name = name}, mt)
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M._build_write_concern(self)
|
|
||||||
return {
|
|
||||||
j = self._db._moongoo.journal;
|
|
||||||
w = tonumber(self._db._moongoo.w) and cbson.int(self._db._moongoo.w) or self._db._moongoo.w;
|
|
||||||
wtimeout = cbson.int(self._db._moongoo.wtimeout);
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
local function check_write_concern(doc, ...)
|
|
||||||
-- even if write concern failed we may still have successful operation
|
|
||||||
-- so we check for number of affected docs, and only warn if its > 0
|
|
||||||
-- otherwise, we just return nil and error
|
|
||||||
|
|
||||||
if doc.writeConcernError then
|
|
||||||
if not doc.n then
|
|
||||||
return nil, doc.writeConcernError.errmsg
|
|
||||||
else
|
|
||||||
print(doc.writeConcernError.errmsg)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return ...
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M._get_last_error(self)
|
|
||||||
local write_concern = self:_build_write_concern()
|
|
||||||
local cmd = { getLastError = cbson.int(1), j = write_concern.j, w = write_concern.w, wtimeout = write_concern.wtimeout }
|
|
||||||
|
|
||||||
local doc, err = self._db:cmd(cmd)
|
|
||||||
if not doc then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
return doc
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M._check_last_error(self, ...)
|
|
||||||
local cmd, err = self:_get_last_error()
|
|
||||||
|
|
||||||
if not cmd then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
if tostring(cmd.err) == "null" then
|
|
||||||
return ...
|
|
||||||
end
|
|
||||||
|
|
||||||
return nil, tostring(cmd.err)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function ensure_oids(docs)
|
|
||||||
local docs = docs
|
|
||||||
local ids = {}
|
|
||||||
for k,v in ipairs(docs) do
|
|
||||||
if not docs[k]._id then
|
|
||||||
docs[k]._id = cbson.oid(generate_oid())
|
|
||||||
end
|
|
||||||
table.insert(ids, docs[k]._id)
|
|
||||||
end
|
|
||||||
return docs, ids
|
|
||||||
end
|
|
||||||
|
|
||||||
local function build_index_names(docs)
|
|
||||||
local docs = docs
|
|
||||||
for k,v in ipairs(docs) do
|
|
||||||
if not v.name then
|
|
||||||
local name = {}
|
|
||||||
for n, d in pairs(v.key) do
|
|
||||||
table.insert(name, n)
|
|
||||||
end
|
|
||||||
name = table.concat(name, '_')
|
|
||||||
docs[k].name = name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return docs
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.insert(self, docs)
|
|
||||||
-- ensure we have oids
|
|
||||||
if #docs == 0 then
|
|
||||||
local newdocs = {}
|
|
||||||
newdocs[1] = docs
|
|
||||||
docs = newdocs
|
|
||||||
end
|
|
||||||
local docs, ids = ensure_oids(docs)
|
|
||||||
|
|
||||||
self._db._moongoo:connect()
|
|
||||||
|
|
||||||
local server_version = tonumber(string.sub(string.gsub(self._db._moongoo.version, "(%D)", ""), 1, 3))
|
|
||||||
|
|
||||||
if server_version < 254 then
|
|
||||||
self._db:insert(self:full_name(), docs)
|
|
||||||
return self:_check_last_error(ids)
|
|
||||||
else
|
|
||||||
local doc, err = self._db:cmd(
|
|
||||||
{ insert = self.name },
|
|
||||||
{
|
|
||||||
documents = docs,
|
|
||||||
ordered = true,
|
|
||||||
writeConcern = self:_build_write_concern()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if not doc then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
return check_write_concern(doc, ids, doc.n)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.create(self, params)
|
|
||||||
local params = params or {}
|
|
||||||
local doc, err = self._db:cmd(
|
|
||||||
{ create = self.name },
|
|
||||||
params
|
|
||||||
)
|
|
||||||
if not doc then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.drop(self)
|
|
||||||
local doc, err = self._db:cmd(
|
|
||||||
{ drop = self.name },
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
if not doc then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.drop_index(self, name)
|
|
||||||
local doc, err = self._db:cmd(
|
|
||||||
{ dropIndexes = self.name },
|
|
||||||
{ index = name }
|
|
||||||
)
|
|
||||||
if not doc then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.ensure_index(self, docs)
|
|
||||||
docs = build_index_names(docs)
|
|
||||||
|
|
||||||
local doc, err = self._db:cmd(
|
|
||||||
{ createIndexes = self.name },
|
|
||||||
{ indexes = docs }
|
|
||||||
)
|
|
||||||
if not doc then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.full_name(self)
|
|
||||||
return self._db.name .. "." .. self.name
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.options(self)
|
|
||||||
local doc, err = self._db:cmd(
|
|
||||||
"listCollections",
|
|
||||||
{
|
|
||||||
filter = { name = self.name }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if not doc then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
return doc.cursor.firstBatch[1]
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.remove(self, query, single)
|
|
||||||
local query = query or {}
|
|
||||||
|
|
||||||
if getmetatable(cbson.oid("000000000000000000000000")) == getmetatable(query) then
|
|
||||||
query = { _id = query }
|
|
||||||
end
|
|
||||||
|
|
||||||
local doc, err = self._db:cmd(
|
|
||||||
{ delete = self.name },
|
|
||||||
{
|
|
||||||
deletes = {{q=query, limit = single and 1 or 0}},
|
|
||||||
ordered = true,
|
|
||||||
writeConcern = self:_build_write_concern()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if not doc then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
return check_write_concern(doc, doc.n)
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.stats(self)
|
|
||||||
local doc, err = self._db:cmd(
|
|
||||||
{collstats = self.name},
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
if not doc then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
return doc
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.index_information(self)
|
|
||||||
local doc, err = self._db:cmd(
|
|
||||||
{ listIndexes = self.name },
|
|
||||||
{ }
|
|
||||||
)
|
|
||||||
if not doc then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
return doc.cursor.firstBatch
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.rename(self, to_name, drop)
|
|
||||||
local drop = drop or false
|
|
||||||
-- rename
|
|
||||||
local doc, err = self._db._moongoo:db("admin"):cmd(
|
|
||||||
{ renameCollection = self:full_name() },
|
|
||||||
{
|
|
||||||
to = to_name,
|
|
||||||
dropTarget = drop
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if not doc then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
return self.new(to_name, self._db)
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.update(self, query, update, flags)
|
|
||||||
local flags = flags or {}
|
|
||||||
local query = query or {}
|
|
||||||
|
|
||||||
if getmetatable(cbson.oid("000000000000000000000000")) == getmetatable(query) then
|
|
||||||
query = { _id = query }
|
|
||||||
end
|
|
||||||
|
|
||||||
local update = {
|
|
||||||
q = query,
|
|
||||||
u = update,
|
|
||||||
upsert = flags.upsert or false,
|
|
||||||
multi = flags.multi or false
|
|
||||||
}
|
|
||||||
|
|
||||||
local doc, err = self._db:cmd(
|
|
||||||
{ update = self.name },
|
|
||||||
{
|
|
||||||
updates = { update },
|
|
||||||
ordered = true,
|
|
||||||
writeConcern = self:_build_write_concern()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if not doc then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
return doc.nModified
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.save(self, doc)
|
|
||||||
if not doc._id then
|
|
||||||
doc._id = cbson.oid(generate_oid())
|
|
||||||
end
|
|
||||||
local r, err = self:update(doc._id, doc, {upsert = true});
|
|
||||||
if not r then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
return doc._id
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.map_reduce(self, map, reduce, flags)
|
|
||||||
local flags = flags or {}
|
|
||||||
flags.map = cbson.code(map)
|
|
||||||
flags.reduce = cbson.code(reduce)
|
|
||||||
flags.out = flags.out or { inline = true }
|
|
||||||
|
|
||||||
local doc, err = self._db:cmd(
|
|
||||||
{ mapReduce = self.name },
|
|
||||||
flags
|
|
||||||
)
|
|
||||||
if not doc then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
if doc.results then
|
|
||||||
return doc.results
|
|
||||||
end
|
|
||||||
|
|
||||||
return self.new(doc.result, self._db)
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.find(self, query, fields)
|
|
||||||
local query = query or {}
|
|
||||||
if getmetatable(cbson.oid("000000000000000000000000")) == getmetatable(query) then
|
|
||||||
query = { _id = query }
|
|
||||||
end
|
|
||||||
return cursor.new(self, query, fields)
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.find_one(self, query, fields)
|
|
||||||
local query = query or {}
|
|
||||||
if getmetatable(cbson.oid("000000000000000000000000")) == getmetatable(query) then
|
|
||||||
query = { _id = query }
|
|
||||||
end
|
|
||||||
|
|
||||||
return self:find(query, fields):limit(-1):next()
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.find_and_modify(self, query, opts)
|
|
||||||
local query = query or {}
|
|
||||||
if getmetatable(cbson.oid("000000000000000000000000")) == getmetatable(query) then
|
|
||||||
query = { _id = query }
|
|
||||||
end
|
|
||||||
|
|
||||||
local opts = opts or {}
|
|
||||||
opts.query = query
|
|
||||||
|
|
||||||
local doc, err = self._db:cmd(
|
|
||||||
{ findAndModify = self.name },
|
|
||||||
opts
|
|
||||||
)
|
|
||||||
if not doc then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
return doc.value
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.aggregate(self, pipeline, opts)
|
|
||||||
local opts = opts or {}
|
|
||||||
opts.pipeline = pipeline
|
|
||||||
if not opts.explain then
|
|
||||||
opts.cursor = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
local doc, err = self._db:cmd(
|
|
||||||
{ aggregate = self.name },
|
|
||||||
opts
|
|
||||||
)
|
|
||||||
if not doc then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
if opts.explain then
|
|
||||||
return doc
|
|
||||||
end
|
|
||||||
|
|
||||||
-- collection
|
|
||||||
if opts.pipeline[#opts.pipeline]['$out'] then
|
|
||||||
return self.new(opts.pipeline[#opts.pipeline]['$out'], self._db)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- cursor
|
|
||||||
return cursor.new(self, {}, {}, false, doc.cursor.id):add_batch(doc.cursor.firstBatch)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return _M
|
|
||||||
@ -1,210 +0,0 @@
|
|||||||
local socket = ngx and ngx.socket.tcp or require("socket").tcp
|
|
||||||
local cbson = require("cbson")
|
|
||||||
|
|
||||||
local opcodes = {
|
|
||||||
OP_REPLY = 1;
|
|
||||||
OP_MSG = 1000;
|
|
||||||
OP_UPDATE = 2001;
|
|
||||||
OP_INSERT = 2002;
|
|
||||||
RESERVED = 2003;
|
|
||||||
OP_QUERY = 2004;
|
|
||||||
OP_GET_MORE = 2005;
|
|
||||||
OP_DELETE = 2006;
|
|
||||||
OP_KILL_CURSORS = 2007;
|
|
||||||
}
|
|
||||||
|
|
||||||
local _M = {}
|
|
||||||
|
|
||||||
local mt = { __index = _M }
|
|
||||||
|
|
||||||
function _M.new(host, port, timeout)
|
|
||||||
local sock = socket()
|
|
||||||
if timeout then
|
|
||||||
sock:settimeout(timeout)
|
|
||||||
end
|
|
||||||
|
|
||||||
return setmetatable({
|
|
||||||
sock = sock;
|
|
||||||
host = host;
|
|
||||||
port = port;
|
|
||||||
_id = 0;
|
|
||||||
}, mt)
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.connect(self, host, port)
|
|
||||||
self.host = host or self.host
|
|
||||||
self.port = port or self.port
|
|
||||||
return self.sock:connect(self.host, self.port)
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.handshake(self)
|
|
||||||
if ngx then
|
|
||||||
self.sock:sslhandshake()
|
|
||||||
else
|
|
||||||
local ssl = require("ssl")
|
|
||||||
self.sock = ssl.wrap(self.sock, {mode = "client", protocol = "tlsv1_2"})
|
|
||||||
assert(self.sock)
|
|
||||||
self.sock:dohandshake()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.close(self)
|
|
||||||
if ngx then
|
|
||||||
self.sock:setkeepalive()
|
|
||||||
else
|
|
||||||
self.sock:close()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.get_reused_times(self)
|
|
||||||
if not self.sock then
|
|
||||||
return nil, "not initialized"
|
|
||||||
end
|
|
||||||
|
|
||||||
return self.sock:getreusedtimes()
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.settimeout(self, ms)
|
|
||||||
self.sock:settimeout(ms)
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.send(self, data)
|
|
||||||
return self.sock:send(data)
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.receive(self, pat)
|
|
||||||
return self.sock:receive(pat)
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M._handle_reply(self)
|
|
||||||
local header = assert ( self.sock:receive ( 16 ) )
|
|
||||||
|
|
||||||
local length = cbson.raw_to_uint( string.sub(header , 1 , 4 ))
|
|
||||||
local r_id = cbson.raw_to_uint( string.sub(header , 5 , 8 ))
|
|
||||||
local r_to = cbson.raw_to_uint( string.sub(header , 9 , 12 ))
|
|
||||||
local opcode = cbson.raw_to_uint( string.sub(header , 13 , 16 ))
|
|
||||||
|
|
||||||
assert ( opcode == cbson.uint(opcodes.OP_REPLY ) )
|
|
||||||
assert ( r_to == cbson.uint(self._id) )
|
|
||||||
|
|
||||||
local data = assert ( self.sock:receive ( tostring(length-16 ) ) )
|
|
||||||
|
|
||||||
local flags = cbson.raw_to_uint( string.sub(data , 1 , 4 ))
|
|
||||||
local cursor_id = cbson.raw_to_uint( string.sub(data , 5 , 12 ))
|
|
||||||
local from = cbson.raw_to_uint( string.sub(data , 13 , 16 ))
|
|
||||||
local number = tonumber(tostring(cbson.raw_to_uint( string.sub(data , 17 , 20 ))))
|
|
||||||
|
|
||||||
local docs = string.sub(data , 21)
|
|
||||||
|
|
||||||
local pos = 1
|
|
||||||
local index = 0
|
|
||||||
local r_docs = {}
|
|
||||||
while index < number do
|
|
||||||
local bson_size = tonumber(tostring(cbson.raw_to_uint(docs:sub(pos, pos+3))))
|
|
||||||
|
|
||||||
local dt = docs:sub(pos,pos+bson_size-1) -- get bson data according to size
|
|
||||||
|
|
||||||
table.insert(r_docs, cbson.decode(dt))
|
|
||||||
|
|
||||||
pos = pos + bson_size
|
|
||||||
index = index + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
return flags, cursor_id, from, number, r_docs
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M._build_header(self, op, payload_size)
|
|
||||||
local size = cbson.uint_to_raw(cbson.uint(payload_size+16), 4)
|
|
||||||
local op = cbson.uint_to_raw(cbson.uint(op), 4)
|
|
||||||
self._id = self._id+1
|
|
||||||
local id = cbson.uint_to_raw(cbson.uint(self._id), 4)
|
|
||||||
local reply_to = "\0\0\0\0"
|
|
||||||
return size .. id .. reply_to .. op
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M._query(self, collection, query, to_skip, to_return, selector, flags)
|
|
||||||
local flags = {
|
|
||||||
tailable = flags and flags.tailable and 1 or 0,
|
|
||||||
slaveok = flags and flags.slaveok and 1 or 0,
|
|
||||||
notimeout = flags and flags.notimeout and 1 or 0,
|
|
||||||
await = flags and flags.await and 1 or 0,
|
|
||||||
exhaust = flags and flags.exhaust and 1 or 0,
|
|
||||||
partial = flags and flags.partial and 1 or 0
|
|
||||||
}
|
|
||||||
|
|
||||||
local flagset = cbson.int_to_raw(
|
|
||||||
cbson.int(
|
|
||||||
2 * flags["tailable"] +
|
|
||||||
2^2 * flags["slaveok"] +
|
|
||||||
2^4 * flags["notimeout"] +
|
|
||||||
2^5 * flags["await"] +
|
|
||||||
2^6 * flags["exhaust"] +
|
|
||||||
2^7 * flags["partial"]
|
|
||||||
),
|
|
||||||
4)
|
|
||||||
|
|
||||||
local selector = selector and #selector and cbson.encode(selector) or ""
|
|
||||||
|
|
||||||
local to_skip = cbson.int_to_raw(cbson.int(to_skip), 4)
|
|
||||||
local to_return = cbson.int_to_raw(cbson.int(to_return), 4)
|
|
||||||
|
|
||||||
local size = 4 + #collection + 1 + 4 + 4 + #query + #selector
|
|
||||||
|
|
||||||
local header = self:_build_header(opcodes["OP_QUERY"], size)
|
|
||||||
|
|
||||||
local data = header .. flagset .. collection .. "\0" .. to_skip .. to_return .. query .. selector
|
|
||||||
|
|
||||||
assert(self:send(data))
|
|
||||||
return self:_handle_reply()
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M._insert(self, collection, docs, flags)
|
|
||||||
local encoded_docs = {}
|
|
||||||
for k, doc in ipairs(docs) do
|
|
||||||
encoded_docs[k] = cbson.encode(doc)
|
|
||||||
end
|
|
||||||
string_docs = table.concat(encoded_docs)
|
|
||||||
|
|
||||||
local flags = {
|
|
||||||
continue_on_error = flags and flags.continue_on_error and 1 or 0
|
|
||||||
}
|
|
||||||
|
|
||||||
local flagset = cbson.int_to_raw(
|
|
||||||
cbson.int(
|
|
||||||
2 * flags["continue_on_error"]
|
|
||||||
),
|
|
||||||
4)
|
|
||||||
|
|
||||||
local size = 4 + 1 + #collection + #string_docs
|
|
||||||
local header = self:_build_header(opcodes["OP_INSERT"], size)
|
|
||||||
|
|
||||||
local data = header .. flagset .. collection .. "\0" .. string_docs
|
|
||||||
|
|
||||||
assert(self:send(data))
|
|
||||||
|
|
||||||
return true -- Mongo doesn't send a reply
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M._kill_cursors(self, id)
|
|
||||||
local id = cbson.uint_to_raw(id, 8)
|
|
||||||
local num = cbson.int_to_raw(cbson.int(1), 4)
|
|
||||||
local zero = cbson.int_to_raw(cbson.int(0), 4)
|
|
||||||
local size = 8+4+4
|
|
||||||
local header = self:_build_header(opcodes["OP_KILL_CURSORS"], size)
|
|
||||||
local data = header .. zero .. num .. id
|
|
||||||
assert(self:send(data))
|
|
||||||
return true -- Mongo doesn't send a reply
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M._get_more(self, collection, number, cursor)
|
|
||||||
local num = cbson.int_to_raw(cbson.int(number), 4)
|
|
||||||
local zero = cbson.int_to_raw(cbson.int(0), 4)
|
|
||||||
local cursor = cbson.uint_to_raw(cursor, 8)
|
|
||||||
local size = 4+#collection+1+4+8
|
|
||||||
local header = self:_build_header(opcodes["OP_GET_MORE"], size)
|
|
||||||
local data = header .. zero .. collection .. '\0' .. num .. cursor
|
|
||||||
assert(self:send(data))
|
|
||||||
return self:_handle_reply()
|
|
||||||
end
|
|
||||||
|
|
||||||
return _M
|
|
||||||
@ -1,256 +0,0 @@
|
|||||||
local cbson = require("cbson")
|
|
||||||
local bit = require("bit")
|
|
||||||
|
|
||||||
|
|
||||||
local function check_bit(num, bitnum)
|
|
||||||
return bit.band(num,math.pow(2,bitnum)) ~= 0 -- and true or false
|
|
||||||
end
|
|
||||||
|
|
||||||
local _M = {}
|
|
||||||
|
|
||||||
local mt = { __index = _M }
|
|
||||||
|
|
||||||
function _M.new(collection, query, fields, explain, id)
|
|
||||||
return setmetatable(
|
|
||||||
{
|
|
||||||
_collection = collection,
|
|
||||||
_query = query,
|
|
||||||
_fields = fields,
|
|
||||||
_id = id or cbson.uint(0),
|
|
||||||
_skip = 0,
|
|
||||||
_limit = 0,
|
|
||||||
_docs = {},
|
|
||||||
_started = false,
|
|
||||||
_cnt = 0,
|
|
||||||
_comment = nil,
|
|
||||||
_hint = nil,
|
|
||||||
_max_scan = nil ,
|
|
||||||
_max_time_ms = nil,
|
|
||||||
_read_preference = nil,
|
|
||||||
_snapshot = nil,
|
|
||||||
_sort = nil,
|
|
||||||
_await = false,
|
|
||||||
_tailable = false,
|
|
||||||
_explain = explain or false
|
|
||||||
},
|
|
||||||
mt)
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.tailable(self, tailable)
|
|
||||||
self._tailable = tailable
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.await(self, await)
|
|
||||||
self._await = await
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function _M.comment(self, comment)
|
|
||||||
self._comment = comment
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.hint(self, hint)
|
|
||||||
self._hint = hint
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.max_scan(self, max_scan)
|
|
||||||
self._max_scan = max_scan
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.max_time_ms(self, max_time_ms)
|
|
||||||
self._max_time_ms = max_time_ms
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.read_preference(self, read_preference)
|
|
||||||
self._read_preference = read_preference
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.snapshot(self, snapshot)
|
|
||||||
self._snapshot = snapshot
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.sort(self, sort)
|
|
||||||
self._sort = sort
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function _M.clone(self, explain)
|
|
||||||
local clone = self.new(self._collection, self._query, self._fields, explain)
|
|
||||||
clone:limit(self._limit)
|
|
||||||
clone:skip(self._skip)
|
|
||||||
|
|
||||||
clone:comment(self._comment)
|
|
||||||
clone:hint(self._hint)
|
|
||||||
clone:max_scan(self._max_scan)
|
|
||||||
clone:max_time_ms(self._max_time_ms)
|
|
||||||
clone:read_preference(self._read_preference)
|
|
||||||
clone:snapshot(self._snapshot)
|
|
||||||
clone:sort(self._sort)
|
|
||||||
|
|
||||||
return clone
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.skip(self, skip)
|
|
||||||
if self._started then
|
|
||||||
print("Can's set skip after starting cursor")
|
|
||||||
else
|
|
||||||
self._skip = skip
|
|
||||||
end
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.limit(self, limit)
|
|
||||||
if self._started then
|
|
||||||
print("Can's set limit after starting cursor")
|
|
||||||
else
|
|
||||||
self._limit = limit
|
|
||||||
end
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M._build_query(self)
|
|
||||||
local ext = {}
|
|
||||||
if self._comment then ext['$comment'] = self._comment end
|
|
||||||
if self._explain then ext['$explain'] = true end
|
|
||||||
|
|
||||||
|
|
||||||
if self._hint then ext['$hint'] = self._hint end
|
|
||||||
if self._max_scan then ext['$maxScan'] = self._max_scan end
|
|
||||||
if self._max_time_ms then ext['$maxTimeMS'] = self._max_time_ms end
|
|
||||||
if self._read_preference then ext['$readPreference'] = self._read_preference end
|
|
||||||
if self._snapshot then ext['$snapshot'] = true end
|
|
||||||
if self._sort then ext['$orderby'] = self._sort end
|
|
||||||
|
|
||||||
ext['$query'] = self._query
|
|
||||||
|
|
||||||
return cbson.encode(ext)
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.next(self)
|
|
||||||
local moongoo, err = self._collection._db._moongoo:connect()
|
|
||||||
if not moongoo then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
if self:_finished() then
|
|
||||||
if self._id ~= cbson.uint(0) then
|
|
||||||
self._collection._db._moongoo.connection:_kill_cursors(self._id)
|
|
||||||
self._id = cbson.uint(0)
|
|
||||||
end
|
|
||||||
return nil, "no more data"
|
|
||||||
end
|
|
||||||
|
|
||||||
if (not self._started) and (self._id == cbson.uint(0)) then
|
|
||||||
|
|
||||||
-- query and add id and batch
|
|
||||||
local flags, id, from, number, docs = self._collection._db._moongoo.connection:_query(self._collection:full_name(), self:_build_query(), self._skip, self._limit, self._fields, {tailable = self._tailable, await = self._await})
|
|
||||||
|
|
||||||
flags = tonumber(tostring(flags)) -- bitop can't work with cbson.int, so...
|
|
||||||
|
|
||||||
if check_bit(flags, 1) then -- QueryFailure
|
|
||||||
return nil, docs[1]['$err'] -- why is this $err and not errmsg, like others??
|
|
||||||
end
|
|
||||||
self._id = id
|
|
||||||
self:add_batch(docs)
|
|
||||||
elseif #self._docs == 0 and self._id ~= cbson.uint(0) then
|
|
||||||
-- we have something to fetch - get_more and add_batch
|
|
||||||
local flags, id, from, number, docs = self._collection._db._moongoo.connection:_get_more(self._collection:full_name(), self._limit, self._id)
|
|
||||||
|
|
||||||
flags = tonumber(tostring(flags)) -- bitop can't work with cbson.int, so...
|
|
||||||
|
|
||||||
if check_bit(flags, 0) then -- QueryFailure
|
|
||||||
return nil, "wrong cursor id"
|
|
||||||
end
|
|
||||||
self:add_batch(docs)
|
|
||||||
self._id = id
|
|
||||||
|
|
||||||
elseif #self._docs == 0 then--or self._id == cbson.uint(0) then
|
|
||||||
return nil, "no more data"
|
|
||||||
end
|
|
||||||
self._cnt = self._cnt+1
|
|
||||||
return table.remove(self._docs, 1) or nil, 'No more data'
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.all(self)
|
|
||||||
local docs = {}
|
|
||||||
while true do
|
|
||||||
local doc = self:next()
|
|
||||||
if doc == nil then break end
|
|
||||||
table.insert(docs, doc)
|
|
||||||
end
|
|
||||||
return docs
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.rewind(self)
|
|
||||||
self._started = false
|
|
||||||
self._docs = {}
|
|
||||||
self._collection._db._moongoo.connection:_kill_cursors(self._id)
|
|
||||||
self._id = cbson.uint(0)
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.count(self)
|
|
||||||
local doc, err = self._collection._db:cmd(
|
|
||||||
{ count = self._collection.name },
|
|
||||||
{
|
|
||||||
query = self._query,
|
|
||||||
skip = self._skip,
|
|
||||||
limit = self._limit
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if not doc then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
return doc and doc.n or 0
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.distinct(self, key)
|
|
||||||
local doc, err = self._collection._db:cmd(
|
|
||||||
{ distinct = self._collection.name },
|
|
||||||
{
|
|
||||||
query = self._query,
|
|
||||||
key = key
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if not doc then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
return doc and doc.values or {}
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.explain(self)
|
|
||||||
return self:clone(true):sort(nil):next()
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.add_batch(self, docs)
|
|
||||||
self._started = true
|
|
||||||
for k,v in ipairs(docs) do
|
|
||||||
table.insert(self._docs, v)
|
|
||||||
end
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M._finished(self)
|
|
||||||
if self._limit == 0 then
|
|
||||||
return false
|
|
||||||
else
|
|
||||||
if self._cnt >= math.abs(self._limit) then
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return _M
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
local cbson = require("cbson")
|
|
||||||
local collection = require("resty.moongoo.collection")
|
|
||||||
local gridfs = require("resty.moongoo.gridfs")
|
|
||||||
|
|
||||||
local _M = {}
|
|
||||||
|
|
||||||
local mt = { __index = _M }
|
|
||||||
|
|
||||||
function _M.new(name, moongoo)
|
|
||||||
return setmetatable({name = name, _moongoo = moongoo}, mt)
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.collection(self, name)
|
|
||||||
return collection.new(name, self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.gridfs(self, name)
|
|
||||||
return gridfs.new(self,name)
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.cmd(self, cmd, params)
|
|
||||||
local r, err = self._moongoo:connect()
|
|
||||||
if not r then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
return self:_cmd(cmd, params)
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M._cmd(self, cmd, params)
|
|
||||||
local params = params or {}
|
|
||||||
if type(cmd) == "table" then
|
|
||||||
local tmpcmd = ''
|
|
||||||
for k,v in pairs(cmd) do
|
|
||||||
params[k] = v
|
|
||||||
tmpcmd = k
|
|
||||||
end
|
|
||||||
cmd = tmpcmd
|
|
||||||
else
|
|
||||||
params[cmd] = true
|
|
||||||
end
|
|
||||||
local cmd = cbson.encode_first(cmd, params)
|
|
||||||
|
|
||||||
local _,_,_,_,docs = self._moongoo.connection:_query(self.name..".$cmd", cmd, 0, 1)
|
|
||||||
|
|
||||||
if not docs[1] then
|
|
||||||
return nil, "Empty reply from mongodb"
|
|
||||||
end
|
|
||||||
|
|
||||||
if not docs[1].ok or docs[1].ok == 0 then
|
|
||||||
return nil, docs[1].errmsg
|
|
||||||
end
|
|
||||||
|
|
||||||
return docs[1]
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.insert(self, collection, docs)
|
|
||||||
local r, err = self._moongoo:connect()
|
|
||||||
if not r then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
return self:_insert(collection, docs)
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M._insert(self, collection, docs)
|
|
||||||
self._moongoo.connection:_insert(collection, docs)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
return _M
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
local cbson = require("cbson")
|
|
||||||
local gfsfile = require("resty.moongoo.gridfs.file")
|
|
||||||
|
|
||||||
local _M = {}
|
|
||||||
|
|
||||||
local mt = { __index = _M }
|
|
||||||
|
|
||||||
function _M.new(db, name)
|
|
||||||
local name = name or 'fs'
|
|
||||||
return setmetatable(
|
|
||||||
{
|
|
||||||
_db = db,
|
|
||||||
_name = name,
|
|
||||||
_files = db:collection(name .. '.files'),
|
|
||||||
_chunks = db:collection(name .. '.chunks')
|
|
||||||
},
|
|
||||||
mt)
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.list(self)
|
|
||||||
return self._files:find({}):distinct('filename')
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.remove(self, id)
|
|
||||||
local r,err = self._files:remove({_id = cbson.oid(id)})
|
|
||||||
if not r then
|
|
||||||
return nil, "Failed to remove file metadata: "..err
|
|
||||||
end
|
|
||||||
r,err = self._chunks:remove({files_id = cbson.oid(id)});
|
|
||||||
if not r then
|
|
||||||
return nil, "Failed to remove file chunks: "..err
|
|
||||||
end
|
|
||||||
return r
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.find_version(self, name, version)
|
|
||||||
-- Positive numbers are absolute and negative ones relative
|
|
||||||
local cursor = self._files:find({filename = name}, {_id = 1}):limit(-1)
|
|
||||||
cursor:sort({uploadDate = (version < 0) and cbson.int(-1) or cbson.int(1)}):skip(version < 0 and (math.abs(version) - 1) or version)
|
|
||||||
local doc, err = cursor:next()
|
|
||||||
if not doc then
|
|
||||||
return nil, "No such file/version"
|
|
||||||
end
|
|
||||||
return doc._id
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.open(self, id)
|
|
||||||
return gfsfile.open(self, id)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function _M.create(self, name, opts, safe)
|
|
||||||
return gfsfile.new(self, name, opts, safe)
|
|
||||||
end
|
|
||||||
|
|
||||||
return _M
|
|
||||||
|
|
||||||
@ -1,219 +0,0 @@
|
|||||||
local cbson = require("cbson")
|
|
||||||
local generate_oid = require("resty.moongoo.utils").generate_oid
|
|
||||||
|
|
||||||
|
|
||||||
local _M = {}
|
|
||||||
|
|
||||||
local mt = { __index = _M }
|
|
||||||
|
|
||||||
function _M.new(gridfs, name, opts, safe, read_only)
|
|
||||||
local read_only = read_only or false
|
|
||||||
local safe = safe == nil and true or safe
|
|
||||||
|
|
||||||
opts = opts or {}
|
|
||||||
opts.filename = name
|
|
||||||
opts.length = opts.length or cbson.uint(0)
|
|
||||||
opts.chunkSize = opts.chunkSize or cbson.uint(261120)
|
|
||||||
|
|
||||||
local write_only = true
|
|
||||||
if not safe then
|
|
||||||
write_only = false
|
|
||||||
end
|
|
||||||
|
|
||||||
return setmetatable(
|
|
||||||
{
|
|
||||||
_gridfs = gridfs,
|
|
||||||
_meta = opts,
|
|
||||||
_write_only = write_only,
|
|
||||||
_read_only = read_only,
|
|
||||||
_pos = 0,
|
|
||||||
_chunks = {},
|
|
||||||
_closed = false,
|
|
||||||
_buffer = '',
|
|
||||||
_n = 0
|
|
||||||
},
|
|
||||||
mt)
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.open(gridfs, id)
|
|
||||||
-- try to fetch
|
|
||||||
local doc, err = gridfs._files:find_one({ _id = id})
|
|
||||||
if not doc then
|
|
||||||
return nil, "No such file"
|
|
||||||
else
|
|
||||||
return _M.new(gridfs, doc.filename, doc, false, true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- props
|
|
||||||
|
|
||||||
function _M.content_type(self) return self._meta.contentType end
|
|
||||||
function _M.filename(self) return self._meta.filename end
|
|
||||||
function _M.md5(self) return self._meta.md5 end
|
|
||||||
function _M.metadata(self) return self._meta.metadata end
|
|
||||||
function _M.raw_length(self) return self._meta.length end
|
|
||||||
function _M.raw_chunk_size(self) return self._meta.chunkSize end
|
|
||||||
function _M.date(self) return self._meta.uploadDate end
|
|
||||||
|
|
||||||
function _M.length(self) return tonumber(tostring(self._meta.length)) end
|
|
||||||
function _M.chunk_size(self) return tonumber(tostring(self._meta.chunkSize)) end
|
|
||||||
|
|
||||||
-- reading
|
|
||||||
|
|
||||||
function _M.read(self)
|
|
||||||
if self._write_only then
|
|
||||||
return nil, "Can't read from write-only file"
|
|
||||||
end
|
|
||||||
|
|
||||||
if self._pos >= (self:length() or 0) then
|
|
||||||
return nil, "EOF"
|
|
||||||
end
|
|
||||||
|
|
||||||
local n = math.modf(self._pos / self:chunk_size())
|
|
||||||
local query = {files_id = self._meta._id, n = n}
|
|
||||||
local fields = {_id = false, data = true}
|
|
||||||
|
|
||||||
local chunk = self._gridfs._chunks:find_one(query, fields)
|
|
||||||
if not chunk then
|
|
||||||
return nil, "EOF?"
|
|
||||||
end
|
|
||||||
|
|
||||||
return self:_slice(n, chunk.data)
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.seek(self, pos)
|
|
||||||
self._pos = pos
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.tell(self)
|
|
||||||
return self._pos
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.slurp(self)
|
|
||||||
local data = {}
|
|
||||||
local pos = self._pos
|
|
||||||
self:seek(0)
|
|
||||||
while true do
|
|
||||||
local chunk = self:read()
|
|
||||||
if not chunk then break end
|
|
||||||
table.insert(data, chunk)
|
|
||||||
end
|
|
||||||
self:seek(pos)
|
|
||||||
return table.concat(data)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- writing
|
|
||||||
|
|
||||||
function _M.write(self, data)
|
|
||||||
if self._read_only then
|
|
||||||
return nil, "Can't write to read-only file"
|
|
||||||
end
|
|
||||||
|
|
||||||
if self._closed then
|
|
||||||
return nil, "Can't write to closed file"
|
|
||||||
end
|
|
||||||
|
|
||||||
self._buffer = self._buffer .. data
|
|
||||||
self._meta.length = self._meta.length + data:len()
|
|
||||||
|
|
||||||
while self._buffer:len() >= self:chunk_size() do
|
|
||||||
local r, res = self:_chunk()
|
|
||||||
if not r then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.close(self)
|
|
||||||
|
|
||||||
if self._closed then
|
|
||||||
return nil, "File already closed"
|
|
||||||
end
|
|
||||||
self._closed = true
|
|
||||||
|
|
||||||
self:_chunk() -- enqueue/write last chunk of data
|
|
||||||
|
|
||||||
if self._write_only then
|
|
||||||
-- insert all collected chunks
|
|
||||||
for k, v in ipairs(self._chunks) do
|
|
||||||
local r, err = self._gridfs._chunks:insert(v)
|
|
||||||
if not r then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ensure indexes
|
|
||||||
self._gridfs._files:ensure_index({{ key = {filename = true}}})
|
|
||||||
self._gridfs._chunks:ensure_index({ { key = {files_id = 1, n = 1}, unique = true } });
|
|
||||||
-- compute md5
|
|
||||||
local res, err = self._gridfs._db:cmd({filemd5 = self:_files_id()}, {root = self._gridfs._name})
|
|
||||||
if not res then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
local file_md5 = res.md5
|
|
||||||
-- insert metadata
|
|
||||||
local ids, n = self._gridfs._files:insert(self:_metadata(file_md5))
|
|
||||||
|
|
||||||
if not ids then
|
|
||||||
return nil, n
|
|
||||||
end
|
|
||||||
-- return metadata
|
|
||||||
return ids[1]
|
|
||||||
end
|
|
||||||
|
|
||||||
-- private
|
|
||||||
|
|
||||||
function _M._files_id(self)
|
|
||||||
if not self._meta._id then
|
|
||||||
self._meta._id = cbson.oid(generate_oid())
|
|
||||||
end
|
|
||||||
return self._meta._id
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M._metadata(self, file_md5)
|
|
||||||
local doc = {
|
|
||||||
_id = self:_files_id(),
|
|
||||||
length = self:raw_length(),
|
|
||||||
chunkSize = self:raw_chunk_size(),
|
|
||||||
uploadDate = cbson.date(os.time(os.date('!*t'))*1000),
|
|
||||||
md5 = file_md5,
|
|
||||||
filename = self:filename() or nil,
|
|
||||||
content_type = self:content_type() or nil,
|
|
||||||
metadata = self:metadata() or nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return doc
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M._slice(self, n, chunk)
|
|
||||||
local offset = self._pos - (n * self:chunk_size())
|
|
||||||
local chunk = chunk:raw()
|
|
||||||
self._pos = self._pos + chunk:len()
|
|
||||||
return chunk:sub(offset+1);
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M._chunk(self)
|
|
||||||
local chunk = self._buffer:sub(1,self:chunk_size())
|
|
||||||
if not chunk then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
self._buffer = self._buffer:sub(self:chunk_size()+1)
|
|
||||||
local n = self._n
|
|
||||||
self._n = self._n+1
|
|
||||||
local data = cbson.binary("")
|
|
||||||
data:raw(chunk, chunk:len())
|
|
||||||
if self._write_only then
|
|
||||||
-- collect chunks for insert
|
|
||||||
table.insert(self._chunks, {files_id = self:_files_id(), n = cbson.uint(n), data = data})
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
-- insert immidiately, so we can read back (ugh)
|
|
||||||
return self._gridfs._chunks:insert({{files_id = self:_files_id(), n = cbson.uint(n), data = data}})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return _M
|
|
||||||
@ -1,192 +0,0 @@
|
|||||||
local bit = require("bit")
|
|
||||||
local cbson = require("cbson")
|
|
||||||
|
|
||||||
local md5 = ngx and ngx.md5 or function(str) return require("crypto").digest("md5", str) end
|
|
||||||
local hmac_sha1 = ngx and ngx.hmac_sha1 or function(str, key) return require("crypto").hmac.digest("sha1", key, str, true) end
|
|
||||||
local hasposix , posix = pcall(require, "posix")
|
|
||||||
|
|
||||||
local machineid
|
|
||||||
if hasposix then
|
|
||||||
machineid = posix.uname("%n")
|
|
||||||
else
|
|
||||||
machineid = assert(io.popen("uname -n")):read("*l")
|
|
||||||
end
|
|
||||||
machineid = md5(machineid):sub(1, 6)
|
|
||||||
|
|
||||||
local function uint_to_hex(num, len, be)
|
|
||||||
local len = len or 4
|
|
||||||
local be = be or 0
|
|
||||||
local num = cbson.uint(num)
|
|
||||||
local raw = cbson.uint_to_raw(num, len, be)
|
|
||||||
local out = ''
|
|
||||||
for i = 1, #raw do
|
|
||||||
out = out .. string.format("%02x", raw:byte(i,i))
|
|
||||||
end
|
|
||||||
return out
|
|
||||||
end
|
|
||||||
|
|
||||||
local counter = 0
|
|
||||||
|
|
||||||
if not ngx then
|
|
||||||
math.randomseed(os.time())
|
|
||||||
counter = math.random(100)
|
|
||||||
else
|
|
||||||
local resty_random = require "resty.random"
|
|
||||||
local resty_string = require "resty.string"
|
|
||||||
local strong_random = resty_random.bytes(4,true)
|
|
||||||
while strong_random == nil do
|
|
||||||
strong_random = resty_random.bytes(4,true)
|
|
||||||
end
|
|
||||||
counter = tonumber(resty_string.to_hex(strong_random), 16)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function generate_oid()
|
|
||||||
local pid = ngx and ngx.worker.pid() or nil
|
|
||||||
if not pid then
|
|
||||||
if hasposix then
|
|
||||||
pid = posix.getpid("pid")
|
|
||||||
else
|
|
||||||
pid = 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
pid = uint_to_hex(pid,2)
|
|
||||||
|
|
||||||
counter = counter + 1
|
|
||||||
local time = os.time()
|
|
||||||
|
|
||||||
return uint_to_hex(time, 4, 1) .. machineid .. pid .. uint_to_hex(counter, 4, 1):sub(3,8)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function print_r(t, indent)
|
|
||||||
local indent=indent or ''
|
|
||||||
if #indent > 5 then return end
|
|
||||||
if type(t) ~= "table" then
|
|
||||||
print(t)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
for key,value in pairs(t) do
|
|
||||||
io.write(indent,'[',tostring(key),']')
|
|
||||||
if type(value)=="table" then io.write(':\n') print_r(value,indent..'\t')
|
|
||||||
else io.write(' = ',tostring(value),'\n') end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function parse_uri(url)
|
|
||||||
-- initialize default parameters
|
|
||||||
local parsed = {}
|
|
||||||
-- empty url is parsed to nil
|
|
||||||
if not url or url == "" then return nil, "invalid url" end
|
|
||||||
-- remove whitespace
|
|
||||||
url = string.gsub(url, "%s", "")
|
|
||||||
-- get fragment
|
|
||||||
url = string.gsub(url, "#(.*)$", function(f)
|
|
||||||
parsed.fragment = f
|
|
||||||
return ""
|
|
||||||
end)
|
|
||||||
-- get scheme
|
|
||||||
url = string.gsub(url, "^([%w][%w%+%-%.]*)%:",
|
|
||||||
function(s) parsed.scheme = s; return "" end)
|
|
||||||
|
|
||||||
-- get authority
|
|
||||||
local location
|
|
||||||
url = string.gsub(url, "^//([^/]*)", function(n)
|
|
||||||
location = n
|
|
||||||
return ""
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- get query stringing
|
|
||||||
url = string.gsub(url, "%?(.*)", function(q)
|
|
||||||
parsed.query_string = q
|
|
||||||
return ""
|
|
||||||
end)
|
|
||||||
-- get params
|
|
||||||
url = string.gsub(url, "%;(.*)", function(p)
|
|
||||||
parsed.params = p
|
|
||||||
return ""
|
|
||||||
end)
|
|
||||||
-- path is whatever was left
|
|
||||||
if url ~= "" then parsed.database = string.gsub(url,"^/([^/]*).*","%1") end
|
|
||||||
if not parsed.database or #parsed.database == 0 then parsed.database = "admin" end
|
|
||||||
|
|
||||||
if not location then return parsed end
|
|
||||||
|
|
||||||
location = string.gsub(location,"^([^@]*)@",
|
|
||||||
function(u) parsed.userinfo = u; return "" end)
|
|
||||||
|
|
||||||
parsed.hosts = {}
|
|
||||||
string.gsub(location, "([^,]+)", function(u)
|
|
||||||
local pr = { host = "localhost", port = 27017 }
|
|
||||||
u = string.gsub(u, ":([^:]*)$",
|
|
||||||
function(p) pr.port = p; return "" end)
|
|
||||||
if u ~= "" then pr.host = u end
|
|
||||||
table.insert(parsed.hosts, pr)
|
|
||||||
end)
|
|
||||||
if #parsed.hosts == 0 then parsed.hosts = {{ host = "localhost", port = 27017 }} end
|
|
||||||
|
|
||||||
parsed.query = {}
|
|
||||||
if parsed.query_string then
|
|
||||||
string.gsub(parsed.query_string, "([^&]+)", function(u)
|
|
||||||
u = string.gsub(u, "([^=]*)=([^=]*)$",
|
|
||||||
function(k,v) parsed.query[k] = v; return "" end)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
local userinfo = parsed.userinfo
|
|
||||||
if not userinfo then return parsed end
|
|
||||||
userinfo = string.gsub(userinfo, ":([^:]*)$",
|
|
||||||
function(p) parsed.password = p; return "" end)
|
|
||||||
parsed.user = userinfo
|
|
||||||
return parsed
|
|
||||||
end
|
|
||||||
|
|
||||||
local function xor_bytestr( a, b )
|
|
||||||
local res = ""
|
|
||||||
for i=1,#a do
|
|
||||||
res = res .. string.char(bit.bxor(string.byte(a,i,i), string.byte(b, i, i)))
|
|
||||||
end
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
local function xor_bytestr( a, b )
|
|
||||||
local res = ""
|
|
||||||
for i=1,#a do
|
|
||||||
res = res .. string.char(bit.bxor(string.byte(a,i,i), string.byte(b, i, i)))
|
|
||||||
end
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
-- A simple implementation of PBKDF2_HMAC_SHA1
|
|
||||||
local function pbkdf2_hmac_sha1( pbkdf2_key, iterations, salt, len )
|
|
||||||
local u1 = hmac_sha1(pbkdf2_key, salt .. "\0\0\0\1")
|
|
||||||
local ui = u1
|
|
||||||
for i=1,iterations-1 do
|
|
||||||
u1 = hmac_sha1(pbkdf2_key, u1)
|
|
||||||
ui = xor_bytestr(ui, u1)
|
|
||||||
end
|
|
||||||
if #ui < len then
|
|
||||||
for i=1,len-(#ui) do
|
|
||||||
ui = string.char(0) .. ui
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return ui
|
|
||||||
end
|
|
||||||
|
|
||||||
-- not full implementation, but oh well
|
|
||||||
local function saslprep(username)
|
|
||||||
return string.gsub(string.gsub(username, '=', '=3D'), ',' , '=2C')
|
|
||||||
end
|
|
||||||
|
|
||||||
local function pass_digest ( username , password )
|
|
||||||
return md5(username .. ":mongo:" .. password)
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
parse_uri = parse_uri;
|
|
||||||
print_r = print_r;
|
|
||||||
pbkdf2_hmac_sha1 = pbkdf2_hmac_sha1;
|
|
||||||
saslprep = saslprep;
|
|
||||||
pass_digest = pass_digest;
|
|
||||||
xor_bytestr = xor_bytestr;
|
|
||||||
generate_oid = generate_oid;
|
|
||||||
}
|
|
||||||
@ -1,439 +0,0 @@
|
|||||||
-- Copyright (C) Yichun Zhang (agentzh)
|
|
||||||
|
|
||||||
|
|
||||||
local sub = string.sub
|
|
||||||
local byte = string.byte
|
|
||||||
local tcp = ngx.socket.tcp
|
|
||||||
local null = ngx.null
|
|
||||||
local type = type
|
|
||||||
local pairs = pairs
|
|
||||||
local unpack = unpack
|
|
||||||
local setmetatable = setmetatable
|
|
||||||
local tonumber = tonumber
|
|
||||||
local tostring = tostring
|
|
||||||
local rawget = rawget
|
|
||||||
--local error = error
|
|
||||||
|
|
||||||
|
|
||||||
local ok, new_tab = pcall(require, "table.new")
|
|
||||||
if not ok or type(new_tab) ~= "function" then
|
|
||||||
new_tab = function (narr, nrec) return {} end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local _M = new_tab(0, 54)
|
|
||||||
|
|
||||||
_M._VERSION = '0.26'
|
|
||||||
|
|
||||||
|
|
||||||
local common_cmds = {
|
|
||||||
"get", "set", "mget", "mset",
|
|
||||||
"del", "incr", "decr", -- Strings
|
|
||||||
"llen", "lindex", "lpop", "lpush",
|
|
||||||
"lrange", "linsert", -- Lists
|
|
||||||
"hexists", "hget", "hset", "hmget",
|
|
||||||
--[[ "hmset", ]] "hdel", -- Hashes
|
|
||||||
"smembers", "sismember", "sadd", "srem",
|
|
||||||
"sdiff", "sinter", "sunion", -- Sets
|
|
||||||
"zrange", "zrangebyscore", "zrank", "zadd",
|
|
||||||
"zrem", "zincrby", -- Sorted Sets
|
|
||||||
"auth", "eval", "expire", "script",
|
|
||||||
"sort" -- Others
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
local sub_commands = {
|
|
||||||
"subscribe", "psubscribe"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
local unsub_commands = {
|
|
||||||
"unsubscribe", "punsubscribe"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
local mt = { __index = _M }
|
|
||||||
|
|
||||||
|
|
||||||
function _M.new(self)
|
|
||||||
local sock, err = tcp()
|
|
||||||
if not sock then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
return setmetatable({ _sock = sock, _subscribed = false }, mt)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function _M.set_timeout(self, timeout)
|
|
||||||
local sock = rawget(self, "_sock")
|
|
||||||
if not sock then
|
|
||||||
return nil, "not initialized"
|
|
||||||
end
|
|
||||||
|
|
||||||
return sock:settimeout(timeout)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function _M.connect(self, ...)
|
|
||||||
local sock = rawget(self, "_sock")
|
|
||||||
if not sock then
|
|
||||||
return nil, "not initialized"
|
|
||||||
end
|
|
||||||
|
|
||||||
self._subscribed = false
|
|
||||||
|
|
||||||
return sock:connect(...)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function _M.set_keepalive(self, ...)
|
|
||||||
local sock = rawget(self, "_sock")
|
|
||||||
if not sock then
|
|
||||||
return nil, "not initialized"
|
|
||||||
end
|
|
||||||
|
|
||||||
if rawget(self, "_subscribed") then
|
|
||||||
return nil, "subscribed state"
|
|
||||||
end
|
|
||||||
|
|
||||||
return sock:setkeepalive(...)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function _M.get_reused_times(self)
|
|
||||||
local sock = rawget(self, "_sock")
|
|
||||||
if not sock then
|
|
||||||
return nil, "not initialized"
|
|
||||||
end
|
|
||||||
|
|
||||||
return sock:getreusedtimes()
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function close(self)
|
|
||||||
local sock = rawget(self, "_sock")
|
|
||||||
if not sock then
|
|
||||||
return nil, "not initialized"
|
|
||||||
end
|
|
||||||
|
|
||||||
return sock:close()
|
|
||||||
end
|
|
||||||
_M.close = close
|
|
||||||
|
|
||||||
|
|
||||||
local function _read_reply(self, sock)
|
|
||||||
local line, err = sock:receive()
|
|
||||||
if not line then
|
|
||||||
if err == "timeout" and not rawget(self, "_subscribed") then
|
|
||||||
sock:close()
|
|
||||||
end
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
local prefix = byte(line)
|
|
||||||
|
|
||||||
if prefix == 36 then -- char '$'
|
|
||||||
-- print("bulk reply")
|
|
||||||
|
|
||||||
local size = tonumber(sub(line, 2))
|
|
||||||
if size < 0 then
|
|
||||||
return null
|
|
||||||
end
|
|
||||||
|
|
||||||
local data, err = sock:receive(size)
|
|
||||||
if not data then
|
|
||||||
if err == "timeout" then
|
|
||||||
sock:close()
|
|
||||||
end
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
local dummy, err = sock:receive(2) -- ignore CRLF
|
|
||||||
if not dummy then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
elseif prefix == 43 then -- char '+'
|
|
||||||
-- print("status reply")
|
|
||||||
|
|
||||||
return sub(line, 2)
|
|
||||||
|
|
||||||
elseif prefix == 42 then -- char '*'
|
|
||||||
local n = tonumber(sub(line, 2))
|
|
||||||
|
|
||||||
-- print("multi-bulk reply: ", n)
|
|
||||||
if n < 0 then
|
|
||||||
return null
|
|
||||||
end
|
|
||||||
|
|
||||||
local vals = new_tab(n, 0)
|
|
||||||
local nvals = 0
|
|
||||||
for i = 1, n do
|
|
||||||
local res, err = _read_reply(self, sock)
|
|
||||||
if res then
|
|
||||||
nvals = nvals + 1
|
|
||||||
vals[nvals] = res
|
|
||||||
|
|
||||||
elseif res == nil then
|
|
||||||
return nil, err
|
|
||||||
|
|
||||||
else
|
|
||||||
-- be a valid redis error value
|
|
||||||
nvals = nvals + 1
|
|
||||||
vals[nvals] = {false, err}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return vals
|
|
||||||
|
|
||||||
elseif prefix == 58 then -- char ':'
|
|
||||||
-- print("integer reply")
|
|
||||||
return tonumber(sub(line, 2))
|
|
||||||
|
|
||||||
elseif prefix == 45 then -- char '-'
|
|
||||||
-- print("error reply: ", n)
|
|
||||||
|
|
||||||
return false, sub(line, 2)
|
|
||||||
|
|
||||||
else
|
|
||||||
-- when `line` is an empty string, `prefix` will be equal to nil.
|
|
||||||
return nil, "unknown prefix: \"" .. tostring(prefix) .. "\""
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function _gen_req(args)
|
|
||||||
local nargs = #args
|
|
||||||
|
|
||||||
local req = new_tab(nargs * 5 + 1, 0)
|
|
||||||
req[1] = "*" .. nargs .. "\r\n"
|
|
||||||
local nbits = 2
|
|
||||||
|
|
||||||
for i = 1, nargs do
|
|
||||||
local arg = args[i]
|
|
||||||
if type(arg) ~= "string" then
|
|
||||||
arg = tostring(arg)
|
|
||||||
end
|
|
||||||
|
|
||||||
req[nbits] = "$"
|
|
||||||
req[nbits + 1] = #arg
|
|
||||||
req[nbits + 2] = "\r\n"
|
|
||||||
req[nbits + 3] = arg
|
|
||||||
req[nbits + 4] = "\r\n"
|
|
||||||
|
|
||||||
nbits = nbits + 5
|
|
||||||
end
|
|
||||||
|
|
||||||
-- it is much faster to do string concatenation on the C land
|
|
||||||
-- in real world (large number of strings in the Lua VM)
|
|
||||||
return req
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function _do_cmd(self, ...)
|
|
||||||
local args = {...}
|
|
||||||
|
|
||||||
local sock = rawget(self, "_sock")
|
|
||||||
if not sock then
|
|
||||||
return nil, "not initialized"
|
|
||||||
end
|
|
||||||
|
|
||||||
local req = _gen_req(args)
|
|
||||||
|
|
||||||
local reqs = rawget(self, "_reqs")
|
|
||||||
if reqs then
|
|
||||||
reqs[#reqs + 1] = req
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- print("request: ", table.concat(req))
|
|
||||||
|
|
||||||
local bytes, err = sock:send(req)
|
|
||||||
if not bytes then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
return _read_reply(self, sock)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function _check_subscribed(self, res)
|
|
||||||
if type(res) == "table"
|
|
||||||
and (res[1] == "unsubscribe" or res[1] == "punsubscribe")
|
|
||||||
and res[3] == 0
|
|
||||||
then
|
|
||||||
self._subscribed = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function _M.read_reply(self)
|
|
||||||
local sock = rawget(self, "_sock")
|
|
||||||
if not sock then
|
|
||||||
return nil, "not initialized"
|
|
||||||
end
|
|
||||||
|
|
||||||
if not rawget(self, "_subscribed") then
|
|
||||||
return nil, "not subscribed"
|
|
||||||
end
|
|
||||||
|
|
||||||
local res, err = _read_reply(self, sock)
|
|
||||||
_check_subscribed(self, res)
|
|
||||||
|
|
||||||
return res, err
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
for i = 1, #common_cmds do
|
|
||||||
local cmd = common_cmds[i]
|
|
||||||
|
|
||||||
_M[cmd] =
|
|
||||||
function (self, ...)
|
|
||||||
return _do_cmd(self, cmd, ...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
for i = 1, #sub_commands do
|
|
||||||
local cmd = sub_commands[i]
|
|
||||||
|
|
||||||
_M[cmd] =
|
|
||||||
function (self, ...)
|
|
||||||
self._subscribed = true
|
|
||||||
return _do_cmd(self, cmd, ...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
for i = 1, #unsub_commands do
|
|
||||||
local cmd = unsub_commands[i]
|
|
||||||
|
|
||||||
_M[cmd] =
|
|
||||||
function (self, ...)
|
|
||||||
local res, err = _do_cmd(self, cmd, ...)
|
|
||||||
_check_subscribed(self, res)
|
|
||||||
return res, err
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function _M.hmset(self, hashname, ...)
|
|
||||||
if select('#', ...) == 1 then
|
|
||||||
local t = select(1, ...)
|
|
||||||
|
|
||||||
local n = 0
|
|
||||||
for k, v in pairs(t) do
|
|
||||||
n = n + 2
|
|
||||||
end
|
|
||||||
|
|
||||||
local array = new_tab(n, 0)
|
|
||||||
|
|
||||||
local i = 0
|
|
||||||
for k, v in pairs(t) do
|
|
||||||
array[i + 1] = k
|
|
||||||
array[i + 2] = v
|
|
||||||
i = i + 2
|
|
||||||
end
|
|
||||||
-- print("key", hashname)
|
|
||||||
return _do_cmd(self, "hmset", hashname, unpack(array))
|
|
||||||
end
|
|
||||||
|
|
||||||
-- backwards compatibility
|
|
||||||
return _do_cmd(self, "hmset", hashname, ...)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function _M.init_pipeline(self, n)
|
|
||||||
self._reqs = new_tab(n or 4, 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function _M.cancel_pipeline(self)
|
|
||||||
self._reqs = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function _M.commit_pipeline(self)
|
|
||||||
local reqs = rawget(self, "_reqs")
|
|
||||||
if not reqs then
|
|
||||||
return nil, "no pipeline"
|
|
||||||
end
|
|
||||||
|
|
||||||
self._reqs = nil
|
|
||||||
|
|
||||||
local sock = rawget(self, "_sock")
|
|
||||||
if not sock then
|
|
||||||
return nil, "not initialized"
|
|
||||||
end
|
|
||||||
|
|
||||||
local bytes, err = sock:send(reqs)
|
|
||||||
if not bytes then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
local nvals = 0
|
|
||||||
local nreqs = #reqs
|
|
||||||
local vals = new_tab(nreqs, 0)
|
|
||||||
for i = 1, nreqs do
|
|
||||||
local res, err = _read_reply(self, sock)
|
|
||||||
if res then
|
|
||||||
nvals = nvals + 1
|
|
||||||
vals[nvals] = res
|
|
||||||
|
|
||||||
elseif res == nil then
|
|
||||||
if err == "timeout" then
|
|
||||||
close(self)
|
|
||||||
end
|
|
||||||
return nil, err
|
|
||||||
|
|
||||||
else
|
|
||||||
-- be a valid redis error value
|
|
||||||
nvals = nvals + 1
|
|
||||||
vals[nvals] = {false, err}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return vals
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function _M.array_to_hash(self, t)
|
|
||||||
local n = #t
|
|
||||||
-- print("n = ", n)
|
|
||||||
local h = new_tab(0, n / 2)
|
|
||||||
for i = 1, n, 2 do
|
|
||||||
h[t[i]] = t[i + 1]
|
|
||||||
end
|
|
||||||
return h
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- this method is deperate since we already do lazy method generation.
|
|
||||||
function _M.add_commands(...)
|
|
||||||
local cmds = {...}
|
|
||||||
for i = 1, #cmds do
|
|
||||||
local cmd = cmds[i]
|
|
||||||
_M[cmd] =
|
|
||||||
function (self, ...)
|
|
||||||
return _do_cmd(self, cmd, ...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
setmetatable(_M, {__index = function(self, cmd)
|
|
||||||
local method =
|
|
||||||
function (self, ...)
|
|
||||||
return _do_cmd(self, cmd, ...)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- cache the lazily generated method in our
|
|
||||||
-- module table
|
|
||||||
_M[cmd] = method
|
|
||||||
return method
|
|
||||||
end})
|
|
||||||
|
|
||||||
|
|
||||||
return _M
|
|
||||||
@ -1,321 +0,0 @@
|
|||||||
-----------------------------------------------------------------------------
|
|
||||||
-- SMTP client support for the Lua language.
|
|
||||||
-- LuaSocket toolkit.
|
|
||||||
-- Author: Diego Nehab
|
|
||||||
-- RCS ID: $Id: smtp.lua,v 1.46 2007/03/12 04:08:40 diego Exp $
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
-- Author: Hungpu DU
|
|
||||||
-- ChangeLog:
|
|
||||||
-- * 2014/04/06 03:50:15 - Modified for lua-nginx-module with pure Lua
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
local base = _G
|
|
||||||
local coroutine = require("coroutine")
|
|
||||||
local string = require("string")
|
|
||||||
local math = require("math")
|
|
||||||
local os = require("os")
|
|
||||||
|
|
||||||
local mime = require("resty.smtp.mime")
|
|
||||||
local ltn12 = require("resty.smtp.ltn12")
|
|
||||||
local tp = require("resty.smtp.tp")
|
|
||||||
local misc = require("resty.smtp.misc")
|
|
||||||
|
|
||||||
module("resty.smtp")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
_VERSION = "0.0.3"
|
|
||||||
|
|
||||||
-- timeout for connection
|
|
||||||
TIMEOUT = 6000
|
|
||||||
-- default server used to send e-mails
|
|
||||||
SERVER = "localhost"
|
|
||||||
-- default port
|
|
||||||
PORT = 25
|
|
||||||
-- domain used in HELO command and default sendmail
|
|
||||||
-- If we are under a CGI, try to get from environment
|
|
||||||
DOMAIN = "localhost"
|
|
||||||
-- default time zone (means we don"t know)
|
|
||||||
ZONE = "-0000"
|
|
||||||
|
|
||||||
|
|
||||||
local metat = { __index= {} }
|
|
||||||
|
|
||||||
function metat.__index:greet(domain)
|
|
||||||
self.try(self.tp:expect("2.."))
|
|
||||||
self.try(self.tp:command("EHLO", domain or DOMAIN))
|
|
||||||
|
|
||||||
return misc.skip(1, self.try(self.tp:expect("2..")))
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function metat.__index:mail(from)
|
|
||||||
self.try(self.tp:command("MAIL", "FROM:" .. from))
|
|
||||||
|
|
||||||
return self.try(self.tp:expect("2.."))
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function metat.__index:rcpt(to)
|
|
||||||
self.try(self.tp:command("RCPT", "TO:" .. to))
|
|
||||||
|
|
||||||
return self.try(self.tp:expect("2.."))
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function metat.__index:data(src, step)
|
|
||||||
self.try(self.tp:command("DATA"))
|
|
||||||
self.try(self.tp:expect("3.."))
|
|
||||||
self.try(self.tp:source(src, step))
|
|
||||||
self.try(self.tp:send("\r\n.\r\n"))
|
|
||||||
|
|
||||||
return self.try(self.tp:expect("2.."))
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function metat.__index:quit()
|
|
||||||
self.try(self.tp:command("QUIT"))
|
|
||||||
|
|
||||||
return self.try(self.tp:expect("2.."))
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function metat.__index:close()
|
|
||||||
return self.tp:close()
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function metat.__index:login(user, password)
|
|
||||||
self.try(self.tp:command("AUTH", "LOGIN"))
|
|
||||||
self.try(self.tp:expect("3.."))
|
|
||||||
self.try(self.tp:command(mime.b64(user)))
|
|
||||||
self.try(self.tp:expect("3.."))
|
|
||||||
self.try(self.tp:command(mime.b64(password)))
|
|
||||||
|
|
||||||
return self.try(self.tp:expect("2.."))
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function metat.__index:plain(user, password)
|
|
||||||
local auth = "PLAIN " .. mime.b64("\0" .. user .. "\0" .. password)
|
|
||||||
self.try(self.tp:command("AUTH", auth))
|
|
||||||
return self.try(self.tp:expect("2.."))
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function metat.__index:auth(user, password, ext)
|
|
||||||
if not user or not password then return 1 end
|
|
||||||
|
|
||||||
if string.find(ext, "AUTH[^\n]+LOGIN") then
|
|
||||||
return self:login(user, password)
|
|
||||||
|
|
||||||
elseif string.find(ext, "AUTH[^\n]+PLAIN") then
|
|
||||||
return self:plain(user, password)
|
|
||||||
|
|
||||||
else
|
|
||||||
self.try(nil, "authentication not supported")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- send message or throw an exception
|
|
||||||
function metat.__index:send(mailt)
|
|
||||||
self:mail(mailt.from)
|
|
||||||
|
|
||||||
if base.type(mailt.rcpt) == "table" then
|
|
||||||
for i, v in base.ipairs(mailt.rcpt) do
|
|
||||||
self:rcpt(v)
|
|
||||||
end
|
|
||||||
|
|
||||||
else
|
|
||||||
self:rcpt(mailt.rcpt)
|
|
||||||
end
|
|
||||||
|
|
||||||
self:data(ltn12.source.chain(mailt.source, mime.stuff()), mailt.step)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- private methods
|
|
||||||
--
|
|
||||||
function open(server, port, timeout, create, ssl)
|
|
||||||
local tp = misc.try(tp.connect(server, port, timeout, create, ssl))
|
|
||||||
local session = base.setmetatable({tp= tp}, metat)
|
|
||||||
|
|
||||||
-- make sure tp is closed if we get an exception
|
|
||||||
session.try = misc.newtry(function()
|
|
||||||
session:close()
|
|
||||||
end)
|
|
||||||
return session
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- convert headers to lowercase
|
|
||||||
local function lower_headers(headers)
|
|
||||||
local lower = {}
|
|
||||||
|
|
||||||
for i,v in base.pairs(headers or lower) do
|
|
||||||
lower[string.lower(i)] = v
|
|
||||||
end
|
|
||||||
|
|
||||||
return lower
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- returns a hopefully unique mime boundary
|
|
||||||
local seqno = 0
|
|
||||||
local function newboundary()
|
|
||||||
seqno = seqno + 1
|
|
||||||
|
|
||||||
return string.format("%s%05d==%05u", os.date("%d%m%Y%H%M%S"),
|
|
||||||
math.random(0, 99999), seqno)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- send_message forward declaration
|
|
||||||
local send_message
|
|
||||||
|
|
||||||
-- yield the headers all at once, it"s faster
|
|
||||||
local function send_headers(headers)
|
|
||||||
local h = {}
|
|
||||||
|
|
||||||
for k, v in base.pairs(headers) do
|
|
||||||
base.table.insert(h, base.table.concat({k, v}, ": "))
|
|
||||||
end
|
|
||||||
base.table.insert(h, "\r\n")
|
|
||||||
|
|
||||||
coroutine.yield(base.table.concat(h, "\r\n"))
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- yield multipart message body from a multipart message table
|
|
||||||
local function send_multipart(mesgt)
|
|
||||||
-- make sure we have our boundary and send headers
|
|
||||||
local bd = newboundary()
|
|
||||||
local headers = lower_headers(mesgt.headers or {})
|
|
||||||
|
|
||||||
headers["content-type"] = headers["content-type"] or "multipart/mixed"
|
|
||||||
headers["content-type"] = headers["content-type"] ..
|
|
||||||
'; boundary="' .. bd .. '"'
|
|
||||||
|
|
||||||
send_headers(headers)
|
|
||||||
|
|
||||||
-- send preamble
|
|
||||||
if mesgt.body.preamble then
|
|
||||||
coroutine.yield(mesgt.body.preamble)
|
|
||||||
coroutine.yield("\r\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- send each part separated by a boundary
|
|
||||||
for i, m in base.ipairs(mesgt.body) do
|
|
||||||
coroutine.yield("\r\n--" .. bd .. "\r\n")
|
|
||||||
send_message(m)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- send last boundary
|
|
||||||
coroutine.yield("\r\n--" .. bd .. "--\r\n\r\n")
|
|
||||||
|
|
||||||
-- send epilogue
|
|
||||||
if mesgt.body.epilogue then
|
|
||||||
coroutine.yield(mesgt.body.epilogue)
|
|
||||||
coroutine.yield("\r\n")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- yield message body from a source
|
|
||||||
local function send_source(mesgt)
|
|
||||||
-- make sure we have a content-type
|
|
||||||
local headers = lower_headers(mesgt.headers or {})
|
|
||||||
|
|
||||||
headers["content-type"] = headers["content-type"] or
|
|
||||||
'text/plain; charset="iso-8859-1"'
|
|
||||||
|
|
||||||
send_headers(headers)
|
|
||||||
|
|
||||||
-- send body from source
|
|
||||||
while true do
|
|
||||||
local chunk, err = mesgt.body()
|
|
||||||
if err then coroutine.yield(nil, err)
|
|
||||||
elseif chunk then coroutine.yield(chunk)
|
|
||||||
else break end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- yield message body from a string
|
|
||||||
local function send_string(mesgt)
|
|
||||||
-- make sure we have a content-type
|
|
||||||
local headers = lower_headers(mesgt.headers or {})
|
|
||||||
|
|
||||||
headers["content-type"] = headers["content-type"] or
|
|
||||||
'text/plain; charset="iso-8859-1"'
|
|
||||||
|
|
||||||
send_headers(headers)
|
|
||||||
|
|
||||||
-- send body from string
|
|
||||||
coroutine.yield(mesgt.body)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- message source
|
|
||||||
function send_message(mesgt)
|
|
||||||
if base.type(mesgt.body) == "table" then
|
|
||||||
send_multipart(mesgt)
|
|
||||||
|
|
||||||
elseif base.type(mesgt.body) == "function" then
|
|
||||||
send_source(mesgt)
|
|
||||||
|
|
||||||
else
|
|
||||||
send_string(mesgt)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- set defaul headers
|
|
||||||
local function adjust_headers(mesgt)
|
|
||||||
-- to eliminate duplication for following headers
|
|
||||||
local lower = lower_headers(mesgt.headers)
|
|
||||||
|
|
||||||
lower["date"] = lower["date"] or
|
|
||||||
os.date("!%a, %d %b %Y %H:%M:%S ") .. (mesgt.zone or ZONE)
|
|
||||||
lower["x-mailer"] = lower["x-mailer"] or VERSION
|
|
||||||
-- this can"t be overriden
|
|
||||||
lower["mime-version"] = "1.0"
|
|
||||||
|
|
||||||
return lower
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function message(mesgt)
|
|
||||||
mesgt.headers = adjust_headers(mesgt)
|
|
||||||
|
|
||||||
-- create and return message source
|
|
||||||
local co = coroutine.create(function() send_message(mesgt) end)
|
|
||||||
|
|
||||||
return function()
|
|
||||||
local ok, a, b = coroutine.resume(co)
|
|
||||||
if ok then return a, b
|
|
||||||
else return nil, a end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- public methods
|
|
||||||
--
|
|
||||||
send = misc.except(function(mailt)
|
|
||||||
local session = open(mailt.server or SERVER, mailt.port or PORT,
|
|
||||||
mailt.timeout or TIMEOUT,
|
|
||||||
mailt.create or base.ngx.socket.tcp,
|
|
||||||
mailt.ssl or {enable= false, verify_cert= false})
|
|
||||||
|
|
||||||
local ext = session:greet(mailt.domain)
|
|
||||||
|
|
||||||
session:auth(mailt.user, mailt.password, ext)
|
|
||||||
session:send(mailt)
|
|
||||||
session:quit()
|
|
||||||
|
|
||||||
return session:close()
|
|
||||||
end)
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,142 +0,0 @@
|
|||||||
-----------------------------------------------------------------------------
|
|
||||||
-- base64 convertion routines for the Lua language
|
|
||||||
-- Author: Diego Nehab
|
|
||||||
-- Date: 26/12/2000
|
|
||||||
-- Conforms to: RFC 1521
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
-- Author: Hungpu DU
|
|
||||||
-- Date: 2014/04/06
|
|
||||||
-- ChangeLog:
|
|
||||||
-- * Taken from LuaSocket 1.1 and modified for Lua 5.1
|
|
||||||
-- * Modified to be suitable for b64 filter
|
|
||||||
|
|
||||||
local math = require("math")
|
|
||||||
local string = require("string")
|
|
||||||
|
|
||||||
module("resty.smtp.base64")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
local t64 = {
|
|
||||||
[0] = 'A', [1] = 'B', [2] = 'C', [3] = 'D', [4] = 'E', [5] = 'F', [6] = 'G',
|
|
||||||
[7] = 'H', [8] = 'I', [9] = 'J', [10] = 'K', [11] = 'L', [12] = 'M',
|
|
||||||
[13] = 'N', [14] = 'O', [15] = 'P', [16] = 'Q', [17] = 'R', [18] = 'S',
|
|
||||||
[19] = 'T', [20] = 'U', [21] = 'V', [22] = 'W', [23] = 'X', [24] = 'Y',
|
|
||||||
[25] = 'Z', [26] = 'a', [27] = 'b', [28] = 'c', [29] = 'd', [30] = 'e',
|
|
||||||
[31] = 'f', [32] = 'g', [33] = 'h', [34] = 'i', [35] = 'j', [36] = 'k',
|
|
||||||
[37] = 'l', [38] = 'm', [39] = 'n', [40] = 'o', [41] = 'p', [42] = 'q',
|
|
||||||
[43] = 'r', [44] = 's', [45] = 't', [46] = 'u', [47] = 'v', [48] = 'w',
|
|
||||||
[49] = 'x', [50] = 'y', [51] = 'z', [52] = '0', [53] = '1', [54] = '2',
|
|
||||||
[55] = '3', [56] = '4', [57] = '5', [58] = '6', [59] = '7', [60] = '8',
|
|
||||||
[61] = '9', [62] = '+', [63] = '/',
|
|
||||||
}
|
|
||||||
|
|
||||||
local f64 = {
|
|
||||||
['A'] = 0, ['B'] = 1, ['C'] = 2, ['D'] = 3, ['E'] = 4, ['F'] = 5, ['G'] = 6,
|
|
||||||
['H'] = 7, ['I'] = 8, ['J'] = 9, ['K'] = 10, ['L'] = 11, ['M'] = 12,
|
|
||||||
['N'] = 13, ['O'] = 14, ['P'] = 15, ['Q'] = 16, ['R'] = 17, ['S'] = 18,
|
|
||||||
['T'] = 19, ['U'] = 20, ['V'] = 21, ['W'] = 22, ['X'] = 23, ['Y'] = 24,
|
|
||||||
['Z'] = 25, ['a'] = 26, ['b'] = 27, ['c'] = 28, ['d'] = 29, ['e'] = 30,
|
|
||||||
['f'] = 31, ['g'] = 32, ['h'] = 33, ['i'] = 34, ['j'] = 35, ['k'] = 36,
|
|
||||||
['l'] = 37, ['m'] = 38, ['n'] = 39, ['o'] = 40, ['p'] = 41, ['q'] = 42,
|
|
||||||
['r'] = 43, ['s'] = 44, ['t'] = 45, ['u'] = 46, ['v'] = 47, ['w'] = 48,
|
|
||||||
['x'] = 49, ['y'] = 50, ['z'] = 51, ['0'] = 52, ['1'] = 53, ['2'] = 54,
|
|
||||||
['3'] = 55, ['4'] = 56, ['5'] = 57, ['6'] = 58, ['7'] = 59, ['8'] = 60,
|
|
||||||
['9'] = 61, ['+'] = 62, ['/'] = 63,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
local t2f = function(a, b, c)
|
|
||||||
local s = a:byte() * 65536 + b:byte() * 256 + c:byte()
|
|
||||||
local ca, cb, cc, cd
|
|
||||||
|
|
||||||
cd = math.fmod(s, 64)
|
|
||||||
s = (s - cd) / 64
|
|
||||||
cc = math.fmod(s, 64)
|
|
||||||
s = (s - cc) / 64
|
|
||||||
cb = math.fmod(s, 64)
|
|
||||||
ca = (s - cb) / 64
|
|
||||||
|
|
||||||
return t64[ca] .. t64[cb] .. t64[cc] .. t64[cd]
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local f2t = function(a, b, c, d)
|
|
||||||
local s, ca, cb, cc
|
|
||||||
|
|
||||||
if d ~= "=" then
|
|
||||||
s = f64[a] * 262144 + f64[b] * 4096 + f64[c] * 64 + f64[d]
|
|
||||||
|
|
||||||
cc = math.fmod(s, 256)
|
|
||||||
s = (s - cc) / 256
|
|
||||||
cb = math.fmod(s, 256)
|
|
||||||
ca = (s - cb) / 256
|
|
||||||
|
|
||||||
return string.char(ca, cb, cc)
|
|
||||||
|
|
||||||
elseif c ~= "=" then
|
|
||||||
s = f64[a] * 1024 + f64[b] * 16 + f64[c] / 4
|
|
||||||
cb = math.fmod(s, 256)
|
|
||||||
ca = (s - cb) / 256
|
|
||||||
|
|
||||||
return string.char(ca, cb)
|
|
||||||
|
|
||||||
else
|
|
||||||
s = f64[a] * 4 + f64[b] / 16
|
|
||||||
ca = math.fmod(s, 256)
|
|
||||||
return string.char(ca)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function pad(chunk)
|
|
||||||
local s, a, b, ca, cb, cc
|
|
||||||
|
|
||||||
if #chunk == 0 then return "" end
|
|
||||||
|
|
||||||
_, _, a, b = chunk:find("(.?)(.?)")
|
|
||||||
if b == "" then
|
|
||||||
s = a:byte() * 16
|
|
||||||
cb = math.fmod(s, 64)
|
|
||||||
ca = (s - cb)/64
|
|
||||||
return t64[ca] .. t64[cb] .. "=="
|
|
||||||
|
|
||||||
else
|
|
||||||
s = a:byte() * 1024 + b:byte() * 4
|
|
||||||
cc = math.fmod(s, 64)
|
|
||||||
s = (s - cc) / 64
|
|
||||||
cb = math.fmod(s, 64)
|
|
||||||
ca = (s - cb)/64
|
|
||||||
return t64[ca] .. t64[cb] .. t64[cc] .. "="
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function encode(chunk)
|
|
||||||
local l = #chunk
|
|
||||||
local r = math.fmod(l, 3)
|
|
||||||
|
|
||||||
l = l - r
|
|
||||||
if l <= 0 then return "", chunk end
|
|
||||||
|
|
||||||
return string.gsub(chunk:sub(1, l), "(.)(.)(.)", t2f), chunk:sub(l + 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
function decode(chunk)
|
|
||||||
local l = #chunk
|
|
||||||
local r = math.fmod(l, 4)
|
|
||||||
|
|
||||||
l = l - r
|
|
||||||
if l <= 0 then return "", chunk end
|
|
||||||
|
|
||||||
return string.gsub(chunk:sub(1, l), "(.)(.)(.)(.)", f2t), chunk:sub(l + 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- for Encoded Word
|
|
||||||
--
|
|
||||||
function qencode(chunk) return encode(chunk) end
|
|
||||||
function qdecode(chunk) return decode(chunk) end
|
|
||||||
function qpad(chunk) return pad(chunk) end
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,297 +0,0 @@
|
|||||||
-----------------------------------------------------------------------------
|
|
||||||
-- LTN12 - Filters, sources, sinks and pumps.
|
|
||||||
-- LuaSocket toolkit.
|
|
||||||
-- Author: Diego Nehab
|
|
||||||
-- RCS ID: $Id: ltn12.lua,v 1.31 2006/04/03 04:45:42 diego Exp $
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
-- Declare module
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
local base = _G
|
|
||||||
local string = require("string")
|
|
||||||
local table = require("table")
|
|
||||||
|
|
||||||
module("resty.smtp.ltn12")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
filter = {}
|
|
||||||
source = {}
|
|
||||||
sink = {}
|
|
||||||
pump = {}
|
|
||||||
|
|
||||||
-- 2048 seems to be better in windows...
|
|
||||||
BLOCKSIZE = 2048
|
|
||||||
_VERSION = "LTN12 1.0.1"
|
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
-- Filter stuff
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
-- returns a high level filter that cycles a low-level filter
|
|
||||||
function filter.cycle(low, ctx, extra)
|
|
||||||
base.assert(low)
|
|
||||||
return function(chunk)
|
|
||||||
local ret
|
|
||||||
ret, ctx = low(ctx, chunk, extra)
|
|
||||||
return ret
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- chains a bunch of filters together
|
|
||||||
-- (thanks to Wim Couwenberg)
|
|
||||||
function filter.chain(...)
|
|
||||||
local arg = {...}
|
|
||||||
local n = #arg
|
|
||||||
local top, index = 1, 1
|
|
||||||
local retry = ""
|
|
||||||
return function(chunk)
|
|
||||||
retry = chunk and retry
|
|
||||||
while true do
|
|
||||||
if index == top then
|
|
||||||
chunk = arg[index](chunk)
|
|
||||||
if chunk == "" or top == n then return chunk
|
|
||||||
elseif chunk then index = index + 1
|
|
||||||
else
|
|
||||||
top = top+1
|
|
||||||
index = top
|
|
||||||
end
|
|
||||||
else
|
|
||||||
chunk = arg[index](chunk or "")
|
|
||||||
if chunk == "" then
|
|
||||||
index = index - 1
|
|
||||||
chunk = retry
|
|
||||||
elseif chunk then
|
|
||||||
if index == n then return chunk
|
|
||||||
else index = index + 1 end
|
|
||||||
else base.error("filter returned inappropriate nil") end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
-- Source stuff
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
-- create an empty source
|
|
||||||
local function empty()
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function source.empty()
|
|
||||||
return empty
|
|
||||||
end
|
|
||||||
|
|
||||||
-- returns a source that just outputs an error
|
|
||||||
function source.error(err)
|
|
||||||
return function()
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- creates a file source
|
|
||||||
function source.file(handle, io_err)
|
|
||||||
if handle then
|
|
||||||
return function()
|
|
||||||
local chunk = handle:read(BLOCKSIZE)
|
|
||||||
if not chunk then handle:close() end
|
|
||||||
return chunk
|
|
||||||
end
|
|
||||||
else return source.error(io_err or "unable to open file") end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- turns a fancy source into a simple source
|
|
||||||
function source.simplify(src)
|
|
||||||
base.assert(src)
|
|
||||||
return function()
|
|
||||||
local chunk, err_or_new = src()
|
|
||||||
src = err_or_new or src
|
|
||||||
if not chunk then return nil, err_or_new
|
|
||||||
else return chunk end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- creates string source
|
|
||||||
function source.string(s)
|
|
||||||
if s then
|
|
||||||
local i = 1
|
|
||||||
return function()
|
|
||||||
local chunk = string.sub(s, i, i+BLOCKSIZE-1)
|
|
||||||
i = i + BLOCKSIZE
|
|
||||||
if chunk ~= "" then return chunk
|
|
||||||
else return nil end
|
|
||||||
end
|
|
||||||
else return source.empty() end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- creates rewindable source
|
|
||||||
function source.rewind(src)
|
|
||||||
base.assert(src)
|
|
||||||
local t = {}
|
|
||||||
return function(chunk)
|
|
||||||
if not chunk then
|
|
||||||
chunk = table.remove(t)
|
|
||||||
if not chunk then return src()
|
|
||||||
else return chunk end
|
|
||||||
else
|
|
||||||
table.insert(t, chunk)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function source.chain(src, f)
|
|
||||||
base.assert(src and f)
|
|
||||||
local last_in, last_out = "", ""
|
|
||||||
local state = "feeding"
|
|
||||||
local err
|
|
||||||
return function()
|
|
||||||
if not last_out then
|
|
||||||
base.error('source is empty!', 2)
|
|
||||||
end
|
|
||||||
while true do
|
|
||||||
if state == "feeding" then
|
|
||||||
last_in, err = src()
|
|
||||||
if err then return nil, err end
|
|
||||||
last_out = f(last_in)
|
|
||||||
if not last_out then
|
|
||||||
if last_in then
|
|
||||||
base.error('filter returned inappropriate nil')
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
elseif last_out ~= "" then
|
|
||||||
state = "eating"
|
|
||||||
if last_in then last_in = "" end
|
|
||||||
return last_out
|
|
||||||
end
|
|
||||||
else
|
|
||||||
last_out = f(last_in)
|
|
||||||
if last_out == "" then
|
|
||||||
if last_in == "" then
|
|
||||||
state = "feeding"
|
|
||||||
else
|
|
||||||
base.error('filter returned ""')
|
|
||||||
end
|
|
||||||
elseif not last_out then
|
|
||||||
if last_in then
|
|
||||||
base.error('filter returned inappropriate nil')
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
else
|
|
||||||
return last_out
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- creates a source that produces contents of several sources, one after the
|
|
||||||
-- other, as if they were concatenated
|
|
||||||
-- (thanks to Wim Couwenberg)
|
|
||||||
function source.cat(...)
|
|
||||||
local arg = {...}
|
|
||||||
local src = table.remove(arg, 1)
|
|
||||||
return function()
|
|
||||||
while src do
|
|
||||||
local chunk, err = src()
|
|
||||||
if chunk then return chunk end
|
|
||||||
if err then return nil, err end
|
|
||||||
src = table.remove(arg, 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
-- Sink stuff
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
-- creates a sink that stores into a table
|
|
||||||
function sink.table(t)
|
|
||||||
t = t or {}
|
|
||||||
local f = function(chunk, err)
|
|
||||||
if chunk then table.insert(t, chunk) end
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
return f, t
|
|
||||||
end
|
|
||||||
|
|
||||||
-- turns a fancy sink into a simple sink
|
|
||||||
function sink.simplify(snk)
|
|
||||||
base.assert(snk)
|
|
||||||
return function(chunk, err)
|
|
||||||
local ret, err_or_new = snk(chunk, err)
|
|
||||||
if not ret then return nil, err_or_new end
|
|
||||||
snk = err_or_new or snk
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- creates a file sink
|
|
||||||
function sink.file(handle, io_err)
|
|
||||||
if handle then
|
|
||||||
return function(chunk, err)
|
|
||||||
if not chunk then
|
|
||||||
handle:close()
|
|
||||||
return 1
|
|
||||||
else return handle:write(chunk) end
|
|
||||||
end
|
|
||||||
else return sink.error(io_err or "unable to open file") end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- creates a sink that discards data
|
|
||||||
local function null()
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
function sink.null()
|
|
||||||
return null
|
|
||||||
end
|
|
||||||
|
|
||||||
-- creates a sink that just returns an error
|
|
||||||
function sink.error(err)
|
|
||||||
return function()
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- chains a sink with a filter
|
|
||||||
function sink.chain(f, snk)
|
|
||||||
base.assert(f and snk)
|
|
||||||
return function(chunk, err)
|
|
||||||
if chunk ~= "" then
|
|
||||||
local filtered = f(chunk)
|
|
||||||
local done = chunk and ""
|
|
||||||
while true do
|
|
||||||
local ret, snkerr = snk(filtered, err)
|
|
||||||
if not ret then return nil, snkerr end
|
|
||||||
if filtered == done then return 1 end
|
|
||||||
filtered = f(done)
|
|
||||||
end
|
|
||||||
else return 1 end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
-- Pump stuff
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
-- pumps one chunk from the source to the sink
|
|
||||||
function pump.step(src, snk)
|
|
||||||
local chunk, src_err = src()
|
|
||||||
local ret, snk_err = snk(chunk, src_err)
|
|
||||||
if chunk and ret then return 1
|
|
||||||
else return nil, src_err or snk_err end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- pumps all data from a source to a sink, using a step function
|
|
||||||
function pump.all(src, snk, step)
|
|
||||||
base.assert(src and snk)
|
|
||||||
step = step or pump.step
|
|
||||||
while true do
|
|
||||||
local ret, err = step(src, snk)
|
|
||||||
if not ret then
|
|
||||||
if err then return nil, err
|
|
||||||
else return 1 end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@ -1,271 +0,0 @@
|
|||||||
local base = _G
|
|
||||||
local string = require("string")
|
|
||||||
local base64 = require("resty.smtp.base64")
|
|
||||||
local qpcore = require("resty.smtp.qp")
|
|
||||||
|
|
||||||
module("resty.smtp.mime")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- FIXME following mime-relative string operations are quite inefficient
|
|
||||||
-- compared with original C version, maybe FFI can help?
|
|
||||||
--
|
|
||||||
-- base64
|
|
||||||
--
|
|
||||||
function b64(ctx, chunk, extra)
|
|
||||||
local part1, part2
|
|
||||||
|
|
||||||
if not ctx then return nil, nil end
|
|
||||||
|
|
||||||
-- remaining data from last round
|
|
||||||
part1, ctx = base64.encode(ctx)
|
|
||||||
|
|
||||||
if not chunk then
|
|
||||||
part1 = part1 .. base64.pad(ctx)
|
|
||||||
|
|
||||||
if #part1 == 0 then return nil, nil
|
|
||||||
else return part1, nil end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- second part
|
|
||||||
part2, ctx = base64.encode(ctx .. chunk)
|
|
||||||
|
|
||||||
return part1 .. part2, ctx
|
|
||||||
end
|
|
||||||
|
|
||||||
function unb64(ctx, chunk, extra)
|
|
||||||
local part1, part2
|
|
||||||
|
|
||||||
if not ctx then return nil, nil end
|
|
||||||
|
|
||||||
-- remaining data from last round
|
|
||||||
part1, ctx = base64.decode(ctx)
|
|
||||||
|
|
||||||
if not chunk then
|
|
||||||
if #part1 == 0 then return nil, nil
|
|
||||||
else return part1, nil end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- second part
|
|
||||||
part2, ctx = base64.decode(ctx .. chunk)
|
|
||||||
|
|
||||||
return part1 .. part2, ctx
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- quoted-printable
|
|
||||||
--
|
|
||||||
function qp(ctx, chunk, extra)
|
|
||||||
local part1, part2, marker
|
|
||||||
|
|
||||||
if not ctx then return nil, nil end
|
|
||||||
|
|
||||||
marker = extra or "\r\n"
|
|
||||||
part1, ctx = qpcore.encode(ctx, marker)
|
|
||||||
|
|
||||||
if not chunk then
|
|
||||||
part1 = part1 .. qpcore.pad(ctx)
|
|
||||||
|
|
||||||
if #part1 == 0 then return nil, nil
|
|
||||||
else return part1, nil end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- second part
|
|
||||||
part2, ctx = qpcore.encode(ctx .. chunk, marker)
|
|
||||||
|
|
||||||
return part1 .. part2, ctx
|
|
||||||
end
|
|
||||||
|
|
||||||
function unqp(ctx, chunk, extra)
|
|
||||||
local part1, part2
|
|
||||||
|
|
||||||
if not ctx then return nil, nil end
|
|
||||||
|
|
||||||
-- remaining data from last round
|
|
||||||
part1, ctx = qpcore.decode(ctx)
|
|
||||||
|
|
||||||
if not chunk then
|
|
||||||
if #part1 == 0 then return nil, nil
|
|
||||||
else return part1, nil end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- second part
|
|
||||||
part2, ctx = qpcore.decode(ctx .. chunk)
|
|
||||||
|
|
||||||
return part1 .. part2, ctx
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- line-wrap
|
|
||||||
--
|
|
||||||
function wrp(ctx, chunk, extra)
|
|
||||||
-- `ctx` shows how many more bytes current line can still hold
|
|
||||||
-- before reach the limit `length`
|
|
||||||
local buffer, length = "", extra or 76
|
|
||||||
|
|
||||||
if not chunk then
|
|
||||||
-- last line already has some chars except \r\n
|
|
||||||
if ctx < length then return buffer .. "\r\n", length
|
|
||||||
else return nil, length end
|
|
||||||
end
|
|
||||||
|
|
||||||
for i = 1, #chunk do
|
|
||||||
local char = chunk:sub(i, i)
|
|
||||||
|
|
||||||
if char == '\r' then
|
|
||||||
-- take it as part of "\r\n"
|
|
||||||
elseif char == '\n' then
|
|
||||||
buffer, ctx = buffer .. "\r\n", length
|
|
||||||
else
|
|
||||||
if ctx <= 0 then -- hit the limit
|
|
||||||
buffer, ctx = buffer .. "\r\n", length
|
|
||||||
end
|
|
||||||
|
|
||||||
buffer, ctx = buffer .. char, ctx - 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return buffer, ctx
|
|
||||||
end
|
|
||||||
|
|
||||||
function qpwrp(ctx, chunk, extra)
|
|
||||||
-- `ctx` shows how many more bytes current line can still hold
|
|
||||||
-- before reach the limit `length`
|
|
||||||
local buffer, length = "", extra or 76
|
|
||||||
|
|
||||||
if not chunk then
|
|
||||||
-- last line already has some chars except \r\n
|
|
||||||
if ctx < length then return buffer .. "=\r\n", length
|
|
||||||
else return nil, length end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
for i = 1, #chunk do
|
|
||||||
local char = chunk:sub(i, i)
|
|
||||||
|
|
||||||
if char == '\r' then
|
|
||||||
-- take it as part of "\r\n"
|
|
||||||
elseif char == '\n' then
|
|
||||||
buffer, ctx = buffer .. "\r\n", length
|
|
||||||
elseif char == '=' then
|
|
||||||
if ctx <= 3 then
|
|
||||||
buffer, ctx = buffer .. "=\r\n", length
|
|
||||||
end
|
|
||||||
|
|
||||||
buffer, ctx = buffer .. char, ctx - 1
|
|
||||||
|
|
||||||
else
|
|
||||||
if ctx <= 1 then
|
|
||||||
buffer, ctx = buffer .. "=\r\n", length
|
|
||||||
end
|
|
||||||
|
|
||||||
buffer, ctx = buffer .. char, ctx - 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return buffer, ctx
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- encoded word
|
|
||||||
--
|
|
||||||
function ew(ctx, chunk, extra)
|
|
||||||
local part0, part1, part2 = "", "", ""
|
|
||||||
local c, e, f
|
|
||||||
|
|
||||||
base.assert(base.type(extra) == "table")
|
|
||||||
|
|
||||||
c = extra.charset or "utf-8"
|
|
||||||
e = extra.encoding or "B"
|
|
||||||
m = (e == "Q") and qpcore or base64
|
|
||||||
|
|
||||||
-- TODO not support Q-encoding yet
|
|
||||||
base.assert(e == "B")
|
|
||||||
|
|
||||||
if extra.initial == nil or extra.initial then
|
|
||||||
part0 = string.format("=?%s?%s?", c, e)
|
|
||||||
extra.initial = false
|
|
||||||
end
|
|
||||||
|
|
||||||
part1, ctx = m.qencode(ctx, true)
|
|
||||||
|
|
||||||
if not chunk then
|
|
||||||
part1 = part1 .. m.qpad(ctx, true)
|
|
||||||
return part0 .. part1 .. "?=", nil
|
|
||||||
end
|
|
||||||
|
|
||||||
part2, ctx = m.qencode(ctx .. chunk, true)
|
|
||||||
|
|
||||||
return part0 .. part1 .. part2, ctx
|
|
||||||
end
|
|
||||||
|
|
||||||
--
|
|
||||||
-- extra - the charset to converted to
|
|
||||||
function unew(ctx, chunk, extra)
|
|
||||||
-- TODO
|
|
||||||
-- This one needs a little more work, because we have to decode
|
|
||||||
-- `chunk` with both specified encoding and charset on the fly.
|
|
||||||
--
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- dot
|
|
||||||
--
|
|
||||||
function dot(ctx, chunk, extra)
|
|
||||||
local buffer = ""
|
|
||||||
|
|
||||||
if not chunk then return nil, 2 end
|
|
||||||
|
|
||||||
for i = 1, #chunk do
|
|
||||||
local char = string.char(string.byte(chunk, i))
|
|
||||||
|
|
||||||
buffer = buffer .. char
|
|
||||||
|
|
||||||
if char == '\r' then
|
|
||||||
ctx = 1
|
|
||||||
elseif char == '\n' then
|
|
||||||
ctx = (ctx == 1) and 2 or 0
|
|
||||||
elseif char == "." then
|
|
||||||
if ctx == 2 then buffer = buffer .. "." end
|
|
||||||
ctx = 0
|
|
||||||
else
|
|
||||||
ctx = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return buffer, ctx
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- eol
|
|
||||||
--
|
|
||||||
function eol(ctx, chunk, marker)
|
|
||||||
local buffer = ""
|
|
||||||
|
|
||||||
if not chunk then return nil, 0 end
|
|
||||||
|
|
||||||
local eolcandidate = function(char)
|
|
||||||
return (char == '\r') or (char == '\n')
|
|
||||||
end
|
|
||||||
|
|
||||||
for i = 1, #chunk do
|
|
||||||
local char = string.char(string.byte(chunk, i))
|
|
||||||
|
|
||||||
if eolcandidate(char) then
|
|
||||||
if eolcandidate(ctx) then
|
|
||||||
if char == ctx then buffer = buffer .. marker end
|
|
||||||
ctx = 0
|
|
||||||
else
|
|
||||||
buffer = buffer .. marker
|
|
||||||
ctx = char
|
|
||||||
end
|
|
||||||
|
|
||||||
else
|
|
||||||
buffer = buffer .. char
|
|
||||||
ctx = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return buffer, ctx
|
|
||||||
end
|
|
||||||
|
|
||||||
@ -1,103 +0,0 @@
|
|||||||
-----------------------------------------------------------------------------
|
|
||||||
-- MIME support for the Lua language.
|
|
||||||
-- Author: Diego Nehab
|
|
||||||
-- Conforming to RFCs 2045-2049
|
|
||||||
-- RCS ID: $Id: mime.lua,v 1.29 2007/06/11 23:44:54 diego Exp $
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
-- Declare module and import dependencies
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
local base = _G
|
|
||||||
local io = require("io")
|
|
||||||
local string = require("string")
|
|
||||||
|
|
||||||
require("resty.smtp.mime-core")
|
|
||||||
local ltn12 = require("resty.smtp.ltn12")
|
|
||||||
|
|
||||||
module("resty.smtp.mime")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- encode, decode and wrap algorithm tables
|
|
||||||
encodet = {}
|
|
||||||
decodet = {}
|
|
||||||
wrapt = {}
|
|
||||||
|
|
||||||
-- creates a function that chooses a filter by name from a given table
|
|
||||||
local function choose(table)
|
|
||||||
return function(name, opt1, opt2)
|
|
||||||
if base.type(name) ~= "string" then
|
|
||||||
name, opt1, opt2 = "default", name, opt1
|
|
||||||
end
|
|
||||||
local f = table[name or "nil"]
|
|
||||||
if not f then
|
|
||||||
base.error("unknown key (" .. base.tostring(name) .. ")", 3)
|
|
||||||
else return f(opt1, opt2) end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- define the encoding filters
|
|
||||||
encodet['base64'] = function()
|
|
||||||
return ltn12.filter.cycle(b64, "")
|
|
||||||
end
|
|
||||||
|
|
||||||
encodet['quoted-printable'] = function(mode)
|
|
||||||
return ltn12.filter.cycle(qp, "",
|
|
||||||
(mode == "binary") and "=0D=0A" or "\r\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
encodet['encoded-word'] = function(charset, encoding)
|
|
||||||
return ltn12.filter.cycle(ew, "",
|
|
||||||
{ charset= charset or "utf-8", encoding= encoding or "B" })
|
|
||||||
end
|
|
||||||
|
|
||||||
-- define the decoding filters
|
|
||||||
decodet['base64'] = function()
|
|
||||||
return ltn12.filter.cycle(unb64, "")
|
|
||||||
end
|
|
||||||
|
|
||||||
decodet['quoted-printable'] = function()
|
|
||||||
return ltn12.filter.cycle(unqp, "")
|
|
||||||
end
|
|
||||||
|
|
||||||
decodet['encoded-word'] = function(encoding)
|
|
||||||
return ltn12.filter.cycle(unew, "",
|
|
||||||
{ encoding= encoding or "Q" })
|
|
||||||
end
|
|
||||||
|
|
||||||
local function format(chunk)
|
|
||||||
if chunk then
|
|
||||||
if chunk == "" then return "''"
|
|
||||||
else return string.len(chunk) end
|
|
||||||
else return "nil" end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- define the line-wrap filters
|
|
||||||
wrapt['text'] = function(length)
|
|
||||||
length = length or 76
|
|
||||||
return ltn12.filter.cycle(wrp, length, length)
|
|
||||||
end
|
|
||||||
wrapt['base64'] = wrapt['text']
|
|
||||||
wrapt['default'] = wrapt['text']
|
|
||||||
|
|
||||||
wrapt['quoted-printable'] = function()
|
|
||||||
return ltn12.filter.cycle(qpwrp, 76, 76)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- function that choose the encoding, decoding or wrap algorithm
|
|
||||||
encode = choose(encodet)
|
|
||||||
decode = choose(decodet)
|
|
||||||
wrap = choose(wrapt)
|
|
||||||
|
|
||||||
-- define the end-of-line normalization filter
|
|
||||||
function normalize(marker)
|
|
||||||
if not marker then marker = '\r\n' end
|
|
||||||
return ltn12.filter.cycle(eol, 0, marker)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- high level stuffing filter
|
|
||||||
function stuff()
|
|
||||||
return ltn12.filter.cycle(dot, 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
local base = _G
|
|
||||||
|
|
||||||
module("resty.smtp.misc")
|
|
||||||
|
|
||||||
|
|
||||||
function skip(amount, ...)
|
|
||||||
return base.unpack({ ... }, amount + 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function newtry(atexit)
|
|
||||||
return function(...)
|
|
||||||
local ret, err = base.select(1, ...), base.select(2, ...)
|
|
||||||
|
|
||||||
if ret then return ... end
|
|
||||||
if base.type(atexit) == "function" then atexit() end
|
|
||||||
|
|
||||||
base.error(err, 2)
|
|
||||||
-- never be here
|
|
||||||
return ret
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function except(func)
|
|
||||||
return function(...)
|
|
||||||
local ok, ret = base.pcall(func, ...)
|
|
||||||
|
|
||||||
if not ok then return nil, ret
|
|
||||||
else return ret end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
try = newtry()
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,222 +0,0 @@
|
|||||||
local base = _G
|
|
||||||
local math = require("math")
|
|
||||||
local table = require("table")
|
|
||||||
local string = require("string")
|
|
||||||
|
|
||||||
module("resty.smtp.qp")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--[[
|
|
||||||
|
|
||||||
[Quoted-Printable Rules](http://en.wikipedia.org/wiki/Quoted-printable)
|
|
||||||
|
|
||||||
* "All characters except printable ASCII characters" or "end of line characters"
|
|
||||||
must be encoded.
|
|
||||||
|
|
||||||
* All printable ASCII characters (decimal 33-126) may be represented by
|
|
||||||
themselves, except "=" (decimal 61)
|
|
||||||
|
|
||||||
* ASCII tab (decimal 9) and space (decimal 32) may be represented by themselves,
|
|
||||||
except if these characters would appear at the end of the encoded line. In
|
|
||||||
that case, they would need to be escaped as "=09" or "=20" (what we use), or
|
|
||||||
be followed by a "=" (soft line break).
|
|
||||||
|
|
||||||
* If the data being encoded contains meaningful line breaks, they must be
|
|
||||||
encoded as an ASCII CRLF sequence. Conversely, if byte value 13 and 10 have
|
|
||||||
meanings other than end of line (in media types, for example), they must
|
|
||||||
be encoded as "=0D" and "=0A" respectively.
|
|
||||||
|
|
||||||
* Lines of Quoted-Printable encoded data must not be longer than 76 characters.
|
|
||||||
To satisfy this requirement without altering the encoded text,
|
|
||||||
_soft line breaks_ consists of an "=" at the end of an encoded line, and does
|
|
||||||
not appear as a line break in the decoded text.
|
|
||||||
|
|
||||||
|
|
||||||
Encoded-Word - A slightly modified version of Quoted-Printable used in message
|
|
||||||
headers.
|
|
||||||
|
|
||||||
|
|
||||||
[RFC 2045](http://tools.ietf.org/html/rfc2045#page-19)
|
|
||||||
|
|
||||||
--]]
|
|
||||||
|
|
||||||
local QP_PLAIN = 0
|
|
||||||
local QP_QUOTE = 1
|
|
||||||
local QP_IF_LAST = 3
|
|
||||||
local QP_BYTE_CR = 13 -- '\r'
|
|
||||||
local QP_BYTE_LF = 10 -- '\n'
|
|
||||||
|
|
||||||
|
|
||||||
qpte = {
|
|
||||||
-- 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 2, 1, 1, -- 0 - 15
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -- 16 - 31
|
|
||||||
3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -- 32 - 47
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, -- 48 - 63
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -- 64 - 79
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -- 80 - 95
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -- 96 - 111
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -- 112 - 127
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -- 128 - 143
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -- 144 - 159
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -- 160 - 175
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -- 176 - 191
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -- 192 - 207
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -- 208 - 223
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -- 224 - 239
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -- 240 - 255
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
qptd = {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
local HEX_BASE = "0123456789ABCDEF"
|
|
||||||
|
|
||||||
local quote = function(byte)
|
|
||||||
local f, s = math.floor(byte/16) + 1, math.fmod(byte, 16) + 1
|
|
||||||
return table.concat({ '=', HEX_BASE:sub(f, f), HEX_BASE:sub(s, s) })
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local HEX_LOOKUP = {
|
|
||||||
['0']= 0, ['1']= 1, ['2']= 2, ['3']= 3,
|
|
||||||
['4']= 4, ['5']= 5, ['6']= 6, ['7']= 7,
|
|
||||||
['8']= 8, ['9']= 9, ['a']= 10, ['b']= 11,
|
|
||||||
['c']= 12, ['d']= 13, ['e']= 14, ['f']= 15,
|
|
||||||
['A']= 10, ['B']= 11,
|
|
||||||
['C']= 12, ['D']= 13, ['E']= 14, ['F']= 15,
|
|
||||||
}
|
|
||||||
|
|
||||||
local unquote = function(fp, sp)
|
|
||||||
local hp, lp = HEX_LOOKUP[fp], HEX_LOOKUP[sp]
|
|
||||||
|
|
||||||
if not hp or not lp then return nil
|
|
||||||
else return string.char(hp * 16 + lp) end
|
|
||||||
end
|
|
||||||
|
|
||||||
local printable = function(char)
|
|
||||||
local byte = string.byte(char)
|
|
||||||
return (byte > 31) and (byte < 127)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function pad(chunk)
|
|
||||||
local buffer, byte = "", 0
|
|
||||||
|
|
||||||
for i = 1, #chunk do
|
|
||||||
byte = string.byte(chunk, i)
|
|
||||||
|
|
||||||
if qpte[byte + 1] == QP_PLAIN then
|
|
||||||
buffer = buffer .. string.char(byte)
|
|
||||||
else
|
|
||||||
buffer = buffer .. quote(byte)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- soft break
|
|
||||||
if #buffer > 0 then buffer = buffer .. "=\r\n" end
|
|
||||||
|
|
||||||
return buffer
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function encode(chunk, marker)
|
|
||||||
local atom, buffer = {}, ""
|
|
||||||
|
|
||||||
for i = 1, #chunk do
|
|
||||||
table.insert(atom, string.byte(chunk, i))
|
|
||||||
|
|
||||||
repeat
|
|
||||||
local shift = 1
|
|
||||||
|
|
||||||
if atom[1] == QP_BYTE_CR then
|
|
||||||
if #atom < 2 then -- need more
|
|
||||||
break
|
|
||||||
elseif atom[2] == QP_BYTE_LF then
|
|
||||||
buffer, shift = buffer .. marker, 2
|
|
||||||
else
|
|
||||||
buffer = buffer .. quote(atom[1])
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif qpte[atom[1] + 1] == QP_IF_LAST then
|
|
||||||
if #atom < 3 then -- need more
|
|
||||||
break
|
|
||||||
elseif atom[2] == QP_BYTE_CR and atom[3] == QP_BYTE_LF then
|
|
||||||
buffer, shift = buffer .. quote(atom[1]) .. marker, 3
|
|
||||||
else -- space not in the end
|
|
||||||
buffer = buffer .. string.char(atom[1])
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif qpte[atom[1] + 1] == QP_QUOTE then
|
|
||||||
buffer = buffer .. quote(atom[1])
|
|
||||||
|
|
||||||
else -- printable char
|
|
||||||
buffer = buffer .. string.char(atom[1])
|
|
||||||
end
|
|
||||||
|
|
||||||
-- shift out used chars
|
|
||||||
for i = 1, 3 do atom[i] = atom[i + shift] end
|
|
||||||
|
|
||||||
until #atom == 0
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
for i = 1, 3 do
|
|
||||||
atom[i] = atom[i] and string.char(atom[i]) or ""
|
|
||||||
end
|
|
||||||
|
|
||||||
return buffer, table.concat(atom, "")
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function decode(chunk)
|
|
||||||
local atom, buffer = {}, ""
|
|
||||||
|
|
||||||
for i = 1, #chunk do
|
|
||||||
table.insert(atom, chunk:sub(i, i))
|
|
||||||
|
|
||||||
repeat
|
|
||||||
local shift = 3
|
|
||||||
|
|
||||||
if atom[1] == '=' then
|
|
||||||
if #atom < 3 then -- need more
|
|
||||||
break
|
|
||||||
elseif atom[2] == '\r' and atom[3] == '\n' then
|
|
||||||
-- eliminate soft line break
|
|
||||||
else
|
|
||||||
local char = unquote(atom[2], atom[3])
|
|
||||||
if not char then
|
|
||||||
buffer = buffer .. table.concat(atom, "")
|
|
||||||
else
|
|
||||||
buffer = buffer .. char
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif atom[1] == '\r' then
|
|
||||||
if #atom < 2 then -- need more
|
|
||||||
break
|
|
||||||
elseif atom[2] == '\n' then
|
|
||||||
buffer, shift = buffer .. "\r\n", 2
|
|
||||||
else -- neglect this '\r' and following char
|
|
||||||
shift = 2
|
|
||||||
end
|
|
||||||
|
|
||||||
else
|
|
||||||
if atom[1] == '\t' or printable(atom[1]) then
|
|
||||||
buffer, shift = buffer .. atom[1], 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- shift out used chars
|
|
||||||
for i = 1, 3 do atom[i] = atom[i + shift] end
|
|
||||||
|
|
||||||
until #atom == 0
|
|
||||||
end
|
|
||||||
|
|
||||||
return buffer, table.concat(atom, "")
|
|
||||||
end
|
|
||||||
|
|
||||||
@ -1,145 +0,0 @@
|
|||||||
-----------------------------------------------------------------------------
|
|
||||||
-- Unified SMTP/FTP subsystem
|
|
||||||
-- LuaSocket toolkit.
|
|
||||||
-- Author: Diego Nehab
|
|
||||||
-- RCS ID: $Id: tp.lua,v 1.22 2006/03/14 09:04:15 diego Exp $
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
-- Author: duhoobo
|
|
||||||
-- ChangeLog:
|
|
||||||
-- * 2014/04/06 03:47:15 - simplified for lua-module-module
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
local base = _G
|
|
||||||
local string = require("string")
|
|
||||||
|
|
||||||
local ltn12 = require("resty.smtp.ltn12")
|
|
||||||
local misc = require("resty.smtp.misc")
|
|
||||||
|
|
||||||
module("resty.smtp.tp")
|
|
||||||
|
|
||||||
|
|
||||||
-- gets server reply (works for SMTP and FTP)
|
|
||||||
local function get_reply(c)
|
|
||||||
local code, current, sep
|
|
||||||
local line, err = c:receive("*l")
|
|
||||||
local reply = line
|
|
||||||
|
|
||||||
if err then return nil, err end
|
|
||||||
|
|
||||||
code, sep = misc.skip(2, string.find(line, "^(%d%d%d)(.?)"))
|
|
||||||
|
|
||||||
if not code then return nil, "invalid server reply" end
|
|
||||||
if sep == "-" then -- reply is multiline
|
|
||||||
repeat
|
|
||||||
line, err = c:receive("*l")
|
|
||||||
|
|
||||||
if err then return nil, err end
|
|
||||||
|
|
||||||
current, sep = misc.skip(2, string.find(line, "^(%d%d%d)(.?)"))
|
|
||||||
reply = reply .. "\n" .. line
|
|
||||||
-- reply ends with same code
|
|
||||||
until code == current and sep == " "
|
|
||||||
end
|
|
||||||
|
|
||||||
return code, reply
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- metatable for sock object
|
|
||||||
local metat = {__index= {}}
|
|
||||||
|
|
||||||
|
|
||||||
function metat.__index:expect(check)
|
|
||||||
local code, reply = get_reply(self.c)
|
|
||||||
|
|
||||||
if not code then return nil, reply end
|
|
||||||
|
|
||||||
if base.type(check) ~= "function" then
|
|
||||||
if base.type(check) == "table" then
|
|
||||||
for i, v in base.ipairs(check) do
|
|
||||||
if string.find(code, v) then
|
|
||||||
return base.tonumber(code), reply
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return nil, reply
|
|
||||||
|
|
||||||
else -- string
|
|
||||||
if string.find(code, check) then
|
|
||||||
return base.tonumber(code), reply
|
|
||||||
else return nil, reply end
|
|
||||||
end
|
|
||||||
|
|
||||||
else return check(base.tonumber(code), reply) end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function metat.__index:command(cmd, arg)
|
|
||||||
local request = cmd .. (arg and (" " .. arg) or "") .. "\r\n"
|
|
||||||
return self.c:send(request)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function metat.__index:sink(snk, pat)
|
|
||||||
local chunk, err = c:receive(pat)
|
|
||||||
return snk(chunk, err)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function metat.__index:send(data)
|
|
||||||
return self.c:send(data)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function metat.__index:receive(pat)
|
|
||||||
return self.c:receive(pat)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function metat.__index:source(source, step)
|
|
||||||
local sink = function(chunk, err)
|
|
||||||
if chunk then return self:send(chunk)
|
|
||||||
else return 1 end
|
|
||||||
end
|
|
||||||
|
|
||||||
return ltn12.pump.all(source, sink, step or ltn12.pump.step)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- closes the underlying c
|
|
||||||
function metat.__index:close()
|
|
||||||
self.c:close()
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- connect with server and return c object
|
|
||||||
function connect(host, port, timeout, create, ssl)
|
|
||||||
local c, e = create()
|
|
||||||
if not c then return nil, e end
|
|
||||||
|
|
||||||
c:settimeout(timeout)
|
|
||||||
|
|
||||||
local r, e = c:connect(host, port)
|
|
||||||
if not r then
|
|
||||||
c:close()
|
|
||||||
return nil, e
|
|
||||||
end
|
|
||||||
|
|
||||||
if ssl.enable then
|
|
||||||
if not c.sslhandshake then
|
|
||||||
c:close()
|
|
||||||
return nil, "socket does not have ssl support"
|
|
||||||
end
|
|
||||||
|
|
||||||
local s, e = c:sslhandshake(nil, host, ssl.verify_cert)
|
|
||||||
if not s then
|
|
||||||
c:close()
|
|
||||||
return nil, "ssl handshake: " .. e
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return base.setmetatable({c= c}, metat)
|
|
||||||
end
|
|
||||||
|
|
||||||
@ -1,153 +0,0 @@
|
|||||||
local type = type
|
|
||||||
local pairs = pairs
|
|
||||||
local type = type
|
|
||||||
local mceil = math.ceil
|
|
||||||
local mfloor = math.floor
|
|
||||||
local mrandom = math.random
|
|
||||||
local mmodf = math.modf
|
|
||||||
local sgsub = string.gsub
|
|
||||||
local tinsert = table.insert
|
|
||||||
local date = require("app.libs.date")
|
|
||||||
local resty_sha256 = require "resty.sha256"
|
|
||||||
local str = require "resty.string"
|
|
||||||
local ngx_quote_sql_str = ngx.quote_sql_str
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
local _M = {}
|
|
||||||
|
|
||||||
function _M.encode(s)
|
|
||||||
local sha256 = resty_sha256:new()
|
|
||||||
sha256:update(s)
|
|
||||||
local digest = sha256:final()
|
|
||||||
return str.to_hex(digest)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function _M.clear_slash(s)
|
|
||||||
s, _ = sgsub(s, "(/+)", "/")
|
|
||||||
return s
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.is_table_empty(t)
|
|
||||||
if t == nil or _G.next(t) == nil then
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.table_is_array(t)
|
|
||||||
if type(t) ~= "table" then return false end
|
|
||||||
local i = 0
|
|
||||||
for _ in pairs(t) do
|
|
||||||
i = i + 1
|
|
||||||
if t[i] == nil then return false end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.mixin(a, b)
|
|
||||||
if a and b then
|
|
||||||
for k, v in pairs(b) do
|
|
||||||
a[k] = b[k]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return a
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.random()
|
|
||||||
return mrandom(0, 1000)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function _M.total_page(total_count, page_size)
|
|
||||||
local total_page = 0
|
|
||||||
if total_count % page_size == 0 then
|
|
||||||
total_page = total_count / page_size
|
|
||||||
else
|
|
||||||
local tmp, _ = mmodf(total_count/page_size)
|
|
||||||
total_page = tmp + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
return total_page
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function _M.days_after_registry(req)
|
|
||||||
local diff = 0
|
|
||||||
local diff_days = 0 -- default value, days after registry
|
|
||||||
|
|
||||||
if req and req.session then
|
|
||||||
local user = req.session.get("user")
|
|
||||||
local create_time = user and user.create_time
|
|
||||||
if create_time then
|
|
||||||
local now = date() -- seconds
|
|
||||||
create_time = date(create_time)
|
|
||||||
diff = date.diff(now, create_time):spandays()
|
|
||||||
diff_days = mfloor(diff)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return diff_days, diff
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.now()
|
|
||||||
local n = date()
|
|
||||||
local result = n:fmt("%Y-%m-%d %H:%M:%S")
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.secure_str(str)
|
|
||||||
return ngx_quote_sql_str(str)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function _M.string_split(str, delimiter)
|
|
||||||
if str==nil or str=='' or delimiter==nil then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local result = {}
|
|
||||||
for match in (str..delimiter):gmatch("(.-)"..delimiter) do
|
|
||||||
tinsert(result, match)
|
|
||||||
end
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
return _M
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- local resty_sha256 = require "resty.sha256"
|
|
||||||
-- local str = require "resty.string"
|
|
||||||
-- local sha256 = resty_sha256:new()
|
|
||||||
-- ngx.say(sha256:update("hello"))
|
|
||||||
-- local digest = sha256:final()
|
|
||||||
-- ngx.say("sha256: ", str.to_hex(digest))
|
|
||||||
|
|
||||||
-- local resty_md5 = require "resty.md5"
|
|
||||||
-- local md5 = resty_md5:new()
|
|
||||||
-- if not md5 then
|
|
||||||
-- ngx.say("failed to create md5 object")
|
|
||||||
-- return
|
|
||||||
-- end
|
|
||||||
|
|
||||||
-- local ok = md5:update("hel")
|
|
||||||
-- if not ok then
|
|
||||||
-- ngx.say("failed to add data")
|
|
||||||
-- return
|
|
||||||
-- end
|
|
||||||
|
|
||||||
-- ok = md5:update("lo")
|
|
||||||
-- if not ok then
|
|
||||||
-- ngx.say("failed to add data")
|
|
||||||
-- return
|
|
||||||
-- end
|
|
||||||
|
|
||||||
-- local digest = md5:final()
|
|
||||||
|
|
||||||
-- local str = require "resty.string"
|
|
||||||
-- ngx.say("md5: ", str.to_hex(digest))
|
|
||||||
-- -- yield "md5: 5d41402abc4b2a76b9719d911017c592"
|
|
||||||
@ -1,205 +0,0 @@
|
|||||||
---------------------------------------------------------------------------------------
|
|
||||||
-- Copyright 2012 Rackspace (original), 2013 Thijs Schreijer (modifications)
|
|
||||||
--
|
|
||||||
-- Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
-- you may not use this file except in compliance with the License.
|
|
||||||
-- You may obtain a copy of the License at
|
|
||||||
--
|
|
||||||
-- http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
--
|
|
||||||
-- Unless required by applicable law or agreed to in writing, software
|
|
||||||
-- distributed under the License is distributed on an "AS-IS" BASIS,
|
|
||||||
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
-- See the License for the specific language governing permissions and
|
|
||||||
-- limitations under the License.
|
|
||||||
--
|
|
||||||
-- see http://www.ietf.org/rfc/rfc4122.txt
|
|
||||||
--
|
|
||||||
-- Note that this is not a true version 4 (random) UUID. Since `os.time()` precision is only 1 second, it would be hard
|
|
||||||
-- to guarantee spacial uniqueness when two hosts generate a uuid after being seeded during the same second. This
|
|
||||||
-- is solved by using the node field from a version 1 UUID. It represents the mac address.
|
|
||||||
--
|
|
||||||
-- 28-apr-2013 modified by Thijs Schreijer from the original [Rackspace code](https://github.com/kans/zirgo/blob/807250b1af6725bad4776c931c89a784c1e34db2/util/uuid.lua) as a generic Lua module.
|
|
||||||
-- Regarding the above mention on `os.time()`; the modifications use the `socket.gettime()` function from LuaSocket
|
|
||||||
-- if available and hence reduce that problem (provided LuaSocket has been loaded before uuid).
|
|
||||||
--
|
|
||||||
-- **6-nov-2015 Please take note of this issue**; [https://github.com/Mashape/kong/issues/478](https://github.com/Mashape/kong/issues/478)
|
|
||||||
-- It demonstrates the problem of using time as a random seed. Specifically when used from multiple processes.
|
|
||||||
-- So make sure to seed only once, application wide. And to not have multiple processes do that
|
|
||||||
-- simultaneously (like nginx does for example).
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
local math = require('math')
|
|
||||||
local os = require('os')
|
|
||||||
local string = require('string')
|
|
||||||
|
|
||||||
local bitsize = 32 -- bitsize assumed for Lua VM. See randomseed function below.
|
|
||||||
local lua_version = tonumber(_VERSION:match("%d%.*%d*")) -- grab Lua version used
|
|
||||||
|
|
||||||
local MATRIX_AND = {{0,0},{0,1} }
|
|
||||||
local MATRIX_OR = {{0,1},{1,1}}
|
|
||||||
local HEXES = '0123456789abcdef'
|
|
||||||
|
|
||||||
local math_floor = math.floor
|
|
||||||
local math_random = math.random
|
|
||||||
local math_abs = math.abs
|
|
||||||
local string_sub = string.sub
|
|
||||||
local to_number = tonumber
|
|
||||||
local assert = assert
|
|
||||||
local type = type
|
|
||||||
|
|
||||||
-- performs the bitwise operation specified by truth matrix on two numbers.
|
|
||||||
local function BITWISE(x, y, matrix)
|
|
||||||
local z = 0
|
|
||||||
local pow = 1
|
|
||||||
while x > 0 or y > 0 do
|
|
||||||
z = z + (matrix[x%2+1][y%2+1] * pow)
|
|
||||||
pow = pow * 2
|
|
||||||
x = math_floor(x/2)
|
|
||||||
y = math_floor(y/2)
|
|
||||||
end
|
|
||||||
return z
|
|
||||||
end
|
|
||||||
|
|
||||||
local function INT2HEX(x)
|
|
||||||
local s,base = '',16
|
|
||||||
local d
|
|
||||||
while x > 0 do
|
|
||||||
d = x % base + 1
|
|
||||||
x = math_floor(x/base)
|
|
||||||
s = string_sub(HEXES, d, d)..s
|
|
||||||
end
|
|
||||||
while #s < 2 do s = "0" .. s end
|
|
||||||
return s
|
|
||||||
end
|
|
||||||
|
|
||||||
----------------------------------------------------------------------------
|
|
||||||
-- Creates a new uuid. Either provide a unique hex string, or make sure the
|
|
||||||
-- random seed is properly set. The module table itself is a shortcut to this
|
|
||||||
-- function, so `my_uuid = uuid.new()` equals `my_uuid = uuid()`.
|
|
||||||
--
|
|
||||||
-- For proper use there are 3 options;
|
|
||||||
--
|
|
||||||
-- 1. first require `luasocket`, then call `uuid.seed()`, and request a uuid using no
|
|
||||||
-- parameter, eg. `my_uuid = uuid()`
|
|
||||||
-- 2. use `uuid` without `luasocket`, set a random seed using `uuid.randomseed(some_good_seed)`,
|
|
||||||
-- and request a uuid using no parameter, eg. `my_uuid = uuid()`
|
|
||||||
-- 3. use `uuid` without `luasocket`, and request a uuid using an unique hex string,
|
|
||||||
-- eg. `my_uuid = uuid(my_networkcard_macaddress)`
|
|
||||||
--
|
|
||||||
-- @return a properly formatted uuid string
|
|
||||||
-- @param hwaddr (optional) string containing a unique hex value (e.g.: `00:0c:29:69:41:c6`), to be used to compensate for the lesser `math_random()` function. Use a mac address for solid results. If omitted, a fully randomized uuid will be generated, but then you must ensure that the random seed is set properly!
|
|
||||||
-- @usage
|
|
||||||
-- local uuid = require("uuid")
|
|
||||||
-- print("here's a new uuid: ",uuid())
|
|
||||||
function M.new(hwaddr)
|
|
||||||
-- bytes are treated as 8bit unsigned bytes.
|
|
||||||
local bytes = {
|
|
||||||
math_random(0, 255),
|
|
||||||
math_random(0, 255),
|
|
||||||
math_random(0, 255),
|
|
||||||
math_random(0, 255),
|
|
||||||
math_random(0, 255),
|
|
||||||
math_random(0, 255),
|
|
||||||
math_random(0, 255),
|
|
||||||
math_random(0, 255),
|
|
||||||
math_random(0, 255),
|
|
||||||
math_random(0, 255),
|
|
||||||
math_random(0, 255),
|
|
||||||
math_random(0, 255),
|
|
||||||
math_random(0, 255),
|
|
||||||
math_random(0, 255),
|
|
||||||
math_random(0, 255),
|
|
||||||
math_random(0, 255)
|
|
||||||
}
|
|
||||||
|
|
||||||
if hwaddr then
|
|
||||||
assert(type(hwaddr)=="string", "Expected hex string, got "..type(hwaddr))
|
|
||||||
-- Cleanup provided string, assume mac address, so start from back and cleanup until we've got 12 characters
|
|
||||||
local i,str = #hwaddr, hwaddr
|
|
||||||
hwaddr = ""
|
|
||||||
while i>0 and #hwaddr<12 do
|
|
||||||
local c = str:sub(i,i):lower()
|
|
||||||
if HEXES:find(c, 1, true) then
|
|
||||||
-- valid HEX character, so append it
|
|
||||||
hwaddr = c..hwaddr
|
|
||||||
end
|
|
||||||
i = i - 1
|
|
||||||
end
|
|
||||||
assert(#hwaddr == 12, "Provided string did not contain at least 12 hex characters, retrieved '"..hwaddr.."' from '"..str.."'")
|
|
||||||
|
|
||||||
-- no split() in lua. :(
|
|
||||||
bytes[11] = to_number(hwaddr:sub(1, 2), 16)
|
|
||||||
bytes[12] = to_number(hwaddr:sub(3, 4), 16)
|
|
||||||
bytes[13] = to_number(hwaddr:sub(5, 6), 16)
|
|
||||||
bytes[14] = to_number(hwaddr:sub(7, 8), 16)
|
|
||||||
bytes[15] = to_number(hwaddr:sub(9, 10), 16)
|
|
||||||
bytes[16] = to_number(hwaddr:sub(11, 12), 16)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- set the version
|
|
||||||
bytes[7] = BITWISE(bytes[7], 0x0f, MATRIX_AND)
|
|
||||||
bytes[7] = BITWISE(bytes[7], 0x40, MATRIX_OR)
|
|
||||||
-- set the variant
|
|
||||||
bytes[9] = BITWISE(bytes[7], 0x3f, MATRIX_AND)
|
|
||||||
bytes[9] = BITWISE(bytes[7], 0x80, MATRIX_OR)
|
|
||||||
return INT2HEX(bytes[1])..INT2HEX(bytes[2])..INT2HEX(bytes[3])..INT2HEX(bytes[4]).."-"..
|
|
||||||
INT2HEX(bytes[5])..INT2HEX(bytes[6]).."-"..
|
|
||||||
INT2HEX(bytes[7])..INT2HEX(bytes[8]).."-"..
|
|
||||||
INT2HEX(bytes[9])..INT2HEX(bytes[10]).."-"..
|
|
||||||
INT2HEX(bytes[11])..INT2HEX(bytes[12])..INT2HEX(bytes[13])..INT2HEX(bytes[14])..INT2HEX(bytes[15])..INT2HEX(bytes[16])
|
|
||||||
end
|
|
||||||
|
|
||||||
----------------------------------------------------------------------------
|
|
||||||
-- Improved randomseed function.
|
|
||||||
-- Lua 5.1 and 5.2 both truncate the seed given if it exceeds the integer
|
|
||||||
-- range. If this happens, the seed will be 0 or 1 and all randomness will
|
|
||||||
-- be gone (each application run will generate the same sequence of random
|
|
||||||
-- numbers in that case). This improved version drops the most significant
|
|
||||||
-- bits in those cases to get the seed within the proper range again.
|
|
||||||
-- @param seed the random seed to set (integer from 0 - 2^32, negative values will be made positive)
|
|
||||||
-- @return the (potentially modified) seed used
|
|
||||||
-- @usage
|
|
||||||
-- local socket = require("socket") -- gettime() has higher precision than os.time()
|
|
||||||
-- local uuid = require("uuid")
|
|
||||||
-- -- see also example at uuid.seed()
|
|
||||||
-- uuid.randomseed(socket.gettime()*10000)
|
|
||||||
-- print("here's a new uuid: ",uuid())
|
|
||||||
function M.randomseed(seed)
|
|
||||||
seed = math_floor(math_abs(seed))
|
|
||||||
if seed >= (2^bitsize) then
|
|
||||||
-- integer overflow, so reduce to prevent a bad seed
|
|
||||||
seed = seed - math_floor(seed / 2^bitsize) * (2^bitsize)
|
|
||||||
end
|
|
||||||
if lua_version < 5.2 then
|
|
||||||
-- 5.1 uses (incorrect) signed int
|
|
||||||
math.randomseed(seed - 2^(bitsize-1))
|
|
||||||
else
|
|
||||||
-- 5.2 uses (correct) unsigned int
|
|
||||||
math.randomseed(seed)
|
|
||||||
end
|
|
||||||
return seed
|
|
||||||
end
|
|
||||||
|
|
||||||
----------------------------------------------------------------------------
|
|
||||||
-- Seeds the random generator.
|
|
||||||
-- It does so in 2 possible ways;
|
|
||||||
--
|
|
||||||
-- 1. use `os.time()`: this only offers resolution to one second (used when
|
|
||||||
-- LuaSocket hasn't been loaded yet
|
|
||||||
-- 2. use luasocket `gettime()` function, but it only does so when LuaSocket
|
|
||||||
-- has been required already.
|
|
||||||
-- @usage
|
|
||||||
-- local socket = require("socket") -- gettime() has higher precision than os.time()
|
|
||||||
-- -- LuaSocket loaded, so below line does the same as the example from randomseed()
|
|
||||||
-- uuid.seed()
|
|
||||||
-- print("here's a new uuid: ",uuid())
|
|
||||||
function M.seed()
|
|
||||||
if package.loaded["socket"] and package.loaded["socket"].gettime then
|
|
||||||
return M.randomseed(package.loaded["socket"].gettime()*10000)
|
|
||||||
else
|
|
||||||
return M.randomseed(os.time())
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return setmetatable( M, { __call = function(self, hwaddr) return self.new(hwaddr) end} )
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
inspect = require("app.libs.inspect")
|
|
||||||
require("app.config.error_code")
|
|
||||||
require "app.libs.log_api"
|
|
||||||
|
|
||||||
local app = require("app.server")
|
|
||||||
app:run()
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
|
|
||||||
### 自定义插件目录(define your own middleware)
|
|
||||||
|
|
||||||
|
|
||||||
You are recommended to define your own middlewares and keep them in one place to manage.
|
|
||||||
|
|
||||||
建议用户将自定义插件存放在此目录下统一管理,然后在其他地方引用,插件的格式如下:
|
|
||||||
|
|
||||||
```
|
|
||||||
local middleware = function(params)
|
|
||||||
return function(req, res, next)
|
|
||||||
-- do something with req/res
|
|
||||||
next()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return middleware
|
|
||||||
```
|
|
||||||
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
local smatch = string.match
|
|
||||||
local sfind = string.find
|
|
||||||
|
|
||||||
local function is_login(req)
|
|
||||||
local user
|
|
||||||
if req.session then
|
|
||||||
user = req.session.get("user")
|
|
||||||
if user and user.username and user.userid then
|
|
||||||
return true, user
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return false, nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local function check_login(whitelist)
|
|
||||||
return function(req, res, next)
|
|
||||||
local requestPath = req.path
|
|
||||||
local in_white_list = false
|
|
||||||
if requestPath == "/" then
|
|
||||||
in_white_list = true
|
|
||||||
else
|
|
||||||
for i, v in ipairs(whitelist) do
|
|
||||||
local match, err = smatch(requestPath, v)
|
|
||||||
if match then
|
|
||||||
in_white_list = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local islogin, user= is_login(req)
|
|
||||||
|
|
||||||
if in_white_list then
|
|
||||||
res.locals.login = islogin
|
|
||||||
res.locals.username = user and user.username
|
|
||||||
res.locals.userid = user and user.userid
|
|
||||||
res.locals.create_time = user and user.create_time
|
|
||||||
next()
|
|
||||||
else
|
|
||||||
if islogin then
|
|
||||||
res.locals.login = true
|
|
||||||
res.locals.username = user.username
|
|
||||||
res.locals.userid = user.userid
|
|
||||||
res.locals.create_time = user.create_time
|
|
||||||
next()
|
|
||||||
else
|
|
||||||
if sfind(req.headers["Accept"], "application/json") then
|
|
||||||
res:json({
|
|
||||||
code = AUTH_ERROR.account_login,
|
|
||||||
message = system_error_msg(AUTH_ERROR.account_login),
|
|
||||||
})
|
|
||||||
-- else
|
|
||||||
-- res:redirect("/api/login")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return check_login
|
|
||||||
|
|
||||||
@ -1,139 +0,0 @@
|
|||||||
local upload = require("resty.upload")
|
|
||||||
local uuid = require("app.libs.uuid")
|
|
||||||
|
|
||||||
local sfind = string.find
|
|
||||||
local match = string.match
|
|
||||||
local ngx_var = ngx.var
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
local function getextension(filename)
|
|
||||||
return filename:match(".+%.(%w+)$")
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function _multipart_formdata(config)
|
|
||||||
|
|
||||||
local form, err = upload:new(config.chunk_size)
|
|
||||||
if not form then
|
|
||||||
ngx.log(ngx.ERR, "failed to new upload: ", err)
|
|
||||||
ngx.exit(500)
|
|
||||||
end
|
|
||||||
form:set_timeout(config.recieve_timeout)
|
|
||||||
|
|
||||||
|
|
||||||
local unique_name = uuid()
|
|
||||||
local success, msg = false, ""
|
|
||||||
local file, origin_filename, filename, path, extname, err
|
|
||||||
while true do
|
|
||||||
local typ, res, err = form:read()
|
|
||||||
|
|
||||||
if not typ then
|
|
||||||
success = false
|
|
||||||
msg = "failed to read"
|
|
||||||
ngx.log(ngx.ERR, "failed to read: ", err)
|
|
||||||
return success, msg
|
|
||||||
end
|
|
||||||
|
|
||||||
if typ == "header" then
|
|
||||||
if res[1] == "Content-Disposition" then
|
|
||||||
key = match(res[2], "name=\"(.-)\"")
|
|
||||||
origin_filename = match(res[2], "filename=\"(.-)\"")
|
|
||||||
elseif res[1] == "Content-Type" then
|
|
||||||
filetype = res[2]
|
|
||||||
end
|
|
||||||
|
|
||||||
if origin_filename and filetype then
|
|
||||||
if not extname then
|
|
||||||
extname = getextension(origin_filename)
|
|
||||||
end
|
|
||||||
|
|
||||||
if extname ~= "png" and extname ~= "jpg" and extname ~= "jpeg" and extname ~= "bmp" and extname ~= "gif" then
|
|
||||||
success = false
|
|
||||||
msg = "not allowed upload file type"
|
|
||||||
ngx.log(ngx.ERR, "not allowed upload file type:", origin_filename)
|
|
||||||
return success, msg
|
|
||||||
end
|
|
||||||
|
|
||||||
filename = unique_name .. "." .. extname
|
|
||||||
path = config.dir.. "/" .. filename
|
|
||||||
|
|
||||||
|
|
||||||
file, err = io.open(path, "w+")
|
|
||||||
|
|
||||||
if err then
|
|
||||||
success = false
|
|
||||||
msg = "open file error"
|
|
||||||
ngx.log(ngx.ERR, "open file error:", err)
|
|
||||||
return success, msg
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif typ == "body" then
|
|
||||||
if file then
|
|
||||||
file:write(res)
|
|
||||||
success = true
|
|
||||||
else
|
|
||||||
success = false
|
|
||||||
msg = "upload file error"
|
|
||||||
ngx.log(ngx.ERR, "upload file error, path:", path)
|
|
||||||
return success, msg
|
|
||||||
end
|
|
||||||
elseif typ == "part_end" then
|
|
||||||
file:close()
|
|
||||||
file = nil
|
|
||||||
elseif typ == "eof" then
|
|
||||||
break
|
|
||||||
else
|
|
||||||
-- do nothing
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return success, msg, origin_filename, extname, path, filename
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
local function uploader(config)
|
|
||||||
return function(req, res, next)
|
|
||||||
|
|
||||||
if ngx_var.request_method == "POST" then
|
|
||||||
local get_headers = ngx.req.get_headers()
|
|
||||||
local header = get_headers['Content-Type']
|
|
||||||
if header then
|
|
||||||
local is_multipart = sfind(header, "multipart")
|
|
||||||
if is_multipart and is_multipart>0 then
|
|
||||||
config = config or {}
|
|
||||||
config.dir = config.dir or "/tmp"
|
|
||||||
config.chunk_size = config.chunk_size or 4096
|
|
||||||
config.recieve_timeout = config.recieve_timeout or 20000 -- 20s
|
|
||||||
|
|
||||||
local success, msg, origin_filename, extname, path, filename = _multipart_formdata(config)
|
|
||||||
if success then
|
|
||||||
req.file = req.file or {}
|
|
||||||
req.file.success = true
|
|
||||||
req.file.origin_filename = origin_filename
|
|
||||||
req.file.extname = extname
|
|
||||||
req.file.path = path
|
|
||||||
req.file.filename = filename
|
|
||||||
else
|
|
||||||
req.file = req.file or {}
|
|
||||||
req.file.success = false
|
|
||||||
req.file.msg = msg
|
|
||||||
end
|
|
||||||
next()
|
|
||||||
|
|
||||||
else
|
|
||||||
next()
|
|
||||||
end
|
|
||||||
else
|
|
||||||
next()
|
|
||||||
end
|
|
||||||
else
|
|
||||||
next()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return uploader
|
|
||||||
|
|
||||||
@ -1,72 +0,0 @@
|
|||||||
local DB = require("app.libs.db")
|
|
||||||
local db = DB:new()
|
|
||||||
|
|
||||||
local user_model = {}
|
|
||||||
|
|
||||||
|
|
||||||
function user_model:new(username, password, avatar, rolelv)
|
|
||||||
return db:query("insert into user(username, password, avatar, role_lv) values(?,?,?,?)",
|
|
||||||
{username, password, avatar, rolelv})
|
|
||||||
end
|
|
||||||
|
|
||||||
function user_model:query(username, password)
|
|
||||||
local res, err = db:query("select * from user where username=? and password=?", {username, password})
|
|
||||||
return res, err
|
|
||||||
end
|
|
||||||
|
|
||||||
-- function user_model:query_by_id(id)
|
|
||||||
-- local result, err = db:query("select * from user where id=?", {tonumber(id)})
|
|
||||||
-- if not result or err or type(result) ~= "table" or #result ~=1 then
|
|
||||||
-- return nil, err
|
|
||||||
-- else
|
|
||||||
-- return result[1], err
|
|
||||||
-- end
|
|
||||||
-- end
|
|
||||||
|
|
||||||
-- return user, err
|
|
||||||
function user_model:query_by_username(username)
|
|
||||||
local res, err = db:query("select * from user where username=? limit 1", {username})
|
|
||||||
if not res or err or type(res) ~= "table" or #res ~=1 then
|
|
||||||
return nil, err or "error"
|
|
||||||
end
|
|
||||||
|
|
||||||
return res[1], err
|
|
||||||
end
|
|
||||||
|
|
||||||
function user_model:update_avatar(userid, avatar)
|
|
||||||
db:query("update user set avatar=? where id=?", {avatar, userid})
|
|
||||||
end
|
|
||||||
|
|
||||||
function user_model:update_pwd(userid, pwd)
|
|
||||||
local res, err = db:query("update user set password=? where id=?", {pwd, userid})
|
|
||||||
if not res or err then
|
|
||||||
return false
|
|
||||||
else
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
function user_model:update(userid, email, email_public, city, company, github, website, sign)
|
|
||||||
local res, err = db:query("update user set email=?, email_public=?, city=?, company=?, github=?, website=?, sign=? where id=?",
|
|
||||||
{ email, email_public, city, company, github, website, sign, userid})
|
|
||||||
|
|
||||||
if not res or err then
|
|
||||||
return false
|
|
||||||
else
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function user_model:get_total_count()
|
|
||||||
local res, err = db:query("select count(id) as c from user")
|
|
||||||
|
|
||||||
if err or not res or #res~=1 or not res[1].c then
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
return res[1].c
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
return user_model
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
-- 业务路由管理
|
|
||||||
local apiRouter = require("app.routes.api")
|
|
||||||
|
|
||||||
return function(app)
|
|
||||||
app:use("/api", apiRouter)
|
|
||||||
end
|
|
||||||
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
local lor = require("lor.index")
|
|
||||||
local apiRouter = lor:Router() -- 生成一个group router对象
|
|
||||||
|
|
||||||
|
|
||||||
local user_login = require("app.controller.user.login")
|
|
||||||
local user_signup = require("app.controller.user.signup")
|
|
||||||
local user_info = require("app.controller.user.user_info")
|
|
||||||
|
|
||||||
--注册
|
|
||||||
apiRouter:post("/signup", function(req, res, next)
|
|
||||||
local body = req.body
|
|
||||||
local username = body.username
|
|
||||||
local password = body.password
|
|
||||||
|
|
||||||
local msg = user_signup(username, password)
|
|
||||||
return res:json(msg)
|
|
||||||
end)
|
|
||||||
|
|
||||||
--登录
|
|
||||||
apiRouter:post("/login", function(req, res, next)
|
|
||||||
local body = req.body
|
|
||||||
local username = body.username
|
|
||||||
local password = body.password
|
|
||||||
|
|
||||||
local msg = user_login(req, username, password)
|
|
||||||
return res:json(msg)
|
|
||||||
end)
|
|
||||||
|
|
||||||
apiRouter:get("/user", function(req, res, next)
|
|
||||||
local msg = user_info(req, res)
|
|
||||||
return res:json(msg)
|
|
||||||
end)
|
|
||||||
|
|
||||||
apiRouter:get("/list", function(req, res, next)
|
|
||||||
local msg = user_info(req, res)
|
|
||||||
return res:json(msg)
|
|
||||||
end)
|
|
||||||
|
|
||||||
|
|
||||||
apiRouter:post("/logout", function(req, res, next)
|
|
||||||
res.locals.login = false
|
|
||||||
res.locals.username = ""
|
|
||||||
res.locals.userid = 0
|
|
||||||
res.locals.create_time = ""
|
|
||||||
req.session.destroy()
|
|
||||||
end)
|
|
||||||
|
|
||||||
return apiRouter
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
|
|
||||||
### 静态文件目录(static files directory)
|
|
||||||
|
|
||||||
nginx对应配置为
|
|
||||||
|
|
||||||
```
|
|
||||||
location /static {
|
|
||||||
alias app/static;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
|
|
||||||
### nginx configuration directory
|
|
||||||
##
|
|
||||||
/usr/local/openresty/nginx/sbin/nginx -c /usr/local/openresty/nginx/conf/nginx.conf
|
|
||||||
|
|
||||||
@ -1,88 +0,0 @@
|
|||||||
types {
|
|
||||||
text/html html htm shtml;
|
|
||||||
text/css css;
|
|
||||||
text/xml xml;
|
|
||||||
image/gif gif;
|
|
||||||
image/jpeg jpeg jpg;
|
|
||||||
application/javascript js;
|
|
||||||
application/atom+xml atom;
|
|
||||||
application/rss+xml rss;
|
|
||||||
|
|
||||||
text/mathml mml;
|
|
||||||
text/plain txt;
|
|
||||||
text/vnd.sun.j2me.app-descriptor jad;
|
|
||||||
text/vnd.wap.wml wml;
|
|
||||||
text/x-component htc;
|
|
||||||
|
|
||||||
image/png png;
|
|
||||||
image/tiff tif tiff;
|
|
||||||
image/vnd.wap.wbmp wbmp;
|
|
||||||
image/x-icon ico;
|
|
||||||
image/x-jng jng;
|
|
||||||
image/x-ms-bmp bmp;
|
|
||||||
image/svg+xml svg svgz;
|
|
||||||
image/webp webp;
|
|
||||||
|
|
||||||
application/font-woff woff;
|
|
||||||
application/java-archive jar war ear;
|
|
||||||
application/json json;
|
|
||||||
application/mac-binhex40 hqx;
|
|
||||||
application/msword doc;
|
|
||||||
application/pdf pdf;
|
|
||||||
application/postscript ps eps ai;
|
|
||||||
application/rtf rtf;
|
|
||||||
application/vnd.apple.mpegurl m3u8;
|
|
||||||
application/vnd.ms-excel xls;
|
|
||||||
application/vnd.ms-fontobject eot;
|
|
||||||
application/vnd.ms-powerpoint ppt;
|
|
||||||
application/vnd.wap.wmlc wmlc;
|
|
||||||
application/vnd.google-earth.kml+xml kml;
|
|
||||||
application/vnd.google-earth.kmz kmz;
|
|
||||||
application/x-7z-compressed 7z;
|
|
||||||
application/x-cocoa cco;
|
|
||||||
application/x-java-archive-diff jardiff;
|
|
||||||
application/x-java-jnlp-file jnlp;
|
|
||||||
application/x-makeself run;
|
|
||||||
application/x-perl pl pm;
|
|
||||||
application/x-pilot prc pdb;
|
|
||||||
application/x-rar-compressed rar;
|
|
||||||
application/x-redhat-package-manager rpm;
|
|
||||||
application/x-sea sea;
|
|
||||||
application/x-shockwave-flash swf;
|
|
||||||
application/x-stuffit sit;
|
|
||||||
application/x-tcl tcl tk;
|
|
||||||
application/x-x509-ca-cert der pem crt;
|
|
||||||
application/x-xpinstall xpi;
|
|
||||||
application/xhtml+xml xhtml;
|
|
||||||
application/xspf+xml xspf;
|
|
||||||
application/zip zip;
|
|
||||||
|
|
||||||
application/octet-stream bin exe dll;
|
|
||||||
application/octet-stream deb;
|
|
||||||
application/octet-stream dmg;
|
|
||||||
application/octet-stream iso img;
|
|
||||||
application/octet-stream msi msp msm;
|
|
||||||
|
|
||||||
application/vnd.openxmlformats-officedocument.wordprocessingml.document docx;
|
|
||||||
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx;
|
|
||||||
application/vnd.openxmlformats-officedocument.presentationml.presentation pptx;
|
|
||||||
|
|
||||||
audio/midi mid midi kar;
|
|
||||||
audio/mpeg mp3;
|
|
||||||
audio/ogg ogg;
|
|
||||||
audio/x-m4a m4a;
|
|
||||||
audio/x-realaudio ra;
|
|
||||||
|
|
||||||
video/3gpp 3gpp 3gp;
|
|
||||||
video/mp2t ts;
|
|
||||||
video/mp4 mp4;
|
|
||||||
video/mpeg mpeg mpg;
|
|
||||||
video/quicktime mov;
|
|
||||||
video/webm webm;
|
|
||||||
video/x-flv flv;
|
|
||||||
video/x-m4v m4v;
|
|
||||||
video/x-mng mng;
|
|
||||||
video/x-ms-asf asx asf;
|
|
||||||
video/x-ms-wmv wmv;
|
|
||||||
video/x-msvideo avi;
|
|
||||||
}
|
|
||||||
@ -1,98 +0,0 @@
|
|||||||
|
|
||||||
pid tmp/nginx.pid;
|
|
||||||
worker_processes 4;
|
|
||||||
#nginx权限问题 https://github.com/smallnewer/bugs/issues/64 ps aux|grep nginx|grep -v grep
|
|
||||||
user root;
|
|
||||||
|
|
||||||
events {
|
|
||||||
worker_connections 4096;
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
include ./mime.types;
|
|
||||||
|
|
||||||
client_max_body_size 10m; #允许客户端请求的最大单文件字节数
|
|
||||||
client_body_buffer_size 10m; #缓冲区代理缓冲用户端请求的最大字节数
|
|
||||||
|
|
||||||
sendfile on;
|
|
||||||
keepalive_timeout 65;
|
|
||||||
charset utf8;
|
|
||||||
|
|
||||||
lua_package_path "./app/?.lua;./app/libs/?.lua;./?.lua;./lor/?.lua;./lor/lib/?.lua;;;;";
|
|
||||||
# lua_package_path "./app/?.lua;./app/library/?.lua;./app/?/init.lua;./?.lua;/usr/local/lor/?.lua;/usr/local/lor/?/init.lua;;";
|
|
||||||
lua_package_cpath "./app/librarys/?.so;/usr/local/lor/?.so;;";
|
|
||||||
lua_code_cache on; # set on @production
|
|
||||||
#LUA_SHARED_DICT
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 9517;
|
|
||||||
#server_name localhost;
|
|
||||||
|
|
||||||
# Access log with buffer, or disable it completetely if unneeded
|
|
||||||
access_log logs/dev-www-access.log combined buffer=16k;
|
|
||||||
# Error log
|
|
||||||
error_log logs/dev-www-error.log;
|
|
||||||
|
|
||||||
location /admin {
|
|
||||||
root ./www/;
|
|
||||||
|
|
||||||
# try_files $uri $uri/ ;
|
|
||||||
index index.html index.htm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 9518;
|
|
||||||
|
|
||||||
# Access log with buffer, or disable it completetely if unneeded
|
|
||||||
access_log logs/dev-www-access.log combined buffer=16k;
|
|
||||||
# Error log
|
|
||||||
error_log logs/dev-www-error.log;
|
|
||||||
|
|
||||||
location /api/profiler {
|
|
||||||
proxy_pass http://127.0.0.1:9527;
|
|
||||||
proxy_read_timeout 300s;
|
|
||||||
proxy_send_timeout 300s;
|
|
||||||
|
|
||||||
proxy_set_header Host $host:$server_port;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection "upgrade";
|
|
||||||
}
|
|
||||||
|
|
||||||
location /api/ {
|
|
||||||
proxy_pass http://127.0.0.1:9527;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 7777;
|
|
||||||
#server_name localhost;
|
|
||||||
set $template_root '';
|
|
||||||
|
|
||||||
# # 项目本身的静态文件
|
|
||||||
# location /static/css {
|
|
||||||
# alias ./app/static/css;
|
|
||||||
# }
|
|
||||||
# location /static/img {
|
|
||||||
# alias ./app/static/img;
|
|
||||||
# }
|
|
||||||
# location /static/fonts {
|
|
||||||
# alias ./app/static/fonts;
|
|
||||||
# }
|
|
||||||
# location /static/js {
|
|
||||||
# alias ./app/static/js;
|
|
||||||
# }
|
|
||||||
|
|
||||||
|
|
||||||
# Access log with buffer, or disable it completetely if unneeded
|
|
||||||
access_log logs/dev-api-access.log combined buffer=16k;
|
|
||||||
# Error log
|
|
||||||
error_log logs/dev-api-error.log;
|
|
||||||
|
|
||||||
# lor runtime
|
|
||||||
location / {
|
|
||||||
content_by_lua_file ./app/main.lua;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
###test
|
|
||||||
local_install.sh
|
|
||||||
gen_rockspec.sh
|
|
||||||
test.sh
|
|
||||||
test.lua
|
|
||||||
snippets.lua
|
|
||||||
.idea
|
|
||||||
*.iml
|
|
||||||
*.rock
|
|
||||||
*.rockspec
|
|
||||||
lor-*
|
|
||||||
.DS_Store
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
std = "ngx_lua"
|
|
||||||
globals = {"LOR_FRAMEWORK_DEBUG"}
|
|
||||||
|
|
||||||
exclude_files = {"test/*", "resty", "bin"}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"linters": {
|
|
||||||
"luacheck": {
|
|
||||||
"@disable": false,
|
|
||||||
"args": [],
|
|
||||||
"std": "ngx_lua",
|
|
||||||
"excludes": ["test/*", "resty", "bin", "*_spec.lua", "*.test.lua"],
|
|
||||||
"globals": ["LOR_FRAMEWORK_DEBUG"],
|
|
||||||
"ignore": [
|
|
||||||
"channel"
|
|
||||||
],
|
|
||||||
"limit": null,
|
|
||||||
"only": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
language: c
|
|
||||||
|
|
||||||
sudo: false
|
|
||||||
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- LUAROCKS=2.2.2
|
|
||||||
matrix:
|
|
||||||
# Lua 5.1
|
|
||||||
- LUA=lua5.1
|
|
||||||
# LuaJIT latest stable version (2.0.4)
|
|
||||||
- LUA=luajit
|
|
||||||
# Openresty + LuaJIT + mysql
|
|
||||||
- LUA=luajit SERVER=openresty
|
|
||||||
# - LUA=luajit2.0 # current head of 2.0 branch
|
|
||||||
# - LUA=luajit2.1 # current head of 2.1 branch
|
|
||||||
# Not supported
|
|
||||||
# - LUA=lua5.2
|
|
||||||
# - LUA=lua5.3
|
|
||||||
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
- v0.3.0
|
|
||||||
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- source .travis/setenv_lua.sh
|
|
||||||
|
|
||||||
install:
|
|
||||||
- luarocks install https://luarocks.org/manifests/olivine-labs/busted-2.0.rc12-1.rockspec
|
|
||||||
- luarocks install lrexlib-pcre 2.7.2-1
|
|
||||||
- luarocks install luaposix
|
|
||||||
- luarocks install lua-cjson
|
|
||||||
#- luarocks make
|
|
||||||
|
|
||||||
script:
|
|
||||||
- busted spec/*
|
|
||||||
|
|
||||||
notifications:
|
|
||||||
email:
|
|
||||||
on_success: change
|
|
||||||
on_failure: always
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
if [ -z "${PLATFORM:-}" ]; then
|
|
||||||
PLATFORM=$TRAVIS_OS_NAME;
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$PLATFORM" == "osx" ]; then
|
|
||||||
PLATFORM="macosx";
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$PLATFORM" ]; then
|
|
||||||
if [ "$(uname)" == "Linux" ]; then
|
|
||||||
PLATFORM="linux";
|
|
||||||
else
|
|
||||||
PLATFORM="macosx";
|
|
||||||
fi;
|
|
||||||
fi
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
export PATH=${PATH}:$HOME/.lua:$HOME/.local/bin:${TRAVIS_BUILD_DIR}/install/luarocks/bin
|
|
||||||
export PATH=${PATH}:$HOME/.lua:$HOME/.local/bin:${TRAVIS_BUILD_DIR}/install/openresty/nginx/sbin
|
|
||||||
export PATH=${PATH}:$HOME/.lua:$HOME/.local/bin:${TRAVIS_BUILD_DIR}/install/openresty/bin
|
|
||||||
bash .travis/setup_lua.sh
|
|
||||||
if [ "$SERVER" == "openresty" ]; then
|
|
||||||
bash .travis/setup_servers.sh
|
|
||||||
fi
|
|
||||||
|
|
||||||
eval `$HOME/.lua/luarocks path`
|
|
||||||
@ -1,109 +0,0 @@
|
|||||||
#! /bin/bash
|
|
||||||
|
|
||||||
# A script for setting up environment for travis-ci testing.
|
|
||||||
# Sets up Lua and Luarocks.
|
|
||||||
# LUA must be "lua5.1", "lua5.2" or "luajit".
|
|
||||||
# luajit2.0 - master v2.0
|
|
||||||
# luajit2.1 - master v2.1
|
|
||||||
|
|
||||||
set -eufo pipefail
|
|
||||||
|
|
||||||
LUAJIT_BASE="LuaJIT-2.0.4"
|
|
||||||
|
|
||||||
source .travis/platform.sh
|
|
||||||
|
|
||||||
LUA_HOME_DIR=$TRAVIS_BUILD_DIR/install/lua
|
|
||||||
|
|
||||||
LR_HOME_DIR=$TRAVIS_BUILD_DIR/install/luarocks
|
|
||||||
|
|
||||||
mkdir $HOME/.lua
|
|
||||||
|
|
||||||
LUAJIT="no"
|
|
||||||
|
|
||||||
if [ "$PLATFORM" == "macosx" ]; then
|
|
||||||
if [ "$LUA" == "luajit" ]; then
|
|
||||||
LUAJIT="yes";
|
|
||||||
fi
|
|
||||||
if [ "$LUA" == "luajit2.0" ]; then
|
|
||||||
LUAJIT="yes";
|
|
||||||
fi
|
|
||||||
if [ "$LUA" == "luajit2.1" ]; then
|
|
||||||
LUAJIT="yes";
|
|
||||||
fi;
|
|
||||||
elif [ "$(expr substr $LUA 1 6)" == "luajit" ]; then
|
|
||||||
LUAJIT="yes";
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p "$LUA_HOME_DIR"
|
|
||||||
|
|
||||||
if [ "$LUAJIT" == "yes" ]; then
|
|
||||||
|
|
||||||
git clone https://github.com/LuaJIT/LuaJIT $LUAJIT_BASE;
|
|
||||||
|
|
||||||
cd $LUAJIT_BASE
|
|
||||||
|
|
||||||
if [ "$LUA" == "luajit2.1" ]; then
|
|
||||||
git checkout v2.1;
|
|
||||||
# force the INSTALL_TNAME to be luajit
|
|
||||||
perl -i -pe 's/INSTALL_TNAME=.+/INSTALL_TNAME= luajit/' Makefile
|
|
||||||
else
|
|
||||||
git checkout v2.0.4;
|
|
||||||
fi
|
|
||||||
|
|
||||||
make && make install PREFIX="$LUA_HOME_DIR"
|
|
||||||
|
|
||||||
ln -s $LUA_HOME_DIR/bin/luajit $HOME/.lua/luajit
|
|
||||||
ln -s $LUA_HOME_DIR/bin/luajit $HOME/.lua/lua;
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
if [ "$LUA" == "lua5.1" ]; then
|
|
||||||
curl http://www.lua.org/ftp/lua-5.1.5.tar.gz | tar xz
|
|
||||||
cd lua-5.1.5;
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Build Lua without backwards compatibility for testing
|
|
||||||
perl -i -pe 's/-DLUA_COMPAT_(ALL|5_2)//' src/Makefile
|
|
||||||
make $PLATFORM
|
|
||||||
make INSTALL_TOP="$LUA_HOME_DIR" install;
|
|
||||||
|
|
||||||
ln -s $LUA_HOME_DIR/bin/lua $HOME/.lua/lua
|
|
||||||
ln -s $LUA_HOME_DIR/bin/luac $HOME/.lua/luac;
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd $TRAVIS_BUILD_DIR
|
|
||||||
|
|
||||||
lua -v
|
|
||||||
|
|
||||||
LUAROCKS_BASE=luarocks-$LUAROCKS
|
|
||||||
|
|
||||||
curl --location http://luarocks.org/releases/$LUAROCKS_BASE.tar.gz | tar xz
|
|
||||||
|
|
||||||
cd $LUAROCKS_BASE
|
|
||||||
|
|
||||||
if [ "$LUA" == "luajit" ]; then
|
|
||||||
./configure --lua-suffix=jit --with-lua-include="$LUA_HOME_DIR/include/luajit-2.0" --prefix="$LR_HOME_DIR";
|
|
||||||
elif [ "$LUA" == "luajit2.0" ]; then
|
|
||||||
./configure --lua-suffix=jit --with-lua-include="$LUA_HOME_DIR/include/luajit-2.0" --prefix="$LR_HOME_DIR";
|
|
||||||
elif [ "$LUA" == "luajit2.1" ]; then
|
|
||||||
./configure --lua-suffix=jit --with-lua-include="$LUA_HOME_DIR/include/luajit-2.1" --prefix="$LR_HOME_DIR";
|
|
||||||
else
|
|
||||||
./configure --with-lua="$LUA_HOME_DIR" --prefix="$LR_HOME_DIR"
|
|
||||||
fi
|
|
||||||
|
|
||||||
make build && make install
|
|
||||||
|
|
||||||
ln -s $LR_HOME_DIR/bin/luarocks $HOME/.lua/luarocks
|
|
||||||
|
|
||||||
cd $TRAVIS_BUILD_DIR
|
|
||||||
|
|
||||||
luarocks --version
|
|
||||||
|
|
||||||
rm -rf $LUAROCKS_BASE
|
|
||||||
|
|
||||||
if [ "$LUAJIT" == "yes" ]; then
|
|
||||||
rm -rf $LUAJIT_BASE;
|
|
||||||
elif [ "$LUA" == "lua5.1" ]; then
|
|
||||||
rm -rf lua-5.1.5;
|
|
||||||
fi
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
#! /bin/bash
|
|
||||||
|
|
||||||
# A script for setting up environment for travis-ci testing.
|
|
||||||
# Sets up openresty.
|
|
||||||
OPENRESTY_VERSION="1.9.3.1"
|
|
||||||
OPENRESTY_DIR=$TRAVIS_BUILD_DIR/install/openresty
|
|
||||||
|
|
||||||
#if [ "$LUA" == "lua5.1" ]; then
|
|
||||||
# luarocks install LuaBitOp
|
|
||||||
#fi
|
|
||||||
|
|
||||||
wget https://openresty.org/download/ngx_openresty-$OPENRESTY_VERSION.tar.gz
|
|
||||||
tar xzvf ngx_openresty-$OPENRESTY_VERSION.tar.gz
|
|
||||||
cd ngx_openresty-$OPENRESTY_VERSION/
|
|
||||||
|
|
||||||
./configure --prefix="$OPENRESTY_DIR" --with-luajit
|
|
||||||
|
|
||||||
make
|
|
||||||
make install
|
|
||||||
|
|
||||||
ln -s $OPENRESTY_DIR/bin/resty $HOME/.lua/resty
|
|
||||||
ln -s $OPENRESTY_DIR/nginx/sbin/nginx $HOME/.lua/nginx
|
|
||||||
|
|
||||||
export PATH=${PATH}:$HOME/.lua:$HOME/.local/bin:${TRAVIS_BUILD_DIR}/install/openresty/nginx/sbin
|
|
||||||
export PATH=${PATH}:$HOME/.lua:$HOME/.local/bin:${TRAVIS_BUILD_DIR}/install/openresty/bin
|
|
||||||
|
|
||||||
nginx -v
|
|
||||||
resty -V
|
|
||||||
|
|
||||||
cd ../
|
|
||||||
rm -rf ngx_openresty-$OPENRESTY_VERSION
|
|
||||||
cd $TRAVIS_BUILD_DIR
|
|
||||||
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2016 - 2017 sumory.wu
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
TO_INSTALL = lib/* resty spec bin
|
|
||||||
LOR_HOME ?= /usr/local
|
|
||||||
LORD_BIN ?= /usr/local/bin
|
|
||||||
|
|
||||||
.PHONY: test install
|
|
||||||
|
|
||||||
test:
|
|
||||||
busted spec/*
|
|
||||||
|
|
||||||
install_lor:
|
|
||||||
@mkdir -p ${LOR_HOME}/lor
|
|
||||||
@mkdir -p ${LOR_HOME}
|
|
||||||
@rm -rf ${LOR_HOME}/lor/*
|
|
||||||
|
|
||||||
@echo "install lor runtime files to "${LOR_HOME}/lor
|
|
||||||
|
|
||||||
@for item in $(TO_INSTALL) ; do \
|
|
||||||
cp -a $$item ${LOR_HOME}/lor/; \
|
|
||||||
done;
|
|
||||||
|
|
||||||
@echo "lor runtime files installed."
|
|
||||||
|
|
||||||
|
|
||||||
install_lord: install_lor
|
|
||||||
@mkdir -p ${LORD_BIN}
|
|
||||||
@echo "install lord cli to "${LORD_BIN}"/"
|
|
||||||
|
|
||||||
@echo "#!/usr/bin/env resty" > tmp_lor_bin
|
|
||||||
@echo "package.path=\""${LOR_HOME}/lor"/?.lua;;\"" >> tmp_lor_bin
|
|
||||||
@echo "if arg[1] and arg[1] == \"path\" then" >> tmp_lor_bin
|
|
||||||
@echo " print(\"${LOR_HOME}/lor\")" >> tmp_lor_bin
|
|
||||||
@echo " return" >> tmp_lor_bin
|
|
||||||
@echo "end" >> tmp_lor_bin
|
|
||||||
@echo "require('bin.lord')(arg)" >> tmp_lor_bin
|
|
||||||
|
|
||||||
@mv tmp_lor_bin ${LORD_BIN}/lord
|
|
||||||
@chmod +x ${LORD_BIN}/lord
|
|
||||||
|
|
||||||
@echo "lord cli installed."
|
|
||||||
|
|
||||||
install: install_lord
|
|
||||||
@echo "lor framework installed successfully."
|
|
||||||
|
|
||||||
version:
|
|
||||||
@lord -v
|
|
||||||
|
|
||||||
help:
|
|
||||||
@lord -h
|
|
||||||
|
|
||||||
@ -1,143 +0,0 @@
|
|||||||
# Lor
|
|
||||||
|
|
||||||
[](https://travis-ci.org/sumory/lor) [](https://github.com/sumory/lor/releases/latest) [](https://github.com/sumory/lor/blob/master/LICENSE)
|
|
||||||
|
|
||||||
<a href="./README_zh.md" style="font-size:13px">中文</a> <a href="./README.md" style="font-size:13px">English</a>
|
|
||||||
|
|
||||||
A fast and minimalist web framework based on [OpenResty](http://openresty.org).
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local lor = require("lor.index")
|
|
||||||
local app = lor()
|
|
||||||
|
|
||||||
app:get("/", function(req, res, next)
|
|
||||||
res:send("hello world!")
|
|
||||||
end)
|
|
||||||
|
|
||||||
app:run()
|
|
||||||
```
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
- [lor-example](https://github.com/lorlabs/lor-example)
|
|
||||||
- [openresty-china](https://github.com/sumory/openresty-china)
|
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
1) shell
|
|
||||||
|
|
||||||
```shell
|
|
||||||
git clone https://github.com/sumory/lor
|
|
||||||
cd lor
|
|
||||||
make install
|
|
||||||
```
|
|
||||||
|
|
||||||
`LOR_HOME` and `LORD_BIN` are supported by `Makefile`, so the following command could be used to customize installation:
|
|
||||||
|
|
||||||
```
|
|
||||||
make install LOR_HOME=/path/to/lor LORD_BIN=/path/to/lord
|
|
||||||
```
|
|
||||||
|
|
||||||
2) opm
|
|
||||||
|
|
||||||
`opm install` is supported from v0.2.2.
|
|
||||||
|
|
||||||
```
|
|
||||||
opm install sumory/lor
|
|
||||||
```
|
|
||||||
|
|
||||||
`lord` cli is not supported with this installation.
|
|
||||||
|
|
||||||
3) homebrew
|
|
||||||
|
|
||||||
you can use [homebrew-lor](https://github.com/syhily/homebrew-lor) on Mac OSX.
|
|
||||||
|
|
||||||
```
|
|
||||||
$ brew tap syhily/lor
|
|
||||||
$ brew install lor
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- Routing like [Sinatra](http://www.sinatrarb.com/) which is a famous Ruby framework
|
|
||||||
- Similar API with [Express](http://expressjs.com), good experience for Node.js or Javascript developers
|
|
||||||
- Middleware support
|
|
||||||
- Group router support
|
|
||||||
- Session/Cookie/Views supported and could be redefined with `Middleware`
|
|
||||||
- Easy to build HTTP APIs, web site, or single page applications
|
|
||||||
|
|
||||||
|
|
||||||
## Docs & Community
|
|
||||||
|
|
||||||
- [Website and Documentation](http://lor.sumory.com).
|
|
||||||
- [Github Organization](https://github.com/lorlabs) for Official Middleware & Modules.
|
|
||||||
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
A quick way to get started with lor is to utilize the executable cli tool `lord` to generate an scaffold application.
|
|
||||||
|
|
||||||
`lord` is installed with `lor` framework. it looks like:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ lord -h
|
|
||||||
lor ${version}, a Lua web framework based on OpenResty.
|
|
||||||
|
|
||||||
Usage: lord COMMAND [OPTIONS]
|
|
||||||
|
|
||||||
Commands:
|
|
||||||
new [name] Create a new application
|
|
||||||
start Starts the server
|
|
||||||
stop Stops the server
|
|
||||||
restart Restart the server
|
|
||||||
version Show version of lor
|
|
||||||
help Show help tips
|
|
||||||
```
|
|
||||||
|
|
||||||
Create app:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ lord new lor_demo
|
|
||||||
```
|
|
||||||
|
|
||||||
Start server:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ cd lor_demo && lord start
|
|
||||||
```
|
|
||||||
|
|
||||||
Visit [http://localhost:8888](http://localhost:8888).
|
|
||||||
|
|
||||||
|
|
||||||
## Tests
|
|
||||||
|
|
||||||
Install [busted](http://olivinelabs.com/busted/), then run test
|
|
||||||
|
|
||||||
```
|
|
||||||
busted spec/*
|
|
||||||
```
|
|
||||||
|
|
||||||
## Homebrew
|
|
||||||
|
|
||||||
[https://github.com/syhily/homebrew-lor](https://github.com/syhily/homebrew-lor) maintained by [@syhily](https://github.com/syhily)
|
|
||||||
|
|
||||||
## Contributors
|
|
||||||
|
|
||||||
- [@ms2008](https://github.com/ms2008)
|
|
||||||
- [@wanghaisheng](https://github.com/wanghaisheng)
|
|
||||||
- [@lihuibin](https://github.com/lihuibin)
|
|
||||||
- [@syhily](https://github.com/syhily)
|
|
||||||
- [@vinsonzou](https://github.com/vinsonzou)
|
|
||||||
- [@lhmwzy](https://github.com/lhmwzy)
|
|
||||||
- [@hanxi](https://github.com/hanxi)
|
|
||||||
- [@诗兄](https://github.com/269724033)
|
|
||||||
- [@hetz](https://github.com/hetz)
|
|
||||||
- [@XadillaX](https://github.com/XadillaX)
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
[MIT](./LICENSE)
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
package.path = './?.lua;' .. package.path
|
|
||||||
|
|
||||||
local generator = require("bin.scaffold.generator")
|
|
||||||
local lor = require("bin.scaffold.launcher")
|
|
||||||
local version = require("lor.version")
|
|
||||||
|
|
||||||
local usages = [[lor v]] .. version .. [[, a Lua web framework based on OpenResty.
|
|
||||||
|
|
||||||
Usage: lord COMMAND [OPTIONS]
|
|
||||||
|
|
||||||
Commands:
|
|
||||||
new [name] Create a new application
|
|
||||||
start Start running app server
|
|
||||||
stop Stop the server
|
|
||||||
restart Restart the server
|
|
||||||
version Show version of lor
|
|
||||||
help Show help tips
|
|
||||||
path Show install path
|
|
||||||
]]
|
|
||||||
|
|
||||||
local function exec(args)
|
|
||||||
local arg = table.remove(args, 1)
|
|
||||||
|
|
||||||
-- parse commands and options
|
|
||||||
if arg == 'new' and args[1] then
|
|
||||||
generator.new(args[1]) -- generate example code
|
|
||||||
elseif arg == 'start' then
|
|
||||||
lor.start() -- start application
|
|
||||||
elseif arg == 'stop' then
|
|
||||||
lor.stop() -- stop application
|
|
||||||
elseif arg == 'restart' then
|
|
||||||
lor.stop()
|
|
||||||
lor.start()
|
|
||||||
elseif arg == 'reload' then
|
|
||||||
lor.reload()
|
|
||||||
elseif arg == 'help' or arg == '-h' then
|
|
||||||
print(usages)
|
|
||||||
elseif arg == 'version' or arg == '-v' then
|
|
||||||
print(version) -- show lor framework version
|
|
||||||
elseif arg == nil then
|
|
||||||
print(usages)
|
|
||||||
else
|
|
||||||
print("[lord] unsupported commands or options, `lord -h` to check usages.")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return exec
|
|
||||||
@ -1,101 +0,0 @@
|
|||||||
local sgmatch = string.gmatch
|
|
||||||
local ogetenv = os.getenv
|
|
||||||
|
|
||||||
local ngx_handle = require 'bin.scaffold.nginx.handle'
|
|
||||||
local ngx_config = require 'bin.scaffold.nginx.config'
|
|
||||||
local ngx_conf_template = require 'bin.scaffold.nginx.conf_template'
|
|
||||||
|
|
||||||
|
|
||||||
local Lor = {}
|
|
||||||
|
|
||||||
function Lor.nginx_conf_content()
|
|
||||||
-- read nginx.conf file
|
|
||||||
local nginx_conf_template = ngx_conf_template.get_ngx_conf_template()
|
|
||||||
|
|
||||||
-- append notice
|
|
||||||
nginx_conf_template = [[
|
|
||||||
#generated by `lor framework`
|
|
||||||
]] .. nginx_conf_template
|
|
||||||
|
|
||||||
local match = {}
|
|
||||||
local tmp = 1
|
|
||||||
for v in sgmatch(nginx_conf_template , '{{(.-)}}') do
|
|
||||||
match[tmp] = v
|
|
||||||
tmp = tmp + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, directive in ipairs(match) do
|
|
||||||
if ngx_config[directive] ~= nil then
|
|
||||||
nginx_conf_template = string.gsub(nginx_conf_template, '{{' .. directive .. '}}', ngx_config[directive])
|
|
||||||
else
|
|
||||||
nginx_conf_template = string.gsub(nginx_conf_template, '{{' .. directive .. '}}', '#' .. directive)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return nginx_conf_template
|
|
||||||
end
|
|
||||||
|
|
||||||
function new_handler()
|
|
||||||
local necessary_dirs ={ -- runtime nginx conf/pid/logs dir
|
|
||||||
tmp = 'tmp',
|
|
||||||
logs = 'logs'
|
|
||||||
}
|
|
||||||
local env = ogetenv("LOR_ENV") or 'dev'
|
|
||||||
return ngx_handle.new(
|
|
||||||
necessary_dirs,
|
|
||||||
Lor.nginx_conf_content(),
|
|
||||||
"conf/nginx-" .. env .. ".conf"
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Lor.start()
|
|
||||||
local env = ogetenv("LOR_ENV") or 'dev'
|
|
||||||
|
|
||||||
local ok, handler = pcall(function() return new_handler() end)
|
|
||||||
if ok == false then
|
|
||||||
print("ERROR:Cannot initialize handler: " .. handler)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local result = handler:start(env)
|
|
||||||
if result == 0 or result == true or result == "true" then
|
|
||||||
if env ~= 'test' then
|
|
||||||
print("app in " .. env .. " was succesfully started on port " .. ngx_config.PORT)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
print("ERROR: Could not start app on port " .. ngx_config.PORT)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Lor.stop()
|
|
||||||
local env = ogetenv("LOR_ENV") or 'dev'
|
|
||||||
|
|
||||||
local handler = new_handler()
|
|
||||||
local result = handler:stop(env)
|
|
||||||
|
|
||||||
if env ~= 'test' then
|
|
||||||
if result == 0 or result == true or result == "true" then
|
|
||||||
print("app in " .. env .. " was succesfully stopped.")
|
|
||||||
else
|
|
||||||
print("ERROR: Could not stop app (are you sure it is running?).")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Lor.reload()
|
|
||||||
local env = ogetenv("LOR_ENV") or 'dev'
|
|
||||||
|
|
||||||
local handler = new_handler()
|
|
||||||
local result = handler:reload(env)
|
|
||||||
|
|
||||||
if env ~= 'test' then
|
|
||||||
if result == 0 or result == true or result == "true" then
|
|
||||||
print("app in " .. env .. " was succesfully reloaded.")
|
|
||||||
else
|
|
||||||
print("ERROR: Could not reloaded app.")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return Lor
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
local pairs = pairs
|
|
||||||
local ogetenv = os.getenv
|
|
||||||
local utils = require 'bin.scaffold.utils'
|
|
||||||
local app_run_env = ogetenv("LOR_ENV") or 'dev'
|
|
||||||
|
|
||||||
local lor_ngx_conf = {}
|
|
||||||
lor_ngx_conf.common = { -- directives
|
|
||||||
LOR_ENV = app_run_env,
|
|
||||||
-- INIT_BY_LUA_FILE = './app/nginx/init.lua',
|
|
||||||
-- LUA_PACKAGE_PATH = '',
|
|
||||||
-- LUA_PACKAGE_CPATH = '',
|
|
||||||
CONTENT_BY_LUA_FILE = './app/main.lua',
|
|
||||||
STATIC_FILE_DIRECTORY = './app/static'
|
|
||||||
}
|
|
||||||
|
|
||||||
lor_ngx_conf.env = {}
|
|
||||||
lor_ngx_conf.env.dev = {
|
|
||||||
LUA_CODE_CACHE = false,
|
|
||||||
PORT = 8888
|
|
||||||
}
|
|
||||||
|
|
||||||
lor_ngx_conf.env.test = {
|
|
||||||
LUA_CODE_CACHE = true,
|
|
||||||
PORT = 9999
|
|
||||||
}
|
|
||||||
|
|
||||||
lor_ngx_conf.env.prod = {
|
|
||||||
LUA_CODE_CACHE = true,
|
|
||||||
PORT = 80
|
|
||||||
}
|
|
||||||
|
|
||||||
local function getNgxConf(conf_arr)
|
|
||||||
if conf_arr['common'] ~= nil then
|
|
||||||
local common_conf = conf_arr['common']
|
|
||||||
local env_conf = conf_arr['env'][app_run_env]
|
|
||||||
for directive, info in pairs(common_conf) do
|
|
||||||
env_conf[directive] = info
|
|
||||||
end
|
|
||||||
return env_conf
|
|
||||||
elseif conf_arr['env'] ~= nil then
|
|
||||||
return conf_arr['env'][app_run_env]
|
|
||||||
end
|
|
||||||
return {}
|
|
||||||
end
|
|
||||||
|
|
||||||
local function buildConf()
|
|
||||||
local sys_ngx_conf = getNgxConf(lor_ngx_conf)
|
|
||||||
return sys_ngx_conf
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local ngx_directive_handle = require('bin.scaffold.nginx.directive'):new(app_run_env)
|
|
||||||
local ngx_directives = ngx_directive_handle:directiveSets()
|
|
||||||
local ngx_run_conf = buildConf()
|
|
||||||
|
|
||||||
local LorNgxConf = {}
|
|
||||||
for directive, func in pairs(ngx_directives) do
|
|
||||||
if type(func) == 'function' then
|
|
||||||
local func_rs = func(ngx_directive_handle, ngx_run_conf[directive])
|
|
||||||
if func_rs ~= false then
|
|
||||||
LorNgxConf[directive] = func_rs
|
|
||||||
end
|
|
||||||
else
|
|
||||||
LorNgxConf[directive] = ngx_run_conf[directive]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return LorNgxConf
|
|
||||||
|
|
||||||
@ -1,205 +0,0 @@
|
|||||||
-- most code is from https://github.com/idevz/vanilla/blob/master/vanilla/sys/nginx/directive.lua
|
|
||||||
|
|
||||||
package.path = './app/?.lua;' .. package.path
|
|
||||||
package.cpath = './app/library/?.so;' .. package.cpath
|
|
||||||
|
|
||||||
|
|
||||||
local Directive = {}
|
|
||||||
|
|
||||||
function Directive:new(env)
|
|
||||||
local run_env = 'prod'
|
|
||||||
if env ~= nil then run_env = env end
|
|
||||||
local instance = {
|
|
||||||
run_env = run_env,
|
|
||||||
directiveSets = self.directiveSets
|
|
||||||
}
|
|
||||||
setmetatable(instance, Directive)
|
|
||||||
return instance
|
|
||||||
end
|
|
||||||
|
|
||||||
function Directive:luaPackagePath(lua_path)
|
|
||||||
local path = package.path
|
|
||||||
if lua_path ~= nil then path = lua_path .. path end
|
|
||||||
local res = [[lua_package_path "]] .. path .. [[;;";]]
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
function Directive:luaPackageCpath(lua_cpath)
|
|
||||||
local path = package.cpath
|
|
||||||
if lua_cpath ~= nil then path = lua_cpath .. path end
|
|
||||||
local res = [[lua_package_cpath "]] .. path .. [[;;";]]
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
function Directive:codeCache(bool_var)
|
|
||||||
if bool_var == true then bool_var = 'on' else bool_var = 'off' end
|
|
||||||
local res = [[lua_code_cache ]] .. bool_var.. [[;]]
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
function Directive:luaSharedDict( lua_lib )
|
|
||||||
local ok, sh_dict_conf_or_error = pcall(function() return require(lua_lib) end)
|
|
||||||
if ok == false then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
local res = ''
|
|
||||||
if sh_dict_conf_or_error ~= nil then
|
|
||||||
for name,size in pairs(sh_dict_conf_or_error) do
|
|
||||||
res = res .. [[lua_shared_dict ]] .. name .. ' ' .. size .. ';'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
function Directive:initByLua(lua_lib)
|
|
||||||
if lua_lib == nil then return '' end
|
|
||||||
local res = [[init_by_lua require(']] .. lua_lib .. [['):run();]]
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
function Directive:initByLuaFile(lua_file)
|
|
||||||
if lua_file == nil then return '' end
|
|
||||||
local res = [[init_by_lua_file ]] .. lua_file .. [[;]]
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
function Directive:initWorkerByLua(lua_lib)
|
|
||||||
if lua_lib == nil then return '' end
|
|
||||||
local res = [[init_worker_by_lua require(']] .. lua_lib .. [['):run();]]
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
function Directive:initWorkerByLuaFile(lua_file)
|
|
||||||
if lua_file == nil then return '' end
|
|
||||||
local res = [[init_worker_by_lua_file ]] .. lua_file .. [[;]]
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
function Directive:setByLua(lua_lib)
|
|
||||||
if lua_lib == nil then return '' end
|
|
||||||
local res = [[set_by_lua require(']] .. lua_lib .. [[');]]
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
function Directive:setByLuaFile(lua_file)
|
|
||||||
if lua_file == nil then return '' end
|
|
||||||
local res = [[set_by_lua_file ]] .. lua_file .. [[;]]
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
function Directive:rewriteByLua(lua_lib)
|
|
||||||
if lua_lib == nil then return '' end
|
|
||||||
local res = [[rewrite_by_lua require(']] .. lua_lib .. [['):run();]]
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
function Directive:rewriteByLuaFile(lua_file)
|
|
||||||
if lua_file == nil then return '' end
|
|
||||||
local res = [[rewrite_by_lua_file ]] .. lua_file .. [[;]]
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
function Directive:accessByLua(lua_lib)
|
|
||||||
if lua_lib == nil then return '' end
|
|
||||||
local res = [[access_by_lua require(']] .. lua_lib .. [['):run();]]
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
function Directive:accessByLuaFile(lua_file)
|
|
||||||
if lua_file == nil then return '' end
|
|
||||||
local res = [[access_by_lua_file ]] .. lua_file .. [[;]]
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
function Directive:contentByLua(lua_lib)
|
|
||||||
if lua_lib == nil then return '' end
|
|
||||||
-- local res = [[content_by_lua require(']] .. lua_lib .. [['):run();]]
|
|
||||||
local res = [[location / {
|
|
||||||
content_by_lua require(']] .. lua_lib .. [['):run();
|
|
||||||
}]]
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
function Directive:contentByLuaFile(lua_file)
|
|
||||||
if lua_file == nil then return '' end
|
|
||||||
local res = [[location / {
|
|
||||||
content_by_lua_file ]] .. lua_file .. [[;
|
|
||||||
}]]
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
function Directive:headerFilterByLua(lua_lib)
|
|
||||||
if lua_lib == nil then return '' end
|
|
||||||
local res = [[header_filter_by_lua require(']] .. lua_lib .. [['):run();]]
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
function Directive:headerFilterByLuaFile(lua_file)
|
|
||||||
if lua_file == nil then return '' end
|
|
||||||
local res = [[header_filter_by_lua_file ]] .. lua_file .. [[;]]
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
function Directive:bodyFilterByLua(lua_lib)
|
|
||||||
if lua_lib == nil then return '' end
|
|
||||||
local res = [[body_filter_by_lua require(']] .. lua_lib .. [['):run();]]
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
function Directive:bodyFilterByLuaFile(lua_file)
|
|
||||||
if lua_file == nil then return '' end
|
|
||||||
local res = [[body_filter_by_lua_file ]] .. lua_file .. [[;]]
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
function Directive:logByLua(lua_lib)
|
|
||||||
if lua_lib == nil then return '' end
|
|
||||||
local res = [[log_by_lua require(']] .. lua_lib .. [['):run();]]
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
function Directive:logByLuaFile(lua_file)
|
|
||||||
if lua_file == nil then return '' end
|
|
||||||
local res = [[log_by_lua_file ]] .. lua_file .. [[;]]
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Directive:staticFileDirectory(static_file_directory)
|
|
||||||
if static_file_directory == nil then return '' end
|
|
||||||
return static_file_directory
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Directive:directiveSets()
|
|
||||||
return {
|
|
||||||
['LOR_ENV'] = self.run_env,
|
|
||||||
['PORT'] = 80,
|
|
||||||
['NGX_PATH'] = '',
|
|
||||||
['LUA_PACKAGE_PATH'] = Directive.luaPackagePath,
|
|
||||||
['LUA_PACKAGE_CPATH'] = Directive.luaPackageCpath,
|
|
||||||
['LUA_CODE_CACHE'] = Directive.codeCache,
|
|
||||||
['LUA_SHARED_DICT'] = Directive.luaSharedDict,
|
|
||||||
['INIT_BY_LUA'] = Directive.initByLua,
|
|
||||||
['INIT_BY_LUA_FILE'] = Directive.initByLuaFile,
|
|
||||||
['INIT_WORKER_BY_LUA'] = Directive.initWorkerByLua,
|
|
||||||
['INIT_WORKER_BY_LUA_FILE'] = Directive.initWorkerByLuaFile,
|
|
||||||
['SET_BY_LUA'] = Directive.setByLua,
|
|
||||||
['SET_BY_LUA_FILE'] = Directive.setByLuaFile,
|
|
||||||
['REWRITE_BY_LUA'] = Directive.rewriteByLua,
|
|
||||||
['REWRITE_BY_LUA_FILE'] = Directive.rewriteByLuaFile,
|
|
||||||
['ACCESS_BY_LUA'] = Directive.accessByLua,
|
|
||||||
['ACCESS_BY_LUA_FILE'] = Directive.accessByLuaFile,
|
|
||||||
['CONTENT_BY_LUA'] = Directive.contentByLua,
|
|
||||||
['CONTENT_BY_LUA_FILE'] = Directive.contentByLuaFile,
|
|
||||||
['HEADER_FILTER_BY_LUA'] = Directive.headerFilterByLua,
|
|
||||||
['HEADER_FILTER_BY_LUA_FILE'] = Directive.headerFilterByLuaFile,
|
|
||||||
['BODY_FILTER_BY_LUA'] = Directive.bodyFilterByLua,
|
|
||||||
['BODY_FILTER_BY_LUA_FILE'] = Directive.bodyFilterByLuaFile,
|
|
||||||
['LOG_BY_LUA'] = Directive.logByLua,
|
|
||||||
['LOG_BY_LUA_FILE'] = Directive.logByLuaFile,
|
|
||||||
['STATIC_FILE_DIRECTORY'] = Directive.staticFileDirectory
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
return Directive
|
|
||||||
@ -1,76 +0,0 @@
|
|||||||
-- most code is from https://github.com/ostinelli/gin/blob/master/gin/cli/base_launcher.lua
|
|
||||||
local function create_dirs(necessary_dirs)
|
|
||||||
for _, dir in pairs(necessary_dirs) do
|
|
||||||
os.execute("mkdir -p " .. dir .. " > /dev/null")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function create_nginx_conf(nginx_conf_file_path, nginx_conf_content)
|
|
||||||
local fw = io.open(nginx_conf_file_path, "w")
|
|
||||||
fw:write(nginx_conf_content)
|
|
||||||
fw:close()
|
|
||||||
end
|
|
||||||
|
|
||||||
local function remove_nginx_conf(nginx_conf_file_path)
|
|
||||||
os.remove(nginx_conf_file_path)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function nginx_command(env, nginx_conf_file_path, nginx_signal)
|
|
||||||
local env_cmd = ""
|
|
||||||
|
|
||||||
if env ~= nil then env_cmd = "-g \"env LOR_ENV=" .. env .. ";\"" end
|
|
||||||
local cmd = "nginx " .. nginx_signal .. " " .. env_cmd .. " -p `pwd`/ -c " .. nginx_conf_file_path
|
|
||||||
print("execute: " .. cmd)
|
|
||||||
return os.execute(cmd)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function start_nginx(env, nginx_conf_file_path)
|
|
||||||
return nginx_command(env, nginx_conf_file_path, '')
|
|
||||||
end
|
|
||||||
|
|
||||||
local function stop_nginx(env, nginx_conf_file_path)
|
|
||||||
return nginx_command(env, nginx_conf_file_path, '-s stop')
|
|
||||||
end
|
|
||||||
|
|
||||||
local function reload_nginx(env, nginx_conf_file_path)
|
|
||||||
return nginx_command(env, nginx_conf_file_path, '-s reload')
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local NginxHandle = {}
|
|
||||||
NginxHandle.__index = NginxHandle
|
|
||||||
|
|
||||||
function NginxHandle.new(necessary_dirs, nginx_conf_content, nginx_conf_file_path)
|
|
||||||
local instance = {
|
|
||||||
nginx_conf_content = nginx_conf_content,
|
|
||||||
nginx_conf_file_path = nginx_conf_file_path,
|
|
||||||
necessary_dirs = necessary_dirs
|
|
||||||
}
|
|
||||||
setmetatable(instance, NginxHandle)
|
|
||||||
return instance
|
|
||||||
end
|
|
||||||
|
|
||||||
function NginxHandle:start(env)
|
|
||||||
create_dirs(self.necessary_dirs)
|
|
||||||
-- create_nginx_conf(self.nginx_conf_file_path, self.nginx_conf_content)
|
|
||||||
|
|
||||||
return start_nginx(env, self.nginx_conf_file_path)
|
|
||||||
end
|
|
||||||
|
|
||||||
function NginxHandle:stop(env)
|
|
||||||
local result = stop_nginx(env, self.nginx_conf_file_path)
|
|
||||||
-- remove_nginx_conf(self.nginx_conf_file_path)
|
|
||||||
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
function NginxHandle:reload(env)
|
|
||||||
-- remove_nginx_conf(self.nginx_conf_file_path)
|
|
||||||
create_dirs(self.necessary_dirs)
|
|
||||||
-- create_nginx_conf(self.nginx_conf_file_path, self.nginx_conf_content)
|
|
||||||
|
|
||||||
return reload_nginx(env, self.nginx_conf_file_path)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
return NginxHandle
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
local pcall = pcall
|
|
||||||
local require = require
|
|
||||||
local iopen = io.open
|
|
||||||
local smatch = string.match
|
|
||||||
|
|
||||||
local Utils = {}
|
|
||||||
|
|
||||||
-- read file
|
|
||||||
function Utils.read_file(file_path)
|
|
||||||
local f = iopen(file_path, "rb")
|
|
||||||
local content = f:read("*a")
|
|
||||||
f:close()
|
|
||||||
return content
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function require_module(module_name)
|
|
||||||
return require(module_name)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- try to require
|
|
||||||
function Utils.try_require(module_name, default)
|
|
||||||
local ok, module_or_err = pcall(require_module, module_name)
|
|
||||||
|
|
||||||
if ok == true then return module_or_err end
|
|
||||||
|
|
||||||
if ok == false and smatch(module_or_err, "'" .. module_name .. "' not found") then
|
|
||||||
return default
|
|
||||||
else
|
|
||||||
error(module_or_err)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Utils.dirname(str)
|
|
||||||
if str:match(".-/.-") then
|
|
||||||
local name = string.gsub(str, "(.*/)(.*)", "%1")
|
|
||||||
return name
|
|
||||||
else
|
|
||||||
return ''
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return Utils
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
name = lor
|
|
||||||
abstract = A fast and minimalist web framework based on OpenResty.
|
|
||||||
version = 0.3.3
|
|
||||||
author = Sumory Wu (@sumory)
|
|
||||||
is_original = yes
|
|
||||||
license = mit
|
|
||||||
repo_link = https://github.com/sumory/lor
|
|
||||||
main_module = lib/lor/index.lua
|
|
||||||
exclude_files = .travis, docker, docs, .travis.yml
|
|
||||||
requires = bungle/lua-resty-template >= 1.9, p0pr0ck5/lua-resty-cookie >= 0.01
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
local type = type
|
|
||||||
|
|
||||||
local version = require("lor.version")
|
|
||||||
local Group = require("lor.lib.router.group")
|
|
||||||
local Router = require("lor.lib.router.router")
|
|
||||||
local Request = require("lor.lib.request")
|
|
||||||
local Response = require("lor.lib.response")
|
|
||||||
local Application = require("lor.lib.application")
|
|
||||||
local Wrap = require("lor.lib.wrap")
|
|
||||||
|
|
||||||
LOR_FRAMEWORK_DEBUG = false
|
|
||||||
|
|
||||||
local createApplication = function(options)
|
|
||||||
if options and options.debug and type(options.debug) == 'boolean' then
|
|
||||||
LOR_FRAMEWORK_DEBUG = options.debug
|
|
||||||
end
|
|
||||||
|
|
||||||
local app = Application:new()
|
|
||||||
app:init(options)
|
|
||||||
|
|
||||||
return app
|
|
||||||
end
|
|
||||||
|
|
||||||
local lor = Wrap:new(createApplication, Router, Group, Request, Response)
|
|
||||||
lor.version = version
|
|
||||||
|
|
||||||
return lor
|
|
||||||
@ -1,181 +0,0 @@
|
|||||||
local pairs = pairs
|
|
||||||
local type = type
|
|
||||||
local xpcall = xpcall
|
|
||||||
local setmetatable = setmetatable
|
|
||||||
|
|
||||||
local Router = require("lor.lib.router.router")
|
|
||||||
local Request = require("lor.lib.request")
|
|
||||||
local Response = require("lor.lib.response")
|
|
||||||
local View = require("lor.lib.view")
|
|
||||||
local supported_http_methods = require("lor.lib.methods")
|
|
||||||
|
|
||||||
local router_conf = {
|
|
||||||
strict_route = true,
|
|
||||||
ignore_case = true,
|
|
||||||
max_uri_segments = true,
|
|
||||||
max_fallback_depth = true
|
|
||||||
}
|
|
||||||
|
|
||||||
local App = {}
|
|
||||||
|
|
||||||
function App:new()
|
|
||||||
local instance = {}
|
|
||||||
instance.cache = {}
|
|
||||||
instance.settings = {}
|
|
||||||
instance.router = Router:new()
|
|
||||||
|
|
||||||
setmetatable(instance, {
|
|
||||||
__index = self,
|
|
||||||
__call = self.handle
|
|
||||||
})
|
|
||||||
|
|
||||||
instance:init_method()
|
|
||||||
return instance
|
|
||||||
end
|
|
||||||
|
|
||||||
function App:run(final_handler)
|
|
||||||
local request = Request:new()
|
|
||||||
local response = Response:new()
|
|
||||||
|
|
||||||
local enable_view = self:getconf("view enable")
|
|
||||||
if enable_view then
|
|
||||||
local view_config = {
|
|
||||||
view_enable = enable_view,
|
|
||||||
view_engine = self:getconf("view engine"), -- view engine: resty-template or others...
|
|
||||||
view_ext = self:getconf("view ext"), -- defautl is "html"
|
|
||||||
view_layout = self:getconf("view layout"), -- defautl is ""
|
|
||||||
views = self:getconf("views") -- template files directory
|
|
||||||
}
|
|
||||||
|
|
||||||
local view = View:new(view_config)
|
|
||||||
response.view = view
|
|
||||||
end
|
|
||||||
|
|
||||||
self:handle(request, response, final_handler)
|
|
||||||
end
|
|
||||||
|
|
||||||
function App:init(options)
|
|
||||||
self:default_configuration(options)
|
|
||||||
end
|
|
||||||
|
|
||||||
function App:default_configuration(options)
|
|
||||||
options = options or {}
|
|
||||||
|
|
||||||
-- view and template configuration
|
|
||||||
if options["view enable"] ~= nil and options["view enable"] == true then
|
|
||||||
self:conf("view enable", true)
|
|
||||||
else
|
|
||||||
self:conf("view enable", false)
|
|
||||||
end
|
|
||||||
self:conf("view engine", options["view engine"] or "tmpl")
|
|
||||||
self:conf("view ext", options["view ext"] or "html")
|
|
||||||
self:conf("view layout", options["view layout"] or "")
|
|
||||||
self:conf("views", options["views"] or "./app/views/")
|
|
||||||
|
|
||||||
self.locals = {}
|
|
||||||
self.locals.settings = self.setttings
|
|
||||||
end
|
|
||||||
|
|
||||||
-- dispatch `req, res` into the pipeline.
|
|
||||||
function App:handle(req, res, callback)
|
|
||||||
local router = self.router
|
|
||||||
local done = callback or function(err)
|
|
||||||
if err then
|
|
||||||
if ngx then ngx.log(ngx.ERR, err) end
|
|
||||||
res:status(500):send("internal error! please check log.")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not router then
|
|
||||||
return done()
|
|
||||||
end
|
|
||||||
|
|
||||||
local err_msg
|
|
||||||
local ok, e = xpcall(function()
|
|
||||||
router:handle(req, res, done)
|
|
||||||
end, function(msg)
|
|
||||||
err_msg = msg
|
|
||||||
end)
|
|
||||||
|
|
||||||
if not ok then
|
|
||||||
done(err_msg)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function App:use(path, fn)
|
|
||||||
self:inner_use(3, path, fn)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- just a mirror for `erroruse`
|
|
||||||
function App:erruse(path, fn)
|
|
||||||
self:erroruse(path, fn)
|
|
||||||
end
|
|
||||||
|
|
||||||
function App:erroruse(path, fn)
|
|
||||||
self:inner_use(4, path, fn)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- should be private
|
|
||||||
function App:inner_use(fn_args_length, path, fn)
|
|
||||||
local router = self.router
|
|
||||||
|
|
||||||
if path and fn and type(path) == "string" then
|
|
||||||
router:use(path, fn, fn_args_length)
|
|
||||||
elseif path and not fn then
|
|
||||||
fn = path
|
|
||||||
path = nil
|
|
||||||
router:use(path, fn, fn_args_length)
|
|
||||||
else
|
|
||||||
error("error usage for `middleware`")
|
|
||||||
end
|
|
||||||
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function App:init_method()
|
|
||||||
for http_method, _ in pairs(supported_http_methods) do
|
|
||||||
self[http_method] = function(_self, path, ...) -- funcs...
|
|
||||||
_self.router:app_route(http_method, path, ...)
|
|
||||||
return _self
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function App:all(path, ...)
|
|
||||||
for http_method, _ in pairs(supported_http_methods) do
|
|
||||||
self.router:app_route(http_method, path, ...)
|
|
||||||
end
|
|
||||||
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function App:conf(setting, val)
|
|
||||||
self.settings[setting] = val
|
|
||||||
|
|
||||||
if router_conf[setting] == true then
|
|
||||||
self.router:conf(setting, val)
|
|
||||||
end
|
|
||||||
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function App:getconf(setting)
|
|
||||||
return self.settings[setting]
|
|
||||||
end
|
|
||||||
|
|
||||||
function App:enable(setting)
|
|
||||||
self.settings[setting] = true
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function App:disable(setting)
|
|
||||||
self.settings[setting] = false
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
--- only for dev
|
|
||||||
function App:gen_graph()
|
|
||||||
return self.router.trie:gen_graph()
|
|
||||||
end
|
|
||||||
|
|
||||||
return App
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
local pcall = pcall
|
|
||||||
local type = type
|
|
||||||
local pairs = pairs
|
|
||||||
|
|
||||||
|
|
||||||
local function debug(...)
|
|
||||||
if not LOR_FRAMEWORK_DEBUG then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local info = { ... }
|
|
||||||
if info and type(info[1]) == 'function' then
|
|
||||||
pcall(function() info[1]() end)
|
|
||||||
elseif info and type(info[1]) == 'table' then
|
|
||||||
for i, v in pairs(info[1]) do
|
|
||||||
print(i, v)
|
|
||||||
end
|
|
||||||
elseif ... ~= nil then
|
|
||||||
print(...)
|
|
||||||
else
|
|
||||||
print("debug not works...")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return debug
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
local utils = require("lor.lib.utils.utils")
|
|
||||||
local ActionHolder = {}
|
|
||||||
|
|
||||||
function ActionHolder:new(func, node, action_type)
|
|
||||||
local instance = {
|
|
||||||
id = "action-" .. utils.random(),
|
|
||||||
node = node,
|
|
||||||
action_type = action_type,
|
|
||||||
func = func,
|
|
||||||
}
|
|
||||||
|
|
||||||
setmetatable(instance, {
|
|
||||||
__index = self,
|
|
||||||
__call = self.func
|
|
||||||
})
|
|
||||||
return instance
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local NodeHolder = {}
|
|
||||||
|
|
||||||
function NodeHolder:new()
|
|
||||||
local instance = {
|
|
||||||
key = "",
|
|
||||||
val = nil, -- Node
|
|
||||||
}
|
|
||||||
setmetatable(instance, { __index = self })
|
|
||||||
return instance
|
|
||||||
end
|
|
||||||
|
|
||||||
local Matched = {}
|
|
||||||
|
|
||||||
function Matched:new()
|
|
||||||
local instance = {
|
|
||||||
node = nil,
|
|
||||||
params = {},
|
|
||||||
pipeline = {},
|
|
||||||
}
|
|
||||||
setmetatable(instance, { __index = self })
|
|
||||||
return instance
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
|
||||||
ActionHolder = ActionHolder,
|
|
||||||
NodeHolder = NodeHolder,
|
|
||||||
Matched = Matched
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
-- get and post methods is guaranteed, the others is still in process
|
|
||||||
-- but all these methods shoule work at most cases by default
|
|
||||||
local supported_http_methods = {
|
|
||||||
get = true, -- work well
|
|
||||||
post = true, -- work well
|
|
||||||
head = true, -- no test
|
|
||||||
options = true, -- no test
|
|
||||||
put = true, -- work well
|
|
||||||
patch = true, -- no test
|
|
||||||
delete = true, -- work well
|
|
||||||
trace = true, -- no test
|
|
||||||
all = true -- todo:
|
|
||||||
}
|
|
||||||
|
|
||||||
return supported_http_methods
|
|
||||||
@ -1,91 +0,0 @@
|
|||||||
local ck = require("resty.cookie")
|
|
||||||
|
|
||||||
-- Mind:
|
|
||||||
-- base on 'lua-resty-cookie', https://github.com/cloudflare/lua-resty-cookie
|
|
||||||
-- this is the default `cookie` middleware
|
|
||||||
-- you're recommended to define your own `cookie` middleware.
|
|
||||||
|
|
||||||
-- usage example:
|
|
||||||
-- app:get("/user", function(req, res, next)
|
|
||||||
-- local ok, err = req.cookie.set({
|
|
||||||
-- key = "qq",
|
|
||||||
-- value = '4==||==hello zhang==||==123456',
|
|
||||||
-- path = "/",
|
|
||||||
-- domain = "new.cn",
|
|
||||||
-- secure = false, --设置后浏览器只有访问https才会把cookie带过来,否则浏览器请求时不带cookie参数
|
|
||||||
-- httponly = true, --设置后js 无法读取
|
|
||||||
-- --expires = ngx.cookie_time(os.time() + 3600),
|
|
||||||
-- max_age = 3600, --用秒来设置cookie的生存期。
|
|
||||||
-- samesite = "Strict", --或者 Lax 指a域名下收到的cookie 不能通过b域名的表单带过来
|
|
||||||
-- extension = "a4334aebaece"
|
|
||||||
-- })
|
|
||||||
-- end)
|
|
||||||
|
|
||||||
local cookie_middleware = function(cookieConfig)
|
|
||||||
return function(req, res, next)
|
|
||||||
local COOKIE, err = ck:new()
|
|
||||||
|
|
||||||
if not COOKIE then
|
|
||||||
req.cookie = {} -- all cookies
|
|
||||||
res._cookie = nil
|
|
||||||
else
|
|
||||||
req.cookie = {
|
|
||||||
set = function(...)
|
|
||||||
local _cookie = COOKIE
|
|
||||||
if not _cookie then
|
|
||||||
return ngx.log(ngx.ERR, "response#none _cookie found to write")
|
|
||||||
end
|
|
||||||
|
|
||||||
local p = ...
|
|
||||||
if type(p) == "table" then
|
|
||||||
local ok, err = _cookie:set(p)
|
|
||||||
if not ok then
|
|
||||||
return ngx.log(ngx.ERR, err)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local params = { ... }
|
|
||||||
local ok, err = _cookie:set({
|
|
||||||
key = params[1],
|
|
||||||
value = params[2] or "",
|
|
||||||
})
|
|
||||||
if not ok then
|
|
||||||
return ngx.log(ngx.ERR, err)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
get = function (name)
|
|
||||||
local _cookie = COOKIE
|
|
||||||
local field, err = _cookie:get(name)
|
|
||||||
|
|
||||||
if not field then
|
|
||||||
return nil
|
|
||||||
else
|
|
||||||
return field
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
get_all = function ()
|
|
||||||
local _cookie = COOKIE
|
|
||||||
local fields, err = _cookie:get_all()
|
|
||||||
|
|
||||||
local t = {}
|
|
||||||
if not fields then
|
|
||||||
return nil
|
|
||||||
else
|
|
||||||
for k, v in pairs(fields) do
|
|
||||||
if k and v then
|
|
||||||
t[k] = v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return t
|
|
||||||
end
|
|
||||||
end
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
next()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return cookie_middleware
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
local init_middleware = function(req, res, next)
|
|
||||||
req.res = res
|
|
||||||
req.next = next
|
|
||||||
res.req = req
|
|
||||||
res.locals = res.locals or {}
|
|
||||||
next()
|
|
||||||
end
|
|
||||||
|
|
||||||
return init_middleware
|
|
||||||
@ -1,184 +0,0 @@
|
|||||||
local type, xpcall = type, xpcall
|
|
||||||
local traceback = debug.traceback
|
|
||||||
local string_sub = string.sub
|
|
||||||
local string_len = string.len
|
|
||||||
local http_time = ngx.http_time
|
|
||||||
local ngx_time = ngx.time
|
|
||||||
local ck = require("resty.cookie")
|
|
||||||
local utils = require("lor.lib.utils.utils")
|
|
||||||
local aes = require("lor.lib.utils.aes")
|
|
||||||
local base64 = require("lor.lib.utils.base64")
|
|
||||||
|
|
||||||
|
|
||||||
local function decode_data(field, aes_key, ase_secret)
|
|
||||||
if not field or field == "" then return {} end
|
|
||||||
local payload = base64.decode(field)
|
|
||||||
local data = {}
|
|
||||||
local cipher = aes.new()
|
|
||||||
local decrypt_str = cipher:decrypt(payload, aes_key, ase_secret)
|
|
||||||
local decode_obj = utils.json_decode(decrypt_str)
|
|
||||||
return decode_obj or data
|
|
||||||
end
|
|
||||||
|
|
||||||
local function encode_data(obj, aes_key, ase_secret)
|
|
||||||
local default = "{}"
|
|
||||||
local str = utils.json_encode(obj) or default
|
|
||||||
local cipher = aes.new()
|
|
||||||
local encrypt_str = cipher:encrypt(str, aes_key, ase_secret)
|
|
||||||
local encode_encrypt_str = base64.encode(encrypt_str)
|
|
||||||
return encode_encrypt_str
|
|
||||||
end
|
|
||||||
|
|
||||||
local function parse_session(field, aes_key, ase_secret)
|
|
||||||
if not field then return end
|
|
||||||
return decode_data(field, aes_key, ase_secret)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- no much secure & performance consideration
|
|
||||||
--- TODO: optimization & security issues
|
|
||||||
local session_middleware = function(config)
|
|
||||||
config = config or {}
|
|
||||||
config.session_key = config.session_key or "_app_"
|
|
||||||
if config.refresh_cookie ~= false then
|
|
||||||
config.refresh_cookie = true
|
|
||||||
end
|
|
||||||
if not config.timeout or type(config.timeout) ~= "number" then
|
|
||||||
config.timeout = 3600 -- default session timeout is 3600 seconds
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local err_tip = "session_aes_key should be set for session middleware"
|
|
||||||
-- backward compatibility for lor < v0.3.2
|
|
||||||
config.session_aes_key = config.session_aes_key or "custom_session_aes_key"
|
|
||||||
if not config.session_aes_key then
|
|
||||||
ngx.log(ngx.ERR, err_tip)
|
|
||||||
end
|
|
||||||
|
|
||||||
local session_key = config.session_key
|
|
||||||
local session_aes_key = config.session_aes_key
|
|
||||||
local refresh_cookie = config.refresh_cookie
|
|
||||||
local timeout = config.timeout
|
|
||||||
|
|
||||||
-- session_aes_secret must be 8 charactors to respect lua-resty-string v0.10+
|
|
||||||
local session_aes_secret = config.session_aes_secret or config.secret or "12345678"
|
|
||||||
if string_len(session_aes_secret) < 8 then
|
|
||||||
for i=1,8-string_len(session_aes_secret),1 do
|
|
||||||
session_aes_secret = session_aes_secret .. "0"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
session_aes_secret = string_sub(session_aes_secret, 1, 8)
|
|
||||||
|
|
||||||
ngx.log(ngx.INFO, "session middleware initialized")
|
|
||||||
return function(req, res, next)
|
|
||||||
if not session_aes_key then
|
|
||||||
return next(err_tip)
|
|
||||||
end
|
|
||||||
|
|
||||||
local cookie, err = ck:new()
|
|
||||||
if not cookie then
|
|
||||||
ngx.log(ngx.ERR, "cookie is nil:", err)
|
|
||||||
end
|
|
||||||
|
|
||||||
local current_session
|
|
||||||
local session_data, err = cookie:get(session_key)
|
|
||||||
if err then
|
|
||||||
ngx.log(ngx.ERR, "cannot get session_data:", err)
|
|
||||||
else
|
|
||||||
if session_data then
|
|
||||||
current_session = parse_session(session_data, session_aes_key, session_aes_secret)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
current_session = current_session or {}
|
|
||||||
|
|
||||||
req.session = {
|
|
||||||
set = function(...)
|
|
||||||
local p = ...
|
|
||||||
if type(p) == "table" then
|
|
||||||
for i, v in pairs(p) do
|
|
||||||
current_session[i] = v
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local params = { ... }
|
|
||||||
if type(params[2]) == "table" then -- set("k", {1, 2, 3})
|
|
||||||
current_session[params[1]] = params[2]
|
|
||||||
else -- set("k", "123")
|
|
||||||
current_session[params[1]] = params[2] or ""
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local value = encode_data(current_session, session_aes_key, session_aes_secret)
|
|
||||||
local expires = http_time(ngx_time() + timeout)
|
|
||||||
local max_age = timeout
|
|
||||||
local ok, err = cookie:set({
|
|
||||||
key = session_key,
|
|
||||||
value = value or "",
|
|
||||||
expires = expires,
|
|
||||||
max_age = max_age,
|
|
||||||
path = "/"
|
|
||||||
})
|
|
||||||
|
|
||||||
ngx.log(ngx.INFO, "session.set: ", value)
|
|
||||||
|
|
||||||
if err or not ok then
|
|
||||||
return ngx.log(ngx.ERR, "session.set error:", err)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
refresh = function()
|
|
||||||
if session_data and session_data ~= "" then
|
|
||||||
local expires = http_time(ngx_time() + timeout)
|
|
||||||
local max_age = timeout
|
|
||||||
local ok, err = cookie:set({
|
|
||||||
key = session_key,
|
|
||||||
value = session_data or "",
|
|
||||||
expires = expires,
|
|
||||||
max_age = max_age,
|
|
||||||
path = "/"
|
|
||||||
})
|
|
||||||
if err or not ok then
|
|
||||||
return ngx.log(ngx.ERR, "session.refresh error:", err)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
get = function(key)
|
|
||||||
return current_session[key]
|
|
||||||
end,
|
|
||||||
|
|
||||||
destroy = function()
|
|
||||||
local expires = "Thu, 01 Jan 1970 00:00:01 GMT"
|
|
||||||
local max_age = 0
|
|
||||||
local ok, err = cookie:set({
|
|
||||||
key = session_key,
|
|
||||||
value = "",
|
|
||||||
expires = expires,
|
|
||||||
max_age = max_age,
|
|
||||||
path = "/"
|
|
||||||
})
|
|
||||||
if err or not ok then
|
|
||||||
ngx.log(ngx.ERR, "session.destroy error:", err)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
if refresh_cookie then
|
|
||||||
local e, ok
|
|
||||||
ok = xpcall(function()
|
|
||||||
req.session.refresh()
|
|
||||||
end, function()
|
|
||||||
e = traceback()
|
|
||||||
end)
|
|
||||||
|
|
||||||
if not ok then
|
|
||||||
ngx.log(ngx.ERR, "refresh cookie error:", e)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
next()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return session_middleware
|
|
||||||
@ -1,265 +0,0 @@
|
|||||||
local setmetatable = setmetatable
|
|
||||||
local type = type
|
|
||||||
local next = next
|
|
||||||
local ipairs = ipairs
|
|
||||||
local table_insert = table.insert
|
|
||||||
local string_lower = string.lower
|
|
||||||
local string_format = string.format
|
|
||||||
|
|
||||||
local utils = require("lor.lib.utils.utils")
|
|
||||||
local supported_http_methods = require("lor.lib.methods")
|
|
||||||
local ActionHolder = require("lor.lib.holder").ActionHolder
|
|
||||||
local handler_error_tip = "handler must be `function` that matches `function(req, res, next) ... end`"
|
|
||||||
local middlware_error_tip = "middlware must be `function` that matches `function(req, res, next) ... end`"
|
|
||||||
local error_middlware_error_tip = "error middlware must be `function` that matches `function(err, req, res, next) ... end`"
|
|
||||||
local node_count = 0
|
|
||||||
|
|
||||||
local function gen_node_id()
|
|
||||||
local prefix = "node-"
|
|
||||||
local worker_part = "dw"
|
|
||||||
if ngx and ngx.worker then
|
|
||||||
worker_part = ngx.worker.id()
|
|
||||||
end
|
|
||||||
node_count = node_count + 1 -- simply count for lua vm level
|
|
||||||
local unique_part = node_count
|
|
||||||
local random_part = utils.random()
|
|
||||||
node_id = prefix .. worker_part .. "-" .. unique_part .. "-" .. random_part
|
|
||||||
return node_id
|
|
||||||
end
|
|
||||||
|
|
||||||
local function check_method(method)
|
|
||||||
if not method then return false end
|
|
||||||
|
|
||||||
method = string_lower(method)
|
|
||||||
if not supported_http_methods[method] then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local Node = {}
|
|
||||||
|
|
||||||
function Node:new(root)
|
|
||||||
local is_root = false
|
|
||||||
if root == true then
|
|
||||||
is_root = true
|
|
||||||
end
|
|
||||||
|
|
||||||
local instance = {
|
|
||||||
id = gen_node_id(),
|
|
||||||
is_root = is_root,
|
|
||||||
name = "",
|
|
||||||
allow = "",
|
|
||||||
pattern = "",
|
|
||||||
endpoint = false,
|
|
||||||
parent = nil,
|
|
||||||
colon_parent = nil,
|
|
||||||
children = {},
|
|
||||||
colon_child= nil,
|
|
||||||
handlers = {},
|
|
||||||
middlewares = {},
|
|
||||||
error_middlewares = {},
|
|
||||||
regex = nil
|
|
||||||
}
|
|
||||||
setmetatable(instance, {
|
|
||||||
__index = self,
|
|
||||||
__tostring = function(s)
|
|
||||||
local ok, result = pcall(function()
|
|
||||||
return string_format("name: %s", s.id)
|
|
||||||
end)
|
|
||||||
if ok then
|
|
||||||
return result
|
|
||||||
else
|
|
||||||
return "node.tostring() error"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
})
|
|
||||||
return instance
|
|
||||||
end
|
|
||||||
|
|
||||||
function Node:find_child(key)
|
|
||||||
--print("find_child: ", self.id, self.name, self.children)
|
|
||||||
for _, c in ipairs(self.children) do
|
|
||||||
if key == c.key then
|
|
||||||
return c.val
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function Node:find_handler(method)
|
|
||||||
method = string_lower(method)
|
|
||||||
if not self.handlers or not self.handlers[method] or #self.handlers[method] == 0 then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function Node:use(...)
|
|
||||||
local middlewares = {...}
|
|
||||||
if not next(middlewares) then
|
|
||||||
error("middleware should not be nil or empty")
|
|
||||||
end
|
|
||||||
|
|
||||||
local empty = true
|
|
||||||
for _, h in ipairs(middlewares) do
|
|
||||||
if type(h) == "function" then
|
|
||||||
local action = ActionHolder:new(h, self, "middleware")
|
|
||||||
table_insert(self.middlewares, action)
|
|
||||||
empty = false
|
|
||||||
elseif type(h) == "table" then
|
|
||||||
for _, hh in ipairs(h) do
|
|
||||||
if type(hh) == "function" then
|
|
||||||
local action = ActionHolder:new(hh, self, "middleware")
|
|
||||||
table_insert(self.middlewares, action)
|
|
||||||
empty = false
|
|
||||||
else
|
|
||||||
error(middlware_error_tip)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
error(middlware_error_tip)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if empty then
|
|
||||||
error("middleware should not be empty")
|
|
||||||
end
|
|
||||||
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function Node:error_use(...)
|
|
||||||
local middlewares = {...}
|
|
||||||
if not next(middlewares) then
|
|
||||||
error("error middleware should not be nil or empty")
|
|
||||||
end
|
|
||||||
|
|
||||||
local empty = true
|
|
||||||
for _, h in ipairs(middlewares) do
|
|
||||||
if type(h) == "function" then
|
|
||||||
local action = ActionHolder:new(h, self, "error_middleware")
|
|
||||||
table_insert(self.error_middlewares, action)
|
|
||||||
empty = false
|
|
||||||
elseif type(h) == "table" then
|
|
||||||
for _, hh in ipairs(h) do
|
|
||||||
if type(hh) == "function" then
|
|
||||||
local action = ActionHolder:new(hh, self, "error_middleware")
|
|
||||||
table_insert(self.error_middlewares, action)
|
|
||||||
empty = false
|
|
||||||
else
|
|
||||||
error(error_middlware_error_tip)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
error(error_middlware_error_tip)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if empty then
|
|
||||||
error("error middleware should not be empty")
|
|
||||||
end
|
|
||||||
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function Node:handle(method, ...)
|
|
||||||
method = string_lower(method)
|
|
||||||
if not check_method(method) then
|
|
||||||
error("error method: ", method or "nil")
|
|
||||||
end
|
|
||||||
|
|
||||||
if self:find_handler(method) then
|
|
||||||
error("[" .. self.pattern .. "] " .. method .. " handler exists yet!")
|
|
||||||
end
|
|
||||||
|
|
||||||
if not self.handlers[method] then
|
|
||||||
self.handlers[method] = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
local empty = true
|
|
||||||
local handlers = {...}
|
|
||||||
if not next(handlers) then
|
|
||||||
error("handler should not be nil or empty")
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, h in ipairs(handlers) do
|
|
||||||
if type(h) == "function" then
|
|
||||||
local action = ActionHolder:new(h, self, "handler")
|
|
||||||
table_insert(self.handlers[method], action)
|
|
||||||
empty = false
|
|
||||||
elseif type(h) == "table" then
|
|
||||||
for _, hh in ipairs(h) do
|
|
||||||
if type(hh) == "function" then
|
|
||||||
local action = ActionHolder:new(hh, self, "handler")
|
|
||||||
table_insert(self.handlers[method], action)
|
|
||||||
empty = false
|
|
||||||
else
|
|
||||||
error(handler_error_tip)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
error(handler_error_tip)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if empty then
|
|
||||||
error("handler should not be empty")
|
|
||||||
end
|
|
||||||
|
|
||||||
if self.allow == "" then
|
|
||||||
self.allow = method
|
|
||||||
else
|
|
||||||
self.allow = self.allow .. ", " .. method
|
|
||||||
end
|
|
||||||
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function Node:get_allow()
|
|
||||||
return self.allow
|
|
||||||
end
|
|
||||||
|
|
||||||
function Node:remove_nested_property(node)
|
|
||||||
if not node then return end
|
|
||||||
if node.parent then
|
|
||||||
node.parent = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
if node.colon_child then
|
|
||||||
if node.colon_child.handlers then
|
|
||||||
for _, h in pairs(node.colon_child.handlers) do
|
|
||||||
if h then
|
|
||||||
for _, action in ipairs(h) do
|
|
||||||
action.func = nil
|
|
||||||
action.node = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self:remove_nested_property(node.colon_child)
|
|
||||||
end
|
|
||||||
|
|
||||||
local children = node.children
|
|
||||||
if children and #children > 0 then
|
|
||||||
for _, v in ipairs(children) do
|
|
||||||
local c = v.val
|
|
||||||
if c.handlers then -- remove action func
|
|
||||||
for _, h in pairs(c.handlers) do
|
|
||||||
if h then
|
|
||||||
for _, action in ipairs(h) do
|
|
||||||
action.func = nil
|
|
||||||
action.node = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
self:remove_nested_property(v.val)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return Node
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
local sfind = string.find
|
|
||||||
local pairs = pairs
|
|
||||||
local type = type
|
|
||||||
local setmetatable = setmetatable
|
|
||||||
local utils = require("lor.lib.utils.utils")
|
|
||||||
|
|
||||||
local Request = {}
|
|
||||||
|
|
||||||
-- new request: init args/params/body etc from http request
|
|
||||||
function Request:new()
|
|
||||||
local body = {} -- body params
|
|
||||||
local headers = ngx.req.get_headers()
|
|
||||||
|
|
||||||
local header = headers['Content-Type']
|
|
||||||
-- the post request have Content-Type header set
|
|
||||||
if header then
|
|
||||||
if sfind(header, "application/x-www-form-urlencoded", 1, true) then
|
|
||||||
ngx.req.read_body()
|
|
||||||
local post_args = ngx.req.get_post_args()
|
|
||||||
if post_args and type(post_args) == "table" then
|
|
||||||
for k,v in pairs(post_args) do
|
|
||||||
body[k] = v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif sfind(header, "application/json", 1, true) then
|
|
||||||
ngx.req.read_body()
|
|
||||||
local json_str = ngx.req.get_body_data()
|
|
||||||
body = utils.json_decode(json_str)
|
|
||||||
-- form-data request
|
|
||||||
elseif sfind(header, "multipart", 1, true) then
|
|
||||||
-- upload request, should not invoke ngx.req.read_body()
|
|
||||||
-- parsed as raw by default
|
|
||||||
else
|
|
||||||
ngx.req.read_body()
|
|
||||||
body = ngx.req.get_body_data()
|
|
||||||
end
|
|
||||||
-- the post request have no Content-Type header set will be parsed as x-www-form-urlencoded by default
|
|
||||||
else
|
|
||||||
ngx.req.read_body()
|
|
||||||
local post_args = ngx.req.get_post_args()
|
|
||||||
if post_args and type(post_args) == "table" then
|
|
||||||
for k,v in pairs(post_args) do
|
|
||||||
body[k] = v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local instance = {
|
|
||||||
path = ngx.var.uri, -- uri
|
|
||||||
method = ngx.req.get_method(),
|
|
||||||
query = ngx.req.get_uri_args(),
|
|
||||||
params = {},
|
|
||||||
body = body,
|
|
||||||
body_raw = ngx.req.get_body_data(),
|
|
||||||
url = ngx.var.request_uri,
|
|
||||||
origin_uri = ngx.var.request_uri,
|
|
||||||
uri = ngx.var.request_uri,
|
|
||||||
headers = headers, -- request headers
|
|
||||||
|
|
||||||
req_args = ngx.var.args,
|
|
||||||
found = false -- 404 or not
|
|
||||||
}
|
|
||||||
setmetatable(instance, { __index = self })
|
|
||||||
return instance
|
|
||||||
end
|
|
||||||
|
|
||||||
function Request:is_found()
|
|
||||||
return self.found
|
|
||||||
end
|
|
||||||
|
|
||||||
function Request:set_found(found)
|
|
||||||
self.found = found
|
|
||||||
end
|
|
||||||
|
|
||||||
return Request
|
|
||||||
@ -1,141 +0,0 @@
|
|||||||
local pairs = pairs
|
|
||||||
local type = type
|
|
||||||
local setmetatable = setmetatable
|
|
||||||
local tinsert = table.insert
|
|
||||||
local tconcat = table.concat
|
|
||||||
local utils = require("lor.lib.utils.utils")
|
|
||||||
|
|
||||||
local Response = {}
|
|
||||||
|
|
||||||
function Response:new()
|
|
||||||
--ngx.status = 200
|
|
||||||
local instance = {
|
|
||||||
http_status = nil,
|
|
||||||
headers = {},
|
|
||||||
locals = {},
|
|
||||||
body = '--default body. you should not see this by default--',
|
|
||||||
view = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
setmetatable(instance, { __index = self })
|
|
||||||
return instance
|
|
||||||
end
|
|
||||||
|
|
||||||
-- todo: optimize-compile before used
|
|
||||||
function Response:render(view_file, data)
|
|
||||||
if not self.view then
|
|
||||||
ngx.log(ngx.ERR, "`view` object is nil, maybe you disabled the view engine.")
|
|
||||||
error("`view` object is nil, maybe you disabled the view engine.")
|
|
||||||
else
|
|
||||||
self:set_header('Content-Type', 'text/html; charset=UTF-8')
|
|
||||||
data = data or {}
|
|
||||||
data.locals = self.locals -- inject res.locals
|
|
||||||
|
|
||||||
local body = self.view:render(view_file, data)
|
|
||||||
self:_send(body)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Response:html(data)
|
|
||||||
self:set_header('Content-Type', 'text/html; charset=UTF-8')
|
|
||||||
self:_send(data)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Response:json(data, empty_table_as_object)
|
|
||||||
self:set_header('Content-Type', 'application/json; charset=utf-8')
|
|
||||||
self:_send(utils.json_encode(data, empty_table_as_object))
|
|
||||||
end
|
|
||||||
|
|
||||||
function Response:redirect(url, code, query)
|
|
||||||
if url and not code and not query then -- only one param
|
|
||||||
ngx.redirect(url)
|
|
||||||
elseif url and code and not query then -- two param
|
|
||||||
if type(code) == "number" then
|
|
||||||
ngx.redirect(url ,code)
|
|
||||||
elseif type(code) == "table" then
|
|
||||||
query = code
|
|
||||||
local q = {}
|
|
||||||
local is_q_exist = false
|
|
||||||
if query and type(query) == "table" then
|
|
||||||
for i,v in pairs(query) do
|
|
||||||
tinsert(q, i .. "=" .. v)
|
|
||||||
is_q_exist = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if is_q_exist then
|
|
||||||
url = url .. "?" .. tconcat(q, "&")
|
|
||||||
end
|
|
||||||
|
|
||||||
ngx.redirect(url)
|
|
||||||
else
|
|
||||||
ngx.redirect(url)
|
|
||||||
end
|
|
||||||
else -- three param
|
|
||||||
local q = {}
|
|
||||||
local is_q_exist = false
|
|
||||||
if query and type(query) == "table" then
|
|
||||||
for i,v in pairs(query) do
|
|
||||||
tinsert(q, i .. "=" .. v)
|
|
||||||
is_q_exist = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if is_q_exist then
|
|
||||||
url = url .. "?" .. tconcat(q, "&")
|
|
||||||
end
|
|
||||||
ngx.redirect(url ,code)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Response:location(url, data)
|
|
||||||
if data and type(data) == "table" then
|
|
||||||
ngx.req.set_uri_args(data)
|
|
||||||
ngx.req.set_uri(url, false)
|
|
||||||
else
|
|
||||||
ngx.say(url)
|
|
||||||
ngx.req.set_uri(url, false)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Response:send(text)
|
|
||||||
self:set_header('Content-Type', 'text/plain; charset=UTF-8')
|
|
||||||
self:_send(text)
|
|
||||||
end
|
|
||||||
|
|
||||||
--~=============================================================
|
|
||||||
|
|
||||||
function Response:_send(content)
|
|
||||||
ngx.status = self.http_status or 200
|
|
||||||
ngx.say(content)
|
|
||||||
DEBUG("=response send content: ", content)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Response:get_body()
|
|
||||||
return self.body
|
|
||||||
end
|
|
||||||
|
|
||||||
function Response:get_headers()
|
|
||||||
return self.headers
|
|
||||||
end
|
|
||||||
|
|
||||||
function Response:get_header(key)
|
|
||||||
return self.headers[key]
|
|
||||||
end
|
|
||||||
|
|
||||||
function Response:set_body(body)
|
|
||||||
if body ~= nil then self.body = body end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Response:status(status)
|
|
||||||
ngx.status = status
|
|
||||||
self.http_status = status
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function Response:set_header(key, value)
|
|
||||||
ngx.header[key] = value
|
|
||||||
end
|
|
||||||
|
|
||||||
return Response
|
|
||||||
@ -1,141 +0,0 @@
|
|||||||
local setmetatable = setmetatable
|
|
||||||
local pairs = pairs
|
|
||||||
local type = type
|
|
||||||
local error = error
|
|
||||||
local next = next
|
|
||||||
local string_format = string.format
|
|
||||||
local string_lower = string.lower
|
|
||||||
local table_insert = table.insert
|
|
||||||
local unpack = table.unpack or unpack
|
|
||||||
|
|
||||||
local supported_http_methods = require("lor.lib.methods")
|
|
||||||
local debug = require("lor.lib.debug")
|
|
||||||
local utils = require("lor.lib.utils.utils")
|
|
||||||
local random = utils.random
|
|
||||||
local clone = utils.clone
|
|
||||||
local handler_error_tip = "handler must be `function` that matches `function(req, res, next) ... end`"
|
|
||||||
|
|
||||||
local Group = {}
|
|
||||||
|
|
||||||
function Group:new()
|
|
||||||
local group = {}
|
|
||||||
|
|
||||||
group.id = random()
|
|
||||||
group.name = "group-" .. group.id
|
|
||||||
group.is_group = true
|
|
||||||
group.apis = {}
|
|
||||||
self:build_method()
|
|
||||||
|
|
||||||
setmetatable(group, {
|
|
||||||
__index = self,
|
|
||||||
__call = self._call,
|
|
||||||
__tostring = function(s)
|
|
||||||
return s.name
|
|
||||||
end
|
|
||||||
})
|
|
||||||
|
|
||||||
return group
|
|
||||||
end
|
|
||||||
|
|
||||||
--- a magick for usage like `lor:Router()`
|
|
||||||
-- generate a new group for different routes group
|
|
||||||
function Group:_call()
|
|
||||||
local cloned = clone(self)
|
|
||||||
cloned.id = random()
|
|
||||||
cloned.name = cloned.name .. ":clone-" .. cloned.id
|
|
||||||
return cloned
|
|
||||||
end
|
|
||||||
|
|
||||||
function Group:get_apis()
|
|
||||||
return self.apis
|
|
||||||
end
|
|
||||||
|
|
||||||
function Group:set_api(path, method, ...)
|
|
||||||
if not path or not method then
|
|
||||||
return error("`path` & `method` should not be nil.")
|
|
||||||
end
|
|
||||||
|
|
||||||
local handlers = {...}
|
|
||||||
if not next(handlers) then
|
|
||||||
return error("handler should not be nil or empty")
|
|
||||||
end
|
|
||||||
|
|
||||||
if type(path) ~= "string" or type(method) ~= "string" or type(handlers) ~= "table" then
|
|
||||||
return error("params type error.")
|
|
||||||
end
|
|
||||||
|
|
||||||
local extended_handlers = {}
|
|
||||||
for _, h in ipairs(handlers) do
|
|
||||||
if type(h) == "function" then
|
|
||||||
table_insert(extended_handlers, h)
|
|
||||||
elseif type(h) == "table" then
|
|
||||||
for _, hh in ipairs(h) do
|
|
||||||
if type(hh) == "function" then
|
|
||||||
table_insert(extended_handlers, hh)
|
|
||||||
else
|
|
||||||
error(handler_error_tip)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
error(handler_error_tip)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
method = string_lower(method)
|
|
||||||
if not supported_http_methods[method] then
|
|
||||||
return error(string_format("[%s] method is not supported yet.", method))
|
|
||||||
end
|
|
||||||
|
|
||||||
self.apis[path] = self.apis[path] or {}
|
|
||||||
self.apis[path][method] = extended_handlers
|
|
||||||
end
|
|
||||||
|
|
||||||
function Group:build_method()
|
|
||||||
for m, _ in pairs(supported_http_methods) do
|
|
||||||
m = string_lower(m)
|
|
||||||
|
|
||||||
-- 1. group_router:get(func1)
|
|
||||||
-- 2. group_router:get(func1, func2)
|
|
||||||
-- 3. group_router:get({func1, func2})
|
|
||||||
-- 4. group_router:get(path, func1)
|
|
||||||
-- 5. group_router:get(path, func1, func2)
|
|
||||||
-- 6. group_router:get(path, {func1, func2})
|
|
||||||
Group[m] = function(myself, ...)
|
|
||||||
local params = {...}
|
|
||||||
if not next(params) then return error("params should not be nil or empty") end
|
|
||||||
|
|
||||||
-- case 1 or 3
|
|
||||||
if #params == 1 then
|
|
||||||
if type(params[1]) ~= "function" and type(params[1]) ~= "table" then
|
|
||||||
return error("it must be an function if there's only one param")
|
|
||||||
end
|
|
||||||
|
|
||||||
if type(params[1]) == "table" and #(params[1]) == 0 then
|
|
||||||
return error("params should not be nil or empty")
|
|
||||||
end
|
|
||||||
|
|
||||||
return Group.set_api(myself, "", m, ...)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- case 2,4,5,6
|
|
||||||
if #params > 1 then
|
|
||||||
if type(params[1]) == "string" then -- case 4,5,6
|
|
||||||
return Group.set_api(myself, params[1], m, unpack(params, 2))
|
|
||||||
else -- case 2
|
|
||||||
return Group.set_api(myself, "", m, ...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
error("error params for group route define")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Group:clone()
|
|
||||||
local cloned = clone(self)
|
|
||||||
cloned.id = random()
|
|
||||||
cloned.name = cloned.name .. ":clone-" .. cloned.id
|
|
||||||
return cloned
|
|
||||||
end
|
|
||||||
|
|
||||||
return Group
|
|
||||||
@ -1,353 +0,0 @@
|
|||||||
local pairs = pairs
|
|
||||||
local ipairs = ipairs
|
|
||||||
local pcall = pcall
|
|
||||||
local xpcall = xpcall
|
|
||||||
local type = type
|
|
||||||
local error = error
|
|
||||||
local setmetatable = setmetatable
|
|
||||||
local traceback = debug.traceback
|
|
||||||
local tinsert = table.insert
|
|
||||||
local table_concat = table.concat
|
|
||||||
local string_format = string.format
|
|
||||||
local string_lower = string.lower
|
|
||||||
|
|
||||||
local utils = require("lor.lib.utils.utils")
|
|
||||||
local supported_http_methods = require("lor.lib.methods")
|
|
||||||
local debug = require("lor.lib.debug")
|
|
||||||
local Trie = require("lor.lib.trie")
|
|
||||||
local random = utils.random
|
|
||||||
local mixin = utils.mixin
|
|
||||||
|
|
||||||
local allowed_conf = {
|
|
||||||
strict_route = {
|
|
||||||
t = "boolean"
|
|
||||||
},
|
|
||||||
ignore_case = {
|
|
||||||
t = "boolean"
|
|
||||||
},
|
|
||||||
max_uri_segments = {
|
|
||||||
t = "number"
|
|
||||||
},
|
|
||||||
max_fallback_depth = {
|
|
||||||
t = "number"
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
local function restore(fn, obj)
|
|
||||||
local origin = {
|
|
||||||
path = obj['path'],
|
|
||||||
query = obj['query'],
|
|
||||||
next = obj['next'],
|
|
||||||
locals = obj['locals'],
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(err)
|
|
||||||
obj['path'] = origin.path
|
|
||||||
obj['query'] = origin.query
|
|
||||||
obj['next'] = origin.next
|
|
||||||
obj['locals'] = origin.locals
|
|
||||||
fn(err)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function compose_func(matched, method)
|
|
||||||
if not matched or type(matched.pipeline) ~= "table" then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local exact_node = matched.node
|
|
||||||
local pipeline = matched.pipeline or {}
|
|
||||||
if not exact_node or not pipeline then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local stack = {}
|
|
||||||
for _, p in ipairs(pipeline) do
|
|
||||||
local middlewares = p.middlewares
|
|
||||||
local handlers = p.handlers
|
|
||||||
if middlewares then
|
|
||||||
for _, middleware in ipairs(middlewares) do
|
|
||||||
tinsert(stack, middleware)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if p.id == exact_node.id and handlers and handlers[method] then
|
|
||||||
for _, handler in ipairs(handlers[method]) do
|
|
||||||
tinsert(stack, handler)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return stack
|
|
||||||
end
|
|
||||||
|
|
||||||
local function compose_error_handler(node)
|
|
||||||
if not node then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local stack = {}
|
|
||||||
while node do
|
|
||||||
for _, middleware in ipairs(node.error_middlewares) do
|
|
||||||
tinsert(stack, middleware)
|
|
||||||
end
|
|
||||||
node = node.parent
|
|
||||||
end
|
|
||||||
|
|
||||||
return stack
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local Router = {}
|
|
||||||
|
|
||||||
function Router:new(options)
|
|
||||||
local opts = options or {}
|
|
||||||
local router = {}
|
|
||||||
|
|
||||||
router.name = "router-" .. random()
|
|
||||||
router.trie = Trie:new({
|
|
||||||
ignore_case = opts.ignore_case,
|
|
||||||
strict_route = opts.strict_route,
|
|
||||||
max_uri_segments = opts.max_uri_segments,
|
|
||||||
max_fallback_depth = opts.max_fallback_depth
|
|
||||||
})
|
|
||||||
|
|
||||||
self:init()
|
|
||||||
setmetatable(router, {
|
|
||||||
__index = self,
|
|
||||||
__tostring = function(s)
|
|
||||||
local ok, result = pcall(function()
|
|
||||||
return string_format("name: %s", s.name)
|
|
||||||
end)
|
|
||||||
if ok then
|
|
||||||
return result
|
|
||||||
else
|
|
||||||
return "router.tostring() error"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
})
|
|
||||||
|
|
||||||
return router
|
|
||||||
end
|
|
||||||
|
|
||||||
--- a magick to convert `router()` to `router:handle()`
|
|
||||||
-- so a router() could be regarded as a `middleware`
|
|
||||||
function Router:call()
|
|
||||||
return function(req, res, next)
|
|
||||||
return self:handle(req, res, next)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- dispatch a request
|
|
||||||
function Router:handle(req, res, out)
|
|
||||||
local path = req.path
|
|
||||||
if not path or path == "" then
|
|
||||||
path = ""
|
|
||||||
end
|
|
||||||
local method = req.method and string_lower(req.method)
|
|
||||||
local done = out
|
|
||||||
|
|
||||||
local stack = nil
|
|
||||||
local matched = self.trie:match(path)
|
|
||||||
local matched_node = matched.node
|
|
||||||
|
|
||||||
-- ngx.log(ngx.ERR, "rounter handler req: ", inspect(req))
|
|
||||||
|
|
||||||
if not method or not matched_node then
|
|
||||||
if res.status then res:status(404) end
|
|
||||||
return self:error_handle("404! not found.", req, res, self.trie.root, done)
|
|
||||||
else
|
|
||||||
local matched_handlers = matched_node.handlers and matched_node.handlers[method]
|
|
||||||
if not matched_handlers or #matched_handlers <= 0 then
|
|
||||||
return self:error_handle("Oh! no handler to process method: " .. method, req, res, self.trie.root, done)
|
|
||||||
end
|
|
||||||
|
|
||||||
stack = compose_func(matched, method)
|
|
||||||
if not stack or #stack <= 0 then
|
|
||||||
return self:error_handle("Oh! no handlers found.", req, res, self.trie.root, done)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local stack_len = #stack
|
|
||||||
req:set_found(true)
|
|
||||||
local parsed_params = matched.params or {} -- origin params, parsed
|
|
||||||
req.params = parsed_params
|
|
||||||
|
|
||||||
local idx = 0
|
|
||||||
local function next(err)
|
|
||||||
if err then
|
|
||||||
return self:error_handle(err, req, res, stack[idx].node, done)
|
|
||||||
end
|
|
||||||
|
|
||||||
if idx > stack_len then
|
|
||||||
return done(err) -- err is nil or not
|
|
||||||
end
|
|
||||||
|
|
||||||
idx = idx + 1
|
|
||||||
local handler = stack[idx]
|
|
||||||
if not handler then
|
|
||||||
return done(err)
|
|
||||||
end
|
|
||||||
|
|
||||||
local err_msg
|
|
||||||
local ok, ee = xpcall(function()
|
|
||||||
handler.func(req, res, next)
|
|
||||||
req.params = mixin(parsed_params, req.params)
|
|
||||||
end, function(msg)
|
|
||||||
if msg then
|
|
||||||
if type(msg) == "string" then
|
|
||||||
err_msg = msg
|
|
||||||
elseif type(msg) == "table" then
|
|
||||||
err_msg = "[ERROR]" .. table_concat(msg, "|") .. "[/ERROR]"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
err_msg = ""
|
|
||||||
end
|
|
||||||
err_msg = err_msg .. "\n" .. traceback()
|
|
||||||
end)
|
|
||||||
|
|
||||||
if not ok then
|
|
||||||
--debug("handler func:call error ---> to error_handle,", ok, "err_msg:", err_msg)
|
|
||||||
return self:error_handle(err_msg, req, res, handler.node, done)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
next()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- dispatch an error
|
|
||||||
function Router:error_handle(err_msg, req, res, node, done)
|
|
||||||
local stack = compose_error_handler(node)
|
|
||||||
if not stack or #stack <= 0 then
|
|
||||||
return done(err_msg)
|
|
||||||
end
|
|
||||||
|
|
||||||
local idx = 0
|
|
||||||
local stack_len = #stack
|
|
||||||
local function next(err)
|
|
||||||
if idx >= stack_len then
|
|
||||||
return done(err)
|
|
||||||
end
|
|
||||||
|
|
||||||
idx = idx + 1
|
|
||||||
local error_handler = stack[idx]
|
|
||||||
if not error_handler then
|
|
||||||
return done(err)
|
|
||||||
end
|
|
||||||
|
|
||||||
local ok, ee = xpcall(function()
|
|
||||||
error_handler.func(err, req, res, next)
|
|
||||||
end, function(msg)
|
|
||||||
if msg then
|
|
||||||
if type(msg) == "string" then
|
|
||||||
err_msg = msg
|
|
||||||
elseif type(msg) == "table" then
|
|
||||||
err_msg = "[ERROR]" .. table_concat(msg, "|") .. "[/ERROR]"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
err_msg = ""
|
|
||||||
end
|
|
||||||
|
|
||||||
err_msg = string_format("%s\n[ERROR in ErrorMiddleware#%s(%s)] %s \n%s", err, idx, error_handler.id, err_msg, traceback())
|
|
||||||
end)
|
|
||||||
|
|
||||||
if not ok then
|
|
||||||
return done(err_msg)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
next(err_msg)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Router:use(path, fn, fn_args_length)
|
|
||||||
if type(fn) == "function" then -- fn is a function
|
|
||||||
local node
|
|
||||||
if not path then
|
|
||||||
node = self.trie.root
|
|
||||||
else
|
|
||||||
node = self.trie:add_node(path)
|
|
||||||
end
|
|
||||||
if fn_args_length == 3 then
|
|
||||||
node:use(fn)
|
|
||||||
elseif fn_args_length == 4 then
|
|
||||||
node:error_use(fn)
|
|
||||||
end
|
|
||||||
elseif fn and fn.is_group == true then -- fn is a group router
|
|
||||||
if fn_args_length ~= 3 then
|
|
||||||
error("illegal param, fn_args_length should be 3")
|
|
||||||
end
|
|
||||||
|
|
||||||
path = path or "" -- if path is nil, then mount it on `root`
|
|
||||||
self:merge_group(path, fn)
|
|
||||||
end
|
|
||||||
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function Router:merge_group(prefix, group)
|
|
||||||
local apis = group:get_apis()
|
|
||||||
|
|
||||||
if apis then
|
|
||||||
for uri, api_methods in pairs(apis) do
|
|
||||||
if type(api_methods) == "table" then
|
|
||||||
local path
|
|
||||||
if uri == "" then -- for group index route
|
|
||||||
path = utils.clear_slash(prefix)
|
|
||||||
else
|
|
||||||
path = utils.clear_slash(prefix .. "/" .. uri)
|
|
||||||
end
|
|
||||||
|
|
||||||
local node = self.trie:add_node(path)
|
|
||||||
if not node then
|
|
||||||
return error("cann't define node on router trie, path:" .. path)
|
|
||||||
end
|
|
||||||
|
|
||||||
for method, handlers in pairs(api_methods) do
|
|
||||||
local m = string_lower(method)
|
|
||||||
if supported_http_methods[m] == true then
|
|
||||||
node:handle(m, handlers)
|
|
||||||
end -- supported method
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end -- ugly arrow style for missing `continue`
|
|
||||||
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function Router:app_route(http_method, path, ...)
|
|
||||||
local node = self.trie:add_node(path)
|
|
||||||
node:handle(http_method, ...)
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function Router:init()
|
|
||||||
for http_method, _ in pairs(supported_http_methods) do
|
|
||||||
self[http_method] = function(s, path, ...)
|
|
||||||
local node = s.trie:add_node(path)
|
|
||||||
node:handle(http_method, ...)
|
|
||||||
return s
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Router:conf(setting, val)
|
|
||||||
local allow = allowed_conf[setting]
|
|
||||||
if allow then
|
|
||||||
if allow.t == "boolean" then
|
|
||||||
|
|
||||||
if val == "true" or val == true then
|
|
||||||
self.trie[setting] = true
|
|
||||||
elseif val == "false" or val == false then
|
|
||||||
self.trie[setting] = false
|
|
||||||
end
|
|
||||||
elseif allow.t == "number" then
|
|
||||||
val = tonumber(val)
|
|
||||||
self.trie[setting] = val or self[setting]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
return Router
|
|
||||||
@ -1,532 +0,0 @@
|
|||||||
local setmetatable = setmetatable
|
|
||||||
local tonumber = tonumber
|
|
||||||
local string_lower = string.lower
|
|
||||||
local string_find = string.find
|
|
||||||
local string_sub = string.sub
|
|
||||||
local string_gsub = string.gsub
|
|
||||||
local string_len = string.len
|
|
||||||
local string_format = string.format
|
|
||||||
local table_insert = table.insert
|
|
||||||
local table_remove = table.remove
|
|
||||||
local table_concat = table.concat
|
|
||||||
|
|
||||||
local utils = require("lor.lib.utils.utils")
|
|
||||||
local holder = require("lor.lib.holder")
|
|
||||||
local Node = require("lor.lib.node")
|
|
||||||
local NodeHolder = holder.NodeHolder
|
|
||||||
local Matched = holder.Matched
|
|
||||||
local mixin = utils.mixin
|
|
||||||
local valid_segment_tip = "valid path should only contains: [A-Za-z0-9._%-~]"
|
|
||||||
|
|
||||||
|
|
||||||
local function check_segment(segment)
|
|
||||||
local tmp = string_gsub(segment, "([A-Za-z0-9._%-~]+)", "")
|
|
||||||
if tmp ~= "" then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local function check_colon_child(node, colon_child)
|
|
||||||
if not node or not colon_child then
|
|
||||||
return false, nil
|
|
||||||
end
|
|
||||||
|
|
||||||
if node.name ~= colon_child.name or node.regex ~= colon_child.regex then
|
|
||||||
return false, colon_child
|
|
||||||
end
|
|
||||||
|
|
||||||
return true, nil -- could be added
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_or_new_node(parent, frag, ignore_case)
|
|
||||||
if not frag or frag == "/" or frag == "" then
|
|
||||||
frag = ""
|
|
||||||
end
|
|
||||||
|
|
||||||
if ignore_case == true then
|
|
||||||
frag = string_lower(frag)
|
|
||||||
end
|
|
||||||
|
|
||||||
local node = parent:find_child(frag)
|
|
||||||
if node then
|
|
||||||
return node
|
|
||||||
end
|
|
||||||
|
|
||||||
node = Node:new()
|
|
||||||
node.parent = parent
|
|
||||||
|
|
||||||
if frag == "" then
|
|
||||||
local nodePack = NodeHolder:new()
|
|
||||||
nodePack.key = frag
|
|
||||||
nodePack.val = node
|
|
||||||
table_insert(parent.children, nodePack)
|
|
||||||
else
|
|
||||||
local first = string_sub(frag, 1, 1)
|
|
||||||
if first == ":" then
|
|
||||||
local name = string_sub(frag, 2)
|
|
||||||
local trailing = string_sub(name, -1)
|
|
||||||
|
|
||||||
if trailing == ')' then
|
|
||||||
local index = string_find(name, "%(")
|
|
||||||
if index and index > 1 then
|
|
||||||
local regex = string_sub(name, index+1, #name-1)
|
|
||||||
if #regex > 0 then
|
|
||||||
name = string_sub(name, 1, index-1 )
|
|
||||||
node.regex = regex
|
|
||||||
else
|
|
||||||
error("invalid pattern[1]: " .. frag)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local is_name_valid = check_segment(name)
|
|
||||||
if not is_name_valid then
|
|
||||||
error("invalid pattern[2], illegal path:" .. name .. ", " .. valid_segment_tip)
|
|
||||||
end
|
|
||||||
node.name = name
|
|
||||||
|
|
||||||
local colon_child = parent.colon_child
|
|
||||||
if colon_child then
|
|
||||||
local valid, conflict = check_colon_child(node, colon_child)
|
|
||||||
if not valid then
|
|
||||||
error("invalid pattern[3]: [" .. name .. "] conflict with [" .. conflict.name .. "]")
|
|
||||||
else
|
|
||||||
return colon_child
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
parent.colon_child = node
|
|
||||||
else
|
|
||||||
local is_name_valid = check_segment(frag)
|
|
||||||
if not is_name_valid then
|
|
||||||
error("invalid pattern[6]: " .. frag .. ", " .. valid_segment_tip)
|
|
||||||
end
|
|
||||||
|
|
||||||
local nodePack = NodeHolder:new()
|
|
||||||
nodePack.key = frag
|
|
||||||
nodePack.val = node
|
|
||||||
table_insert(parent.children, nodePack)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return node
|
|
||||||
end
|
|
||||||
|
|
||||||
local function insert_node(parent, frags, ignore_case)
|
|
||||||
local frag = frags[1]
|
|
||||||
local child = get_or_new_node(parent, frag, ignore_case)
|
|
||||||
|
|
||||||
if #frags >= 1 then
|
|
||||||
table_remove(frags, 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
if #frags == 0 then
|
|
||||||
child.endpoint = true
|
|
||||||
return child
|
|
||||||
end
|
|
||||||
|
|
||||||
return insert_node(child, frags, ignore_case)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_pipeline(node)
|
|
||||||
local pipeline = {}
|
|
||||||
if not node then return pipeline end
|
|
||||||
|
|
||||||
local tmp = {}
|
|
||||||
local origin_node = node
|
|
||||||
table_insert(tmp, origin_node)
|
|
||||||
while node.parent
|
|
||||||
do
|
|
||||||
table_insert(tmp, node.parent)
|
|
||||||
node = node.parent
|
|
||||||
end
|
|
||||||
|
|
||||||
for i = #tmp, 1, -1 do
|
|
||||||
table_insert(pipeline, tmp[i])
|
|
||||||
end
|
|
||||||
|
|
||||||
return pipeline
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local Trie = {}
|
|
||||||
|
|
||||||
function Trie:new(opts)
|
|
||||||
opts = opts or {}
|
|
||||||
local trie = {
|
|
||||||
-- limit to avoid dead `while` or attack for fallback lookup
|
|
||||||
max_fallback_depth = 100,
|
|
||||||
|
|
||||||
-- limit to avoid uri attack. e.g. a long uri, /a/b/c/d/e/f/g/h/i/j/k...
|
|
||||||
max_uri_segments = 100,
|
|
||||||
|
|
||||||
-- should ignore case or not
|
|
||||||
ignore_case = true,
|
|
||||||
|
|
||||||
-- [true]: "test.com/" is not the same with "test.com".
|
|
||||||
-- [false]: "test.com/" will match "test.com/" first, then try to math "test.com" if not exists
|
|
||||||
strict_route = true,
|
|
||||||
|
|
||||||
-- the root node of this trie structure
|
|
||||||
root = Node:new(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
trie.max_fallback_depth = tonumber(opts.max_fallback_depth) or trie.max_fallback_depth
|
|
||||||
trie.max_uri_segments = tonumber(opts.max_uri_segments) or trie.max_uri_segments
|
|
||||||
trie.ignore_case = opts.ignore_case or trie.ignore_case
|
|
||||||
trie.strict_route = not (opts.strict_route == false)
|
|
||||||
|
|
||||||
setmetatable(trie, {
|
|
||||||
__index = self,
|
|
||||||
__tostring = function(s)
|
|
||||||
return string_format("Trie, ignore_case:%s strict_route:%s max_uri_segments:%d max_fallback_depth:%d",
|
|
||||||
s.ignore_case, s.strict_route, s.max_uri_segments, s.max_fallback_depth)
|
|
||||||
end
|
|
||||||
})
|
|
||||||
|
|
||||||
return trie
|
|
||||||
end
|
|
||||||
|
|
||||||
function Trie:add_node(pattern)
|
|
||||||
pattern = utils.trim_path_spaces(pattern)
|
|
||||||
|
|
||||||
if string_find(pattern, "//") then
|
|
||||||
error("`//` is not allowed: " .. pattern)
|
|
||||||
end
|
|
||||||
|
|
||||||
local tmp_pattern = utils.trim_prefix_slash(pattern)
|
|
||||||
local tmp_segments = utils.split(tmp_pattern, "/")
|
|
||||||
|
|
||||||
local node = insert_node(self.root, tmp_segments, self.ignore_case)
|
|
||||||
if node.pattern == "" then
|
|
||||||
node.pattern = pattern
|
|
||||||
end
|
|
||||||
|
|
||||||
return node
|
|
||||||
end
|
|
||||||
|
|
||||||
--- get matched colon node
|
|
||||||
function Trie:get_colon_node(parent, segment)
|
|
||||||
local child = parent.colon_child
|
|
||||||
if child and child.regex and not utils.is_match(segment, child.regex) then
|
|
||||||
child = nil -- illegal & not mathed regrex
|
|
||||||
end
|
|
||||||
return child
|
|
||||||
end
|
|
||||||
|
|
||||||
--- retry to fallback to lookup the colon nodes in `stack`
|
|
||||||
function Trie:fallback_lookup(fallback_stack, segments, params)
|
|
||||||
if #fallback_stack == 0 then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local fallback = table_remove(fallback_stack, #fallback_stack)
|
|
||||||
local segment_index = fallback.segment_index
|
|
||||||
local parent = fallback.colon_node
|
|
||||||
local matched = Matched:new()
|
|
||||||
|
|
||||||
if parent.name ~= "" then -- fallback to the colon node and fill param if matched
|
|
||||||
matched.params[parent.name] = segments[segment_index]
|
|
||||||
end
|
|
||||||
mixin(params, matched.params) -- mixin params parsed before
|
|
||||||
|
|
||||||
local flag = true
|
|
||||||
for i, s in ipairs(segments) do
|
|
||||||
if i <= segment_index then -- mind: should use <= not <
|
|
||||||
-- continue
|
|
||||||
else
|
|
||||||
local node, colon_node, is_same = self:find_matched_child(parent, s)
|
|
||||||
if self.ignore_case and node == nil then
|
|
||||||
node, colon_node, is_same = self:find_matched_child(parent, string_lower(s))
|
|
||||||
end
|
|
||||||
|
|
||||||
if colon_node and not is_same then
|
|
||||||
-- save colon node to fallback stack
|
|
||||||
table_insert(fallback_stack, {
|
|
||||||
segment_index = i,
|
|
||||||
colon_node = colon_node
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
if node == nil then -- both exact child and colon child is nil
|
|
||||||
flag = false -- should not set parent value
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
parent = node
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if flag and parent.endpoint then
|
|
||||||
matched.node = parent
|
|
||||||
matched.pipeline = get_pipeline(parent)
|
|
||||||
end
|
|
||||||
|
|
||||||
if matched.node then
|
|
||||||
return matched
|
|
||||||
else
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- find exactly mathed node and colon node
|
|
||||||
function Trie:find_matched_child(parent, segment)
|
|
||||||
local child = parent:find_child(segment)
|
|
||||||
local colon_node = self:get_colon_node(parent, segment)
|
|
||||||
|
|
||||||
if child then
|
|
||||||
if colon_node then
|
|
||||||
return child, colon_node, false
|
|
||||||
else
|
|
||||||
return child, nil, false
|
|
||||||
end
|
|
||||||
else -- not child
|
|
||||||
if colon_node then
|
|
||||||
return colon_node, colon_node, true -- 后续不再压栈
|
|
||||||
else
|
|
||||||
return nil, nil, false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Trie:match(path)
|
|
||||||
if not path or path == "" then
|
|
||||||
error("`path` should not be nil or empty")
|
|
||||||
end
|
|
||||||
|
|
||||||
path = utils.slim_path(path)
|
|
||||||
|
|
||||||
local first = string_sub(path, 1, 1)
|
|
||||||
if first ~= '/' then
|
|
||||||
error("`path` is not start with prefix /: " .. path)
|
|
||||||
end
|
|
||||||
|
|
||||||
if path == "" then -- special case: regard "test.com" as "test.com/"
|
|
||||||
path = "/"
|
|
||||||
end
|
|
||||||
|
|
||||||
local matched = self:_match(path)
|
|
||||||
if not matched.node and self.strict_route ~= true then
|
|
||||||
if string_sub(path, -1) == '/' then -- retry to find path without last slash
|
|
||||||
matched = self:_match(string_sub(path, 1, -2))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return matched
|
|
||||||
end
|
|
||||||
|
|
||||||
function Trie:_match(path)
|
|
||||||
local start_pos = 2
|
|
||||||
local end_pos = string_len(path) + 1
|
|
||||||
local segments = {}
|
|
||||||
for i = 2, end_pos, 1 do -- should set max depth to avoid attack
|
|
||||||
if i < end_pos and string_sub(path, i, i) ~= '/' then
|
|
||||||
-- continue
|
|
||||||
else
|
|
||||||
local segment = string_sub(path, start_pos, i-1)
|
|
||||||
table_insert(segments, segment)
|
|
||||||
start_pos = i + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local flag = true -- whether to continue to find matched node or not
|
|
||||||
local matched = Matched:new()
|
|
||||||
local parent = self.root
|
|
||||||
local fallback_stack = {}
|
|
||||||
for i, s in ipairs(segments) do
|
|
||||||
local node, colon_node, is_same = self:find_matched_child(parent, s)
|
|
||||||
if self.ignore_case and node == nil then
|
|
||||||
node, colon_node, is_same = self:find_matched_child(parent, string_lower(s))
|
|
||||||
end
|
|
||||||
|
|
||||||
if colon_node and not is_same then
|
|
||||||
table_insert(fallback_stack, {
|
|
||||||
segment_index = i,
|
|
||||||
colon_node = colon_node
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
if node == nil then -- both exact child and colon child is nil
|
|
||||||
flag = false -- should not set parent value
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
parent = node
|
|
||||||
if parent.name ~= "" then
|
|
||||||
matched.params[parent.name] = s
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if flag and parent.endpoint then
|
|
||||||
matched.node = parent
|
|
||||||
end
|
|
||||||
|
|
||||||
local params = matched.params or {}
|
|
||||||
if not matched.node then
|
|
||||||
local depth = 0
|
|
||||||
local exit = false
|
|
||||||
|
|
||||||
while not exit do
|
|
||||||
depth = depth + 1
|
|
||||||
if depth > self.max_fallback_depth then
|
|
||||||
error("fallback lookup reaches the limit: " .. self.max_fallback_depth)
|
|
||||||
end
|
|
||||||
|
|
||||||
exit = self:fallback_lookup(fallback_stack, segments, params)
|
|
||||||
if exit then
|
|
||||||
matched = exit
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
if #fallback_stack == 0 then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
matched.params = params
|
|
||||||
if matched.node then
|
|
||||||
matched.pipeline = get_pipeline(matched.node)
|
|
||||||
end
|
|
||||||
|
|
||||||
return matched
|
|
||||||
end
|
|
||||||
|
|
||||||
--- only for dev purpose: pretty json preview
|
|
||||||
-- must not be invoked in runtime
|
|
||||||
function Trie:remove_nested_property(node)
|
|
||||||
if not node then return end
|
|
||||||
if node.parent then
|
|
||||||
node.parent = nil
|
|
||||||
end
|
|
||||||
if node.handlers then
|
|
||||||
for _, h in pairs(node.handlers) do
|
|
||||||
if h then
|
|
||||||
for _, action in ipairs(h) do
|
|
||||||
action.func = nil
|
|
||||||
action.node = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if node.middlewares then
|
|
||||||
for _, m in pairs(node.middlewares) do
|
|
||||||
if m then
|
|
||||||
m.func = nil
|
|
||||||
m.node = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if node.error_middlewares then
|
|
||||||
for _, m in pairs(node.error_middlewares) do
|
|
||||||
if m then
|
|
||||||
m.func = nil
|
|
||||||
m.node = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if node.colon_child then
|
|
||||||
if node.colon_child.handlers then
|
|
||||||
for _, h in pairs(node.colon_child.handlers) do
|
|
||||||
if h then
|
|
||||||
for _, action in ipairs(h) do
|
|
||||||
action.func = nil
|
|
||||||
action.node = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if node.colon_child.middlewares then
|
|
||||||
for _, m in pairs(node.colon_child.middlewares) do
|
|
||||||
if m then
|
|
||||||
m.func = nil
|
|
||||||
m.node = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if node.colon_child.error_middlewares then
|
|
||||||
for _, m in pairs(node.colon_child.error_middlewares) do
|
|
||||||
if m then
|
|
||||||
m.func = nil
|
|
||||||
m.node = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self:remove_nested_property(node.colon_child)
|
|
||||||
end
|
|
||||||
|
|
||||||
local children = node.children
|
|
||||||
if children and #children > 0 then
|
|
||||||
for _, v in ipairs(children) do
|
|
||||||
local c = v.val
|
|
||||||
if c.handlers then -- remove action func
|
|
||||||
for _, h in pairs(c.handlers) do
|
|
||||||
if h then
|
|
||||||
for _, action in ipairs(h) do
|
|
||||||
action.func = nil
|
|
||||||
action.node = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if c.middlewares then
|
|
||||||
for _, m in pairs(c.middlewares) do
|
|
||||||
if m then
|
|
||||||
m.func = nil
|
|
||||||
m.node = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if c.error_middlewares then
|
|
||||||
for _, m in pairs(c.error_middlewares) do
|
|
||||||
if m then
|
|
||||||
m.func = nil
|
|
||||||
m.node = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
self:remove_nested_property(v.val)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- only for dev purpose: graph preview
|
|
||||||
-- must not be invoked in runtime
|
|
||||||
function Trie:gen_graph()
|
|
||||||
local cloned_trie = utils.clone(self)
|
|
||||||
cloned_trie:remove_nested_property(cloned_trie.root)
|
|
||||||
local result = {"graph TD", cloned_trie.root.id .. "((root))"}
|
|
||||||
|
|
||||||
local function recursive_draw(node, res)
|
|
||||||
if node.is_root then node.key = "root" end
|
|
||||||
|
|
||||||
local colon_child = node.colon_child
|
|
||||||
if colon_child then
|
|
||||||
table_insert(res, node.id .. "-->" .. colon_child.id .. "(:" .. colon_child.name .. "<br/>" .. colon_child.id .. ")")
|
|
||||||
recursive_draw(colon_child, res)
|
|
||||||
end
|
|
||||||
|
|
||||||
local children = node.children
|
|
||||||
if children and #children > 0 then
|
|
||||||
for _, v in ipairs(children) do
|
|
||||||
if v.key == "" then
|
|
||||||
--table_insert(res, node.id .. "-->" .. v.val.id .. "[*EMPTY*]")
|
|
||||||
local text = {node.id, "-->", v.val.id, "(<center>", "*EMPTY*", "<br/>", v.val.id, "</center>)"}
|
|
||||||
table_insert(res, table_concat(text, ""))
|
|
||||||
else
|
|
||||||
local text = {node.id, "-->", v.val.id, "(<center>", v.key, "<br/>", v.val.id, "</center>)"}
|
|
||||||
table_insert(res, table_concat(text, ""))
|
|
||||||
end
|
|
||||||
recursive_draw(v.val, res)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
recursive_draw(cloned_trie.root, result)
|
|
||||||
return table.concat(result, "\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
return Trie
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
-- from lua-resty-session
|
|
||||||
local setmetatable = setmetatable
|
|
||||||
local tonumber = tonumber
|
|
||||||
local aes = require "resty.aes"
|
|
||||||
local cip = aes.cipher
|
|
||||||
local hashes = aes.hash
|
|
||||||
local var = ngx.var
|
|
||||||
|
|
||||||
local CIPHER_MODES = {
|
|
||||||
ecb = "ecb",
|
|
||||||
cbc = "cbc",
|
|
||||||
cfb1 = "cfb1",
|
|
||||||
cfb8 = "cfb8",
|
|
||||||
cfb128 = "cfb128",
|
|
||||||
ofb = "ofb",
|
|
||||||
ctr = "ctr"
|
|
||||||
}
|
|
||||||
|
|
||||||
local CIPHER_SIZES = {
|
|
||||||
["128"] = 128,
|
|
||||||
["192"] = 192,
|
|
||||||
["256"] = 256
|
|
||||||
}
|
|
||||||
|
|
||||||
local defaults = {
|
|
||||||
size = CIPHER_SIZES[var.session_aes_size] or 256,
|
|
||||||
mode = CIPHER_MODES[var.session_aes_mode] or "cbc",
|
|
||||||
hash = hashes[var.session_aes_hash] or "sha512",
|
|
||||||
rounds = tonumber(var.session_aes_rounds) or 1
|
|
||||||
}
|
|
||||||
|
|
||||||
local cipher = {}
|
|
||||||
|
|
||||||
cipher.__index = cipher
|
|
||||||
|
|
||||||
function cipher.new(config)
|
|
||||||
local a = config and config.aes or defaults
|
|
||||||
return setmetatable({
|
|
||||||
size = CIPHER_SIZES[a.size or defaults.size] or 256,
|
|
||||||
mode = CIPHER_MODES[a.mode or defaults.mode] or "cbc",
|
|
||||||
hash = hashes[a.hash or defaults.hash] or hashes.sha512,
|
|
||||||
rounds = tonumber(a.rounds or defaults.rounds) or 1
|
|
||||||
}, cipher)
|
|
||||||
end
|
|
||||||
|
|
||||||
function cipher:encrypt(d, k, s)
|
|
||||||
return aes:new(k, s, cip(self.size, self.mode), self.hash, self.rounds):encrypt(d)
|
|
||||||
end
|
|
||||||
|
|
||||||
function cipher:decrypt(d, k, s)
|
|
||||||
return aes:new(k, s, cip(self.size, self.mode), self.hash, self.rounds):decrypt(d)
|
|
||||||
end
|
|
||||||
|
|
||||||
return cipher
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
local ngx = ngx
|
|
||||||
local base64enc = ngx.encode_base64
|
|
||||||
local base64dec = ngx.decode_base64
|
|
||||||
|
|
||||||
local ENCODE_CHARS = {
|
|
||||||
["+"] = "-",
|
|
||||||
["/"] = "_",
|
|
||||||
["="] = "."
|
|
||||||
}
|
|
||||||
|
|
||||||
local DECODE_CHARS = {
|
|
||||||
["-"] = "+",
|
|
||||||
["_"] = "/",
|
|
||||||
["."] = "="
|
|
||||||
}
|
|
||||||
|
|
||||||
local base64 = {}
|
|
||||||
|
|
||||||
function base64.encode(value)
|
|
||||||
return (base64enc(value):gsub("[+/=]", ENCODE_CHARS))
|
|
||||||
end
|
|
||||||
|
|
||||||
function base64.decode(value)
|
|
||||||
return base64dec((value:gsub("[-_.]", DECODE_CHARS)))
|
|
||||||
end
|
|
||||||
|
|
||||||
return base64
|
|
||||||
@ -1,152 +0,0 @@
|
|||||||
local type = type
|
|
||||||
local pairs = pairs
|
|
||||||
local setmetatable = setmetatable
|
|
||||||
local mrandom = math.random
|
|
||||||
local sreverse = string.reverse
|
|
||||||
local sfind = string.find
|
|
||||||
local sgsub = string.gsub
|
|
||||||
local smatch = string.match
|
|
||||||
local table_insert = table.insert
|
|
||||||
local json = require("cjson")
|
|
||||||
|
|
||||||
local _M = {}
|
|
||||||
|
|
||||||
function _M.clone(o)
|
|
||||||
local lookup_table = {}
|
|
||||||
local function _copy(object)
|
|
||||||
if type(object) ~= "table" then
|
|
||||||
return object
|
|
||||||
elseif lookup_table[object] then
|
|
||||||
return lookup_table[object]
|
|
||||||
end
|
|
||||||
local new_object = {}
|
|
||||||
lookup_table[object] = new_object
|
|
||||||
for key, value in pairs(object) do
|
|
||||||
new_object[_copy(key)] = _copy(value)
|
|
||||||
end
|
|
||||||
return setmetatable(new_object, getmetatable(object))
|
|
||||||
end
|
|
||||||
return _copy(o)
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.clear_slash(s)
|
|
||||||
local r, _ = sgsub(s, "(/+)", "/")
|
|
||||||
return r
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.is_table_empty(t)
|
|
||||||
if t == nil or _G.next(t) == nil then
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.table_is_array(t)
|
|
||||||
if type(t) ~= "table" then return false end
|
|
||||||
local i = 0
|
|
||||||
for _ in pairs(t) do
|
|
||||||
i = i + 1
|
|
||||||
if t[i] == nil then return false end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.mixin(a, b)
|
|
||||||
if a and b then
|
|
||||||
for k, _ in pairs(b) do
|
|
||||||
a[k] = b[k]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return a
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.random()
|
|
||||||
return mrandom(0, 10000)
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.json_encode(data, empty_table_as_object)
|
|
||||||
local json_value
|
|
||||||
if json.encode_empty_table_as_object then
|
|
||||||
-- empty table encoded as array default
|
|
||||||
json.encode_empty_table_as_object(empty_table_as_object or false)
|
|
||||||
end
|
|
||||||
if require("ffi").os ~= "Windows" then
|
|
||||||
json.encode_sparse_array(true)
|
|
||||||
end
|
|
||||||
pcall(function(d) json_value = json.encode(d) end, data)
|
|
||||||
return json_value
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.json_decode(str)
|
|
||||||
local ok, data = pcall(json.decode, str)
|
|
||||||
if ok then
|
|
||||||
return data
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.start_with(str, substr)
|
|
||||||
if str == nil or substr == nil then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
if sfind(str, substr) ~= 1 then
|
|
||||||
return false
|
|
||||||
else
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.end_with(str, substr)
|
|
||||||
if str == nil or substr == nil then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
local str_reverse = sreverse(str)
|
|
||||||
local substr_reverse = sreverse(substr)
|
|
||||||
if sfind(str_reverse, substr_reverse) ~= 1 then
|
|
||||||
return false
|
|
||||||
else
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.is_match(uri, pattern)
|
|
||||||
if not pattern then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local ok = smatch(uri, pattern)
|
|
||||||
if ok then return true else return false end
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.trim_prefix_slash(s)
|
|
||||||
local str, _ = sgsub(s, "^(//*)", "")
|
|
||||||
return str
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.trim_suffix_slash(s)
|
|
||||||
local str, _ = sgsub(s, "(//*)$", "")
|
|
||||||
return str
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.trim_path_spaces(path)
|
|
||||||
if not path or path == "" then return path end
|
|
||||||
return sgsub(path, "( *)", "")
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.slim_path(path)
|
|
||||||
if not path or path == "" then return path end
|
|
||||||
return sgsub(path, "(//*)", "/")
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.split(str, delimiter)
|
|
||||||
if not str or str == "" then return {} end
|
|
||||||
if not delimiter or delimiter == "" then return { str } end
|
|
||||||
|
|
||||||
local result = {}
|
|
||||||
for match in (str .. delimiter):gmatch("(.-)" .. delimiter) do
|
|
||||||
table_insert(result, match)
|
|
||||||
end
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
return _M
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
local pairs = pairs
|
|
||||||
local type = type
|
|
||||||
local setmetatable = setmetatable
|
|
||||||
local tostring = tostring
|
|
||||||
local template = require "resty.template"
|
|
||||||
local template_new = template.new
|
|
||||||
|
|
||||||
local View = {}
|
|
||||||
|
|
||||||
function View:new(view_config)
|
|
||||||
local instance = {}
|
|
||||||
instance.view_enable = view_config.view_enable
|
|
||||||
if instance.view_enable then
|
|
||||||
if ngx.var.template_root then
|
|
||||||
ngx.var.template_root = view_config.views
|
|
||||||
else
|
|
||||||
ngx.log(ngx.ERR, "$template_root is not set in nginx.conf")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
instance.view_engine = view_config.view_engine
|
|
||||||
instance.view_ext = view_config.view_ext
|
|
||||||
instance.view_layout = view_config.view_layout
|
|
||||||
instance.views = view_config.views
|
|
||||||
|
|
||||||
setmetatable(instance, {__index = self})
|
|
||||||
return instance
|
|
||||||
end
|
|
||||||
|
|
||||||
function View:caching()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- to optimize
|
|
||||||
function View:render(view_file, data)
|
|
||||||
if not self.view_enable then
|
|
||||||
ngx.log(ngx.ERR, "view is not enabled. you may need `app:conf('view enable', true)`")
|
|
||||||
else
|
|
||||||
local view_file_name = view_file .. "." .. self.view_ext
|
|
||||||
local layout_file_name = self.view_layout .. "." .. self.view_ext
|
|
||||||
|
|
||||||
local t = template_new(view_file_name)
|
|
||||||
if self.view_layout ~= "" then
|
|
||||||
t = template_new(view_file_name,layout_file_name)
|
|
||||||
end
|
|
||||||
if data and type(data) == 'table' then
|
|
||||||
for k,v in pairs(data) do
|
|
||||||
t[k] = v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return tostring(t)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return View
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
local setmetatable = setmetatable
|
|
||||||
|
|
||||||
local _M = {}
|
|
||||||
|
|
||||||
function _M:new(create_app, Router, Group, Request, Response)
|
|
||||||
local instance = {}
|
|
||||||
instance.router = Router
|
|
||||||
instance.group = Group
|
|
||||||
instance.request = Request
|
|
||||||
instance.response = Response
|
|
||||||
instance.fn = create_app
|
|
||||||
instance.app = nil
|
|
||||||
|
|
||||||
setmetatable(instance, {
|
|
||||||
__index = self,
|
|
||||||
__call = self.create_app
|
|
||||||
})
|
|
||||||
|
|
||||||
return instance
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Generally, this should only be used by `lor` framework itself.
|
|
||||||
function _M:create_app(options)
|
|
||||||
self.app = self.fn(options)
|
|
||||||
return self.app
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M:Router(options)
|
|
||||||
return self.group:new(options)
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M:Request()
|
|
||||||
return self.request:new()
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M:Response()
|
|
||||||
return self.response:new()
|
|
||||||
end
|
|
||||||
|
|
||||||
return _M
|
|
||||||
@ -1 +0,0 @@
|
|||||||
return "0.3.4"
|
|
||||||
@ -1,192 +0,0 @@
|
|||||||
-- https://github.com/cloudflare/lua-resty-cookie/blob/master/lib/resty/cookie.lua
|
|
||||||
|
|
||||||
-- Copyright (C) 2013 Jiale Zhi (calio), Cloudflare Inc.
|
|
||||||
-- See RFC6265 http://tools.ietf.org/search/rfc6265
|
|
||||||
-- require "luacov"
|
|
||||||
|
|
||||||
local type = type
|
|
||||||
local byte = string.byte
|
|
||||||
local sub = string.sub
|
|
||||||
local format = string.format
|
|
||||||
local log = ngx.log
|
|
||||||
local ERR = ngx.ERR
|
|
||||||
local ngx_header = ngx.header
|
|
||||||
|
|
||||||
local EQUAL = byte("=")
|
|
||||||
local SEMICOLON = byte(";")
|
|
||||||
local SPACE = byte(" ")
|
|
||||||
local HTAB = byte("\t")
|
|
||||||
|
|
||||||
-- table.new(narr, nrec)
|
|
||||||
local ok, new_tab = pcall(require, "table.new")
|
|
||||||
if not ok then
|
|
||||||
new_tab = function () return {} end
|
|
||||||
end
|
|
||||||
|
|
||||||
local ok, clear_tab = pcall(require, "table.clear")
|
|
||||||
if not ok then
|
|
||||||
clear_tab = function(tab) for k, _ in pairs(tab) do tab[k] = nil end end
|
|
||||||
end
|
|
||||||
|
|
||||||
local _M = new_tab(0, 2)
|
|
||||||
|
|
||||||
_M._VERSION = '0.01'
|
|
||||||
|
|
||||||
|
|
||||||
local function get_cookie_table(text_cookie)
|
|
||||||
if type(text_cookie) ~= "string" then
|
|
||||||
log(ERR, format("expect text_cookie to be \"string\" but found %s",
|
|
||||||
type(text_cookie)))
|
|
||||||
return {}
|
|
||||||
end
|
|
||||||
|
|
||||||
local EXPECT_KEY = 1
|
|
||||||
local EXPECT_VALUE = 2
|
|
||||||
local EXPECT_SP = 3
|
|
||||||
|
|
||||||
local n = 0
|
|
||||||
local len = #text_cookie
|
|
||||||
|
|
||||||
for i=1, len do
|
|
||||||
if byte(text_cookie, i) == SEMICOLON then
|
|
||||||
n = n + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local cookie_table = new_tab(0, n + 1)
|
|
||||||
|
|
||||||
local state = EXPECT_SP
|
|
||||||
local i = 1
|
|
||||||
local j = 1
|
|
||||||
local key, value
|
|
||||||
|
|
||||||
while j <= len do
|
|
||||||
if state == EXPECT_KEY then
|
|
||||||
if byte(text_cookie, j) == EQUAL then
|
|
||||||
key = sub(text_cookie, i, j - 1)
|
|
||||||
state = EXPECT_VALUE
|
|
||||||
i = j + 1
|
|
||||||
end
|
|
||||||
elseif state == EXPECT_VALUE then
|
|
||||||
if byte(text_cookie, j) == SEMICOLON
|
|
||||||
or byte(text_cookie, j) == SPACE
|
|
||||||
or byte(text_cookie, j) == HTAB
|
|
||||||
then
|
|
||||||
value = sub(text_cookie, i, j - 1)
|
|
||||||
cookie_table[key] = value
|
|
||||||
|
|
||||||
key, value = nil, nil
|
|
||||||
state = EXPECT_SP
|
|
||||||
i = j + 1
|
|
||||||
end
|
|
||||||
elseif state == EXPECT_SP then
|
|
||||||
if byte(text_cookie, j) ~= SPACE
|
|
||||||
and byte(text_cookie, j) ~= HTAB
|
|
||||||
then
|
|
||||||
state = EXPECT_KEY
|
|
||||||
i = j
|
|
||||||
j = j - 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
j = j + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if key ~= nil and value == nil then
|
|
||||||
cookie_table[key] = sub(text_cookie, i)
|
|
||||||
end
|
|
||||||
|
|
||||||
return cookie_table
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.new(self)
|
|
||||||
local _cookie = ngx.var.http_cookie
|
|
||||||
--if not _cookie then
|
|
||||||
--return nil, "no cookie found in current request"
|
|
||||||
--end
|
|
||||||
return setmetatable({ _cookie = _cookie, set_cookie_table = new_tab(4, 0) },
|
|
||||||
{ __index = self })
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.get(self, key)
|
|
||||||
if not self._cookie then
|
|
||||||
return nil, "no cookie found in the current request"
|
|
||||||
end
|
|
||||||
if self.cookie_table == nil then
|
|
||||||
self.cookie_table = get_cookie_table(self._cookie)
|
|
||||||
end
|
|
||||||
|
|
||||||
return self.cookie_table[key]
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.get_all(self)
|
|
||||||
if not self._cookie then
|
|
||||||
return nil, "no cookie found in the current request"
|
|
||||||
end
|
|
||||||
|
|
||||||
if self.cookie_table == nil then
|
|
||||||
self.cookie_table = get_cookie_table(self._cookie)
|
|
||||||
end
|
|
||||||
|
|
||||||
return self.cookie_table
|
|
||||||
end
|
|
||||||
|
|
||||||
local function bake(cookie)
|
|
||||||
if not cookie.key or not cookie.value then
|
|
||||||
return nil, 'missing cookie field "key" or "value"'
|
|
||||||
end
|
|
||||||
|
|
||||||
if cookie["max-age"] then
|
|
||||||
cookie.max_age = cookie["max-age"]
|
|
||||||
end
|
|
||||||
local str = cookie.key .. "=" .. cookie.value
|
|
||||||
.. (cookie.expires and "; Expires=" .. cookie.expires or "")
|
|
||||||
.. (cookie.max_age and "; Max-Age=" .. cookie.max_age or "")
|
|
||||||
.. (cookie.domain and "; Domain=" .. cookie.domain or "")
|
|
||||||
.. (cookie.path and "; Path=" .. cookie.path or "")
|
|
||||||
.. (cookie.secure and "; Secure" or "")
|
|
||||||
.. (cookie.httponly and "; HttpOnly" or "")
|
|
||||||
.. (cookie.extension and "; " .. cookie.extension or "")
|
|
||||||
return str
|
|
||||||
end
|
|
||||||
|
|
||||||
function _M.set(self, cookie)
|
|
||||||
local cookie_str, err = bake(cookie)
|
|
||||||
if not cookie_str then
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
|
|
||||||
local set_cookie = ngx_header['Set-Cookie']
|
|
||||||
local set_cookie_type = type(set_cookie)
|
|
||||||
local t = self.set_cookie_table
|
|
||||||
clear_tab(t)
|
|
||||||
|
|
||||||
if set_cookie_type == "string" then
|
|
||||||
-- only one cookie has been setted
|
|
||||||
if set_cookie ~= cookie_str then
|
|
||||||
t[1] = set_cookie
|
|
||||||
t[2] = cookie_str
|
|
||||||
ngx_header['Set-Cookie'] = t
|
|
||||||
end
|
|
||||||
elseif set_cookie_type == "table" then
|
|
||||||
-- more than one cookies has been setted
|
|
||||||
local size = #set_cookie
|
|
||||||
|
|
||||||
-- we can not set cookie like ngx.header['Set-Cookie'][3] = val
|
|
||||||
-- so create a new table, copy all the values, and then set it back
|
|
||||||
for i=1, size do
|
|
||||||
t[i] = ngx_header['Set-Cookie'][i]
|
|
||||||
if t[i] == cookie_str then
|
|
||||||
-- new cookie is duplicated
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
t[size + 1] = cookie_str
|
|
||||||
ngx_header['Set-Cookie'] = t
|
|
||||||
else
|
|
||||||
-- no cookie has been setted
|
|
||||||
ngx_header['Set-Cookie'] = cookie_str
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
return _M
|
|
||||||
@ -1,478 +0,0 @@
|
|||||||
local setmetatable = setmetatable
|
|
||||||
local loadstring = loadstring
|
|
||||||
local loadchunk
|
|
||||||
local tostring = tostring
|
|
||||||
local setfenv = setfenv
|
|
||||||
local require = require
|
|
||||||
local capture
|
|
||||||
local concat = table.concat
|
|
||||||
local assert = assert
|
|
||||||
local prefix
|
|
||||||
local write = io.write
|
|
||||||
local pcall = pcall
|
|
||||||
local phase
|
|
||||||
local open = io.open
|
|
||||||
local load = load
|
|
||||||
local type = type
|
|
||||||
local dump = string.dump
|
|
||||||
local find = string.find
|
|
||||||
local gsub = string.gsub
|
|
||||||
local byte = string.byte
|
|
||||||
local null
|
|
||||||
local sub = string.sub
|
|
||||||
local ngx = ngx
|
|
||||||
local jit = jit
|
|
||||||
local var
|
|
||||||
|
|
||||||
local _VERSION = _VERSION
|
|
||||||
local _ENV = _ENV
|
|
||||||
local _G = _G
|
|
||||||
|
|
||||||
local HTML_ENTITIES = {
|
|
||||||
["&"] = "&",
|
|
||||||
["<"] = "<",
|
|
||||||
[">"] = ">",
|
|
||||||
['"'] = """,
|
|
||||||
["'"] = "'",
|
|
||||||
["/"] = "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
local CODE_ENTITIES = {
|
|
||||||
["{"] = "{",
|
|
||||||
["}"] = "}",
|
|
||||||
["&"] = "&",
|
|
||||||
["<"] = "<",
|
|
||||||
[">"] = ">",
|
|
||||||
['"'] = """,
|
|
||||||
["'"] = "'",
|
|
||||||
["/"] = "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
local VAR_PHASES
|
|
||||||
|
|
||||||
local ok, newtab = pcall(require, "table.new")
|
|
||||||
if not ok then newtab = function() return {} end end
|
|
||||||
|
|
||||||
local caching = true
|
|
||||||
local template = newtab(0, 12)
|
|
||||||
|
|
||||||
template._VERSION = "1.9"
|
|
||||||
template.cache = {}
|
|
||||||
|
|
||||||
local function enabled(val)
|
|
||||||
if val == nil then return true end
|
|
||||||
return val == true or (val == "1" or val == "true" or val == "on")
|
|
||||||
end
|
|
||||||
|
|
||||||
local function trim(s)
|
|
||||||
return gsub(gsub(s, "^%s+", ""), "%s+$", "")
|
|
||||||
end
|
|
||||||
|
|
||||||
local function rpos(view, s)
|
|
||||||
while s > 0 do
|
|
||||||
local c = sub(view, s, s)
|
|
||||||
if c == " " or c == "\t" or c == "\0" or c == "\x0B" then
|
|
||||||
s = s - 1
|
|
||||||
else
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return s
|
|
||||||
end
|
|
||||||
|
|
||||||
local function escaped(view, s)
|
|
||||||
if s > 1 and sub(view, s - 1, s - 1) == "\\" then
|
|
||||||
if s > 2 and sub(view, s - 2, s - 2) == "\\" then
|
|
||||||
return false, 1
|
|
||||||
else
|
|
||||||
return true, 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return false, 0
|
|
||||||
end
|
|
||||||
|
|
||||||
local function readfile(path)
|
|
||||||
local file = open(path, "rb")
|
|
||||||
if not file then return nil end
|
|
||||||
local content = file:read "*a"
|
|
||||||
file:close()
|
|
||||||
return content
|
|
||||||
end
|
|
||||||
|
|
||||||
local function loadlua(path)
|
|
||||||
return readfile(path) or path
|
|
||||||
end
|
|
||||||
|
|
||||||
local function loadngx(path)
|
|
||||||
local vars = VAR_PHASES[phase()]
|
|
||||||
local file, location = path, vars and var.template_location
|
|
||||||
if sub(file, 1) == "/" then file = sub(file, 2) end
|
|
||||||
if location and location ~= "" then
|
|
||||||
if sub(location, -1) == "/" then location = sub(location, 1, -2) end
|
|
||||||
local res = capture(concat{ location, '/', file})
|
|
||||||
if res.status == 200 then return res.body end
|
|
||||||
end
|
|
||||||
local root = vars and (var.template_root or var.document_root) or prefix
|
|
||||||
if sub(root, -1) == "/" then root = sub(root, 1, -2) end
|
|
||||||
return readfile(concat{ root, "/", file }) or path
|
|
||||||
end
|
|
||||||
|
|
||||||
do
|
|
||||||
if ngx then
|
|
||||||
VAR_PHASES = {
|
|
||||||
set = true,
|
|
||||||
rewrite = true,
|
|
||||||
access = true,
|
|
||||||
content = true,
|
|
||||||
header_filter = true,
|
|
||||||
body_filter = true,
|
|
||||||
log = true
|
|
||||||
}
|
|
||||||
template.print = ngx.print or write
|
|
||||||
template.load = loadngx
|
|
||||||
prefix, var, capture, null, phase = ngx.config.prefix(), ngx.var, ngx.location.capture, ngx.null, ngx.get_phase
|
|
||||||
if VAR_PHASES[phase()] then
|
|
||||||
caching = enabled(var.template_cache)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
template.print = write
|
|
||||||
template.load = loadlua
|
|
||||||
end
|
|
||||||
if _VERSION == "Lua 5.1" then
|
|
||||||
local context = { __index = function(t, k)
|
|
||||||
return t.context[k] or t.template[k] or _G[k]
|
|
||||||
end }
|
|
||||||
if jit then
|
|
||||||
loadchunk = function(view)
|
|
||||||
return assert(load(view, nil, nil, setmetatable({ template = template }, context)))
|
|
||||||
end
|
|
||||||
else
|
|
||||||
loadchunk = function(view)
|
|
||||||
local func = assert(loadstring(view))
|
|
||||||
setfenv(func, setmetatable({ template = template }, context))
|
|
||||||
return func
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local context = { __index = function(t, k)
|
|
||||||
return t.context[k] or t.template[k] or _ENV[k]
|
|
||||||
end }
|
|
||||||
loadchunk = function(view)
|
|
||||||
return assert(load(view, nil, nil, setmetatable({ template = template }, context)))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function template.caching(enable)
|
|
||||||
if enable ~= nil then caching = enable == true end
|
|
||||||
return caching
|
|
||||||
end
|
|
||||||
|
|
||||||
function template.output(s)
|
|
||||||
if s == nil or s == null then return "" end
|
|
||||||
if type(s) == "function" then return template.output(s()) end
|
|
||||||
return tostring(s)
|
|
||||||
end
|
|
||||||
|
|
||||||
function template.escape(s, c)
|
|
||||||
if type(s) == "string" then
|
|
||||||
if c then return gsub(s, "[}{\">/<'&]", CODE_ENTITIES) end
|
|
||||||
return gsub(s, "[\">/<'&]", HTML_ENTITIES)
|
|
||||||
end
|
|
||||||
return template.output(s)
|
|
||||||
end
|
|
||||||
|
|
||||||
function template.new(view, layout)
|
|
||||||
assert(view, "view was not provided for template.new(view, layout).")
|
|
||||||
local render, compile = template.render, template.compile
|
|
||||||
if layout then
|
|
||||||
if type(layout) == "table" then
|
|
||||||
return setmetatable({ render = function(self, context)
|
|
||||||
local context = context or self
|
|
||||||
context.blocks = context.blocks or {}
|
|
||||||
context.view = compile(view)(context)
|
|
||||||
layout.blocks = context.blocks or {}
|
|
||||||
layout.view = context.view or ""
|
|
||||||
return layout:render()
|
|
||||||
end }, { __tostring = function(self)
|
|
||||||
local context = self
|
|
||||||
context.blocks = context.blocks or {}
|
|
||||||
context.view = compile(view)(context)
|
|
||||||
layout.blocks = context.blocks or {}
|
|
||||||
layout.view = context.view
|
|
||||||
return tostring(layout)
|
|
||||||
end })
|
|
||||||
else
|
|
||||||
return setmetatable({ render = function(self, context)
|
|
||||||
local context = context or self
|
|
||||||
context.blocks = context.blocks or {}
|
|
||||||
context.view = compile(view)(context)
|
|
||||||
return render(layout, context)
|
|
||||||
end }, { __tostring = function(self)
|
|
||||||
local context = self
|
|
||||||
context.blocks = context.blocks or {}
|
|
||||||
context.view = compile(view)(context)
|
|
||||||
return compile(layout)(context)
|
|
||||||
end })
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return setmetatable({ render = function(self, context)
|
|
||||||
return render(view, context or self)
|
|
||||||
end }, { __tostring = function(self)
|
|
||||||
return compile(view)(self)
|
|
||||||
end })
|
|
||||||
end
|
|
||||||
|
|
||||||
function template.precompile(view, path, strip)
|
|
||||||
local chunk = dump(template.compile(view), strip ~= false)
|
|
||||||
if path then
|
|
||||||
local file = open(path, "wb")
|
|
||||||
file:write(chunk)
|
|
||||||
file:close()
|
|
||||||
end
|
|
||||||
return chunk
|
|
||||||
end
|
|
||||||
|
|
||||||
function template.compile(view, key, plain)
|
|
||||||
assert(view, "view was not provided for template.compile(view, key, plain).")
|
|
||||||
if key == "no-cache" then
|
|
||||||
return loadchunk(template.parse(view, plain)), false
|
|
||||||
end
|
|
||||||
key = key or view
|
|
||||||
local cache = template.cache
|
|
||||||
if cache[key] then return cache[key], true end
|
|
||||||
local func = loadchunk(template.parse(view, plain))
|
|
||||||
if caching then cache[key] = func end
|
|
||||||
return func, false
|
|
||||||
end
|
|
||||||
|
|
||||||
function template.parse(view, plain)
|
|
||||||
assert(view, "view was not provided for template.parse(view, plain).")
|
|
||||||
if not plain then
|
|
||||||
view = template.load(view)
|
|
||||||
if byte(view, 1, 1) == 27 then return view end
|
|
||||||
end
|
|
||||||
local j = 2
|
|
||||||
local c = {[[
|
|
||||||
context=... or {}
|
|
||||||
local function include(v, c) return template.compile(v)(c or context) end
|
|
||||||
local ___,blocks,layout={},blocks or {}
|
|
||||||
]] }
|
|
||||||
local i, s = 1, find(view, "{", 1, true)
|
|
||||||
while s do
|
|
||||||
local t, p = sub(view, s + 1, s + 1), s + 2
|
|
||||||
if t == "{" then
|
|
||||||
local e = find(view, "}}", p, true)
|
|
||||||
if e then
|
|
||||||
local z, w = escaped(view, s)
|
|
||||||
if i < s - w then
|
|
||||||
c[j] = "___[#___+1]=[=[\n"
|
|
||||||
c[j+1] = sub(view, i, s - 1 - w)
|
|
||||||
c[j+2] = "]=]\n"
|
|
||||||
j=j+3
|
|
||||||
end
|
|
||||||
if z then
|
|
||||||
i = s
|
|
||||||
else
|
|
||||||
c[j] = "___[#___+1]=template.escape("
|
|
||||||
c[j+1] = trim(sub(view, p, e - 1))
|
|
||||||
c[j+2] = ")\n"
|
|
||||||
j=j+3
|
|
||||||
s, i = e + 1, e + 2
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif t == "*" then
|
|
||||||
local e = find(view, "*}", p, true)
|
|
||||||
if e then
|
|
||||||
local z, w = escaped(view, s)
|
|
||||||
if i < s - w then
|
|
||||||
c[j] = "___[#___+1]=[=[\n"
|
|
||||||
c[j+1] = sub(view, i, s - 1 - w)
|
|
||||||
c[j+2] = "]=]\n"
|
|
||||||
j=j+3
|
|
||||||
end
|
|
||||||
if z then
|
|
||||||
i = s
|
|
||||||
else
|
|
||||||
c[j] = "___[#___+1]=template.output("
|
|
||||||
c[j+1] = trim(sub(view, p, e - 1))
|
|
||||||
c[j+2] = ")\n"
|
|
||||||
j=j+3
|
|
||||||
s, i = e + 1, e + 2
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif t == "%" then
|
|
||||||
local e = find(view, "%}", p, true)
|
|
||||||
if e then
|
|
||||||
local z, w = escaped(view, s)
|
|
||||||
if z then
|
|
||||||
if i < s - w then
|
|
||||||
c[j] = "___[#___+1]=[=[\n"
|
|
||||||
c[j+1] = sub(view, i, s - 1 - w)
|
|
||||||
c[j+2] = "]=]\n"
|
|
||||||
j=j+3
|
|
||||||
end
|
|
||||||
i = s
|
|
||||||
else
|
|
||||||
local n = e + 2
|
|
||||||
if sub(view, n, n) == "\n" then
|
|
||||||
n = n + 1
|
|
||||||
end
|
|
||||||
local r = rpos(view, s - 1)
|
|
||||||
if i <= r then
|
|
||||||
c[j] = "___[#___+1]=[=[\n"
|
|
||||||
c[j+1] = sub(view, i, r)
|
|
||||||
c[j+2] = "]=]\n"
|
|
||||||
j=j+3
|
|
||||||
end
|
|
||||||
c[j] = trim(sub(view, p, e - 1))
|
|
||||||
c[j+1] = "\n"
|
|
||||||
j=j+2
|
|
||||||
s, i = n - 1, n
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif t == "(" then
|
|
||||||
local e = find(view, ")}", p, true)
|
|
||||||
if e then
|
|
||||||
local z, w = escaped(view, s)
|
|
||||||
if i < s - w then
|
|
||||||
c[j] = "___[#___+1]=[=[\n"
|
|
||||||
c[j+1] = sub(view, i, s - 1 - w)
|
|
||||||
c[j+2] = "]=]\n"
|
|
||||||
j=j+3
|
|
||||||
end
|
|
||||||
if z then
|
|
||||||
i = s
|
|
||||||
else
|
|
||||||
local f = sub(view, p, e - 1)
|
|
||||||
local x = find(f, ",", 2, true)
|
|
||||||
if x then
|
|
||||||
c[j] = "___[#___+1]=include([=["
|
|
||||||
c[j+1] = trim(sub(f, 1, x - 1))
|
|
||||||
c[j+2] = "]=],"
|
|
||||||
c[j+3] = trim(sub(f, x + 1))
|
|
||||||
c[j+4] = ")\n"
|
|
||||||
j=j+5
|
|
||||||
else
|
|
||||||
c[j] = "___[#___+1]=include([=["
|
|
||||||
c[j+1] = trim(f)
|
|
||||||
c[j+2] = "]=])\n"
|
|
||||||
j=j+3
|
|
||||||
end
|
|
||||||
s, i = e + 1, e + 2
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif t == "[" then
|
|
||||||
local e = find(view, "]}", p, true)
|
|
||||||
if e then
|
|
||||||
local z, w = escaped(view, s)
|
|
||||||
if i < s - w then
|
|
||||||
c[j] = "___[#___+1]=[=[\n"
|
|
||||||
c[j+1] = sub(view, i, s - 1 - w)
|
|
||||||
c[j+2] = "]=]\n"
|
|
||||||
j=j+3
|
|
||||||
end
|
|
||||||
if z then
|
|
||||||
i = s
|
|
||||||
else
|
|
||||||
c[j] = "___[#___+1]=include("
|
|
||||||
c[j+1] = trim(sub(view, p, e - 1))
|
|
||||||
c[j+2] = ")\n"
|
|
||||||
j=j+3
|
|
||||||
s, i = e + 1, e + 2
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif t == "-" then
|
|
||||||
local e = find(view, "-}", p, true)
|
|
||||||
if e then
|
|
||||||
local x, y = find(view, sub(view, s, e + 1), e + 2, true)
|
|
||||||
if x then
|
|
||||||
local z, w = escaped(view, s)
|
|
||||||
if z then
|
|
||||||
if i < s - w then
|
|
||||||
c[j] = "___[#___+1]=[=[\n"
|
|
||||||
c[j+1] = sub(view, i, s - 1 - w)
|
|
||||||
c[j+2] = "]=]\n"
|
|
||||||
j=j+3
|
|
||||||
end
|
|
||||||
i = s
|
|
||||||
else
|
|
||||||
y = y + 1
|
|
||||||
x = x - 1
|
|
||||||
if sub(view, y, y) == "\n" then
|
|
||||||
y = y + 1
|
|
||||||
end
|
|
||||||
local b = trim(sub(view, p, e - 1))
|
|
||||||
if b == "verbatim" or b == "raw" then
|
|
||||||
if i < s - w then
|
|
||||||
c[j] = "___[#___+1]=[=[\n"
|
|
||||||
c[j+1] = sub(view, i, s - 1 - w)
|
|
||||||
c[j+2] = "]=]\n"
|
|
||||||
j=j+3
|
|
||||||
end
|
|
||||||
c[j] = "___[#___+1]=[=["
|
|
||||||
c[j+1] = sub(view, e + 2, x)
|
|
||||||
c[j+2] = "]=]\n"
|
|
||||||
j=j+3
|
|
||||||
else
|
|
||||||
if sub(view, x, x) == "\n" then
|
|
||||||
x = x - 1
|
|
||||||
end
|
|
||||||
local r = rpos(view, s - 1)
|
|
||||||
if i <= r then
|
|
||||||
c[j] = "___[#___+1]=[=[\n"
|
|
||||||
c[j+1] = sub(view, i, r)
|
|
||||||
c[j+2] = "]=]\n"
|
|
||||||
j=j+3
|
|
||||||
end
|
|
||||||
c[j] = 'blocks["'
|
|
||||||
c[j+1] = b
|
|
||||||
c[j+2] = '"]=include[=['
|
|
||||||
c[j+3] = sub(view, e + 2, x)
|
|
||||||
c[j+4] = "]=]\n"
|
|
||||||
j=j+5
|
|
||||||
end
|
|
||||||
s, i = y - 1, y
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif t == "#" then
|
|
||||||
local e = find(view, "#}", p, true)
|
|
||||||
if e then
|
|
||||||
local z, w = escaped(view, s)
|
|
||||||
if i < s - w then
|
|
||||||
c[j] = "___[#___+1]=[=[\n"
|
|
||||||
c[j+1] = sub(view, i, s - 1 - w)
|
|
||||||
c[j+2] = "]=]\n"
|
|
||||||
j=j+3
|
|
||||||
end
|
|
||||||
if z then
|
|
||||||
i = s
|
|
||||||
else
|
|
||||||
e = e + 2
|
|
||||||
if sub(view, e, e) == "\n" then
|
|
||||||
e = e + 1
|
|
||||||
end
|
|
||||||
s, i = e - 1, e
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
s = find(view, "{", s + 1, true)
|
|
||||||
end
|
|
||||||
s = sub(view, i)
|
|
||||||
if s and s ~= "" then
|
|
||||||
c[j] = "___[#___+1]=[=[\n"
|
|
||||||
c[j+1] = s
|
|
||||||
c[j+2] = "]=]\n"
|
|
||||||
j=j+3
|
|
||||||
end
|
|
||||||
c[j] = "return layout and include(layout,setmetatable({view=table.concat(___),blocks=blocks},{__index=context})) or table.concat(___)"
|
|
||||||
return concat(c)
|
|
||||||
end
|
|
||||||
|
|
||||||
function template.render(view, context, key, plain)
|
|
||||||
assert(view, "view was not provided for template.render(view, context, key, plain).")
|
|
||||||
return template.print(template.compile(view, key, plain)(context))
|
|
||||||
end
|
|
||||||
|
|
||||||
return template
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
local template = require "resty.template"
|
|
||||||
local setmetatable = setmetatable
|
|
||||||
local escape = template.escape
|
|
||||||
local concat = table.concat
|
|
||||||
local pairs = pairs
|
|
||||||
local type = type
|
|
||||||
|
|
||||||
local function tag(name, content, attr)
|
|
||||||
local r, a, content = {}, {}, content or attr
|
|
||||||
r[#r + 1] = "<"
|
|
||||||
r[#r + 1] = name
|
|
||||||
if attr then
|
|
||||||
for k, v in pairs(attr) do
|
|
||||||
if type(k) == "number" then
|
|
||||||
a[#a + 1] = escape(v)
|
|
||||||
else
|
|
||||||
a[#a + 1] = k .. '="' .. escape(v) .. '"'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if #a > 0 then
|
|
||||||
r[#r + 1] = " "
|
|
||||||
r[#r + 1] = concat(a, " ")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if type(content) == "string" then
|
|
||||||
r[#r + 1] = ">"
|
|
||||||
r[#r + 1] = escape(content)
|
|
||||||
r[#r + 1] = "</"
|
|
||||||
r[#r + 1] = name
|
|
||||||
r[#r + 1] = ">"
|
|
||||||
else
|
|
||||||
r[#r + 1] = " />"
|
|
||||||
end
|
|
||||||
return concat(r)
|
|
||||||
end
|
|
||||||
|
|
||||||
local html = { __index = function(_, name)
|
|
||||||
return function(attr)
|
|
||||||
if type(attr) == "table" then
|
|
||||||
return function(content)
|
|
||||||
return tag(name, content, attr)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
return tag(name, attr)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end }
|
|
||||||
|
|
||||||
template.html = setmetatable(html, html)
|
|
||||||
|
|
||||||
return template.html
|
|
||||||
@ -1,148 +0,0 @@
|
|||||||
local template = require "resty.template"
|
|
||||||
|
|
||||||
local ok, new_tab = pcall(require, "table.new")
|
|
||||||
if not ok then
|
|
||||||
new_tab = function() return {} end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function run(iterations)
|
|
||||||
local gc, total, print, parse, compile, iterations, clock, format = collectgarbage, 0, ngx and ngx.say or print, template.parse, template.compile, iterations or 1000, os.clock, string.format
|
|
||||||
local view = [[
|
|
||||||
<ul>
|
|
||||||
{% for _, v in ipairs(context) do %}
|
|
||||||
<li>{{v}}</li>
|
|
||||||
{% end %}
|
|
||||||
</ul>]]
|
|
||||||
|
|
||||||
print(format("Running %d iterations in each test", iterations))
|
|
||||||
|
|
||||||
gc()
|
|
||||||
gc()
|
|
||||||
|
|
||||||
local x = clock()
|
|
||||||
for _ = 1, iterations do
|
|
||||||
parse(view, true)
|
|
||||||
end
|
|
||||||
local z = clock() - x
|
|
||||||
print(format(" Parsing Time: %.6f", z))
|
|
||||||
total = total + z
|
|
||||||
|
|
||||||
gc()
|
|
||||||
gc()
|
|
||||||
|
|
||||||
x = clock()
|
|
||||||
for _ = 1, iterations do
|
|
||||||
compile(view, nil, true)
|
|
||||||
template.cache = {}
|
|
||||||
end
|
|
||||||
z = clock() - x
|
|
||||||
print(format("Compilation Time: %.6f (template)", z))
|
|
||||||
total = total + z
|
|
||||||
|
|
||||||
compile(view, nil, true)
|
|
||||||
|
|
||||||
gc()
|
|
||||||
gc()
|
|
||||||
|
|
||||||
x = clock()
|
|
||||||
for _ = 1, iterations do
|
|
||||||
compile(view, 1, true)
|
|
||||||
end
|
|
||||||
z = clock() - x
|
|
||||||
print(format("Compilation Time: %.6f (template, cached)", z))
|
|
||||||
total = total + z
|
|
||||||
|
|
||||||
local context = { "Emma", "James", "Nicholas", "Mary" }
|
|
||||||
|
|
||||||
template.cache = {}
|
|
||||||
|
|
||||||
gc()
|
|
||||||
gc()
|
|
||||||
|
|
||||||
x = clock()
|
|
||||||
for _ = 1, iterations do
|
|
||||||
compile(view, 1, true)(context)
|
|
||||||
template.cache = {}
|
|
||||||
end
|
|
||||||
z = clock() - x
|
|
||||||
print(format(" Execution Time: %.6f (same template)", z))
|
|
||||||
total = total + z
|
|
||||||
|
|
||||||
template.cache = {}
|
|
||||||
compile(view, 1, true)
|
|
||||||
|
|
||||||
gc()
|
|
||||||
gc()
|
|
||||||
|
|
||||||
x = clock()
|
|
||||||
for _ = 1, iterations do
|
|
||||||
compile(view, 1, true)(context)
|
|
||||||
end
|
|
||||||
z = clock() - x
|
|
||||||
print(format(" Execution Time: %.6f (same template, cached)", z))
|
|
||||||
total = total + z
|
|
||||||
|
|
||||||
template.cache = {}
|
|
||||||
|
|
||||||
local views = new_tab(iterations, 0)
|
|
||||||
for i = 1, iterations do
|
|
||||||
views[i] = "<h1>Iteration " .. i .. "</h1>\n" .. view
|
|
||||||
end
|
|
||||||
|
|
||||||
gc()
|
|
||||||
gc()
|
|
||||||
|
|
||||||
x = clock()
|
|
||||||
for i = 1, iterations do
|
|
||||||
compile(views[i], i, true)(context)
|
|
||||||
end
|
|
||||||
z = clock() - x
|
|
||||||
print(format(" Execution Time: %.6f (different template)", z))
|
|
||||||
total = total + z
|
|
||||||
|
|
||||||
gc()
|
|
||||||
gc()
|
|
||||||
|
|
||||||
x = clock()
|
|
||||||
for i = 1, iterations do
|
|
||||||
compile(views[i], i, true)(context)
|
|
||||||
end
|
|
||||||
z = clock() - x
|
|
||||||
print(format(" Execution Time: %.6f (different template, cached)", z))
|
|
||||||
total = total + z
|
|
||||||
|
|
||||||
local contexts = new_tab(iterations, 0)
|
|
||||||
|
|
||||||
for i = 1, iterations do
|
|
||||||
contexts[i] = { "Emma", "James", "Nicholas", "Mary" }
|
|
||||||
end
|
|
||||||
|
|
||||||
template.cache = {}
|
|
||||||
|
|
||||||
gc()
|
|
||||||
gc()
|
|
||||||
|
|
||||||
x = clock()
|
|
||||||
for i = 1, iterations do
|
|
||||||
compile(views[i], i, true)(contexts[i])
|
|
||||||
end
|
|
||||||
z = clock() - x
|
|
||||||
print(format(" Execution Time: %.6f (different template, different context)", z))
|
|
||||||
total = total + z
|
|
||||||
|
|
||||||
gc()
|
|
||||||
gc()
|
|
||||||
|
|
||||||
x = clock()
|
|
||||||
for i = 1, iterations do
|
|
||||||
compile(views[i], i, true)(contexts[i])
|
|
||||||
end
|
|
||||||
z = clock() - x
|
|
||||||
print(format(" Execution Time: %.6f (different template, different context, cached)", z))
|
|
||||||
total = total + z
|
|
||||||
print(format(" Total Time: %.6f", total))
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
run = run
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue