🔧 build: 调整 web 后端 库

develop
cloudfreexiao 5 years ago
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,59 +0,0 @@
return {
-- 白名单配置:不需要登录即可访问;除非要二次开发,否则不应更改
whitelist = {
"^/api/login$", -- login page
"^/api/sign_up$", -- sign up page
-- "^/api/user$",
"^/error/$" -- error page
},
-- 静态模板配置,保持默认不修改即可
view_config = {
engine = "tmpl",
ext = "html",
views = "./app/views"
},
-- 分页时每页条数配置
page_config = {
index_topic_page_size = 10, -- 首页每页文章数
},
-- ########################## 以下配置需要使用者自定义为本地需要的配置 ########################## --
-- 生成session的secret请一定要修改此值为一复杂的字符串用于加密session
session_secret = "3584827dfed45b40328acb6242bhngod",
-- 用于存储密码的盐,请一定要修改此值, 一旦使用不能修改,用户也可自行实现其他密码方案
pwd_secret = "salt_secret_for_password",
jwt_secret = "hjuhdk_jjdkdh763nnjhf", --jwt 私钥,用于加密
-- mysql配置
--ERROR 1045 (28000): Access denied for user 'root'@'192.168.1.25' (using password: YES)
--原因是 非本机不可以root 登录
mysql = {
timeout = 5000,
connect_config = {
host = "192.168.1.14",
port = 3306,
database = "fishadmin",
user = "fish",
password = "111111",
max_packet_size = 1024 * 1024
},
pool_config = {
max_idle_timeout = 20000, -- 20s
pool_size = 50 -- connection pool size
}
},
-- 上传文件配置,如上传的头像、文章中的图片等
upload_config = {
dir = "/opt/fishadmin/static", -- 文件目录修改此值时须同时修改nginx配置文件中的$static_files_path值
},
cors_whitelist = "http://192.168.1.35:9527", --请修改自己对应的 frontend url
}

@ -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,11 +0,0 @@
--- 中间件示例: 为每个请求注入一些通用的变量
local lor = require("lor.index")
return function()
return function(req, res, next)
-- res.locals是一个table, 可以在这里注入一些“全局”变量
-- 这个示例里注入app的名称和版本号 在渲染页面时即可使用
res.locals.app_name = "lor application"
res.locals.app_version = lor.version or ""
next()
end
end

@ -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,90 +0,0 @@
local string_find = string.find
local string_lower = string.lower
local lor = require("lor.index")
local router = require("app.router")
local app = lor()
local cors_header = require "cors_header"
local config = require("app.config.config")
local whitelist = config.whitelist
local view_config = config.view_config
local upload_config = config.upload_config
-- 模板配置
app:conf("view enable", true)
app:conf("view engine", view_config.engine)
app:conf("view ext", view_config.ext)
app:conf("views", view_config.views)
-- session和cookie支持如果不需要可注释以下配置
local mw_cookie = require("lor.lib.middleware.cookie")
local mw_session = require("lor.lib.middleware.session")
local mw_check_login = require("app.middleware.check_login")
-- local mw_uploader = require("app.middleware.uploader")
-- 自定义中间件1: 注入一些全局变量供模板渲染使用
local mw_inject_version = require("app.middleware.inject_app_info")
app:use(mw_cookie())
app:use(mw_session({
session_key = "__rilladmin_app__", -- the key injected in cookie
session_aes_key = "aes_&%$#@(*&Gjjd563hdngds35781fhxgh", -- should set by yourself
timeout = 3600 -- default session timeout is 3600 seconds
}))
app:use(mw_inject_version())
-- intercepter: login or not
app:use(mw_check_login(whitelist))
-- -- uploader
-- app:use(mw_uploader({
-- dir = upload_config.dir
-- }))
--自定义中间件2: 设置响应头
app:use(function(req, res, next)
cors_header(res)
next()
end)
router(app) -- 业务路由处理
-- 错误处理插件,可根据需要定义多个
app:erroruse(function(err, req, res, next)
-- ERROR("error: ", err)
cors_header(res)
local method = req.method and string_lower(req.method)
if method == "options" then
res:set_header("Access-Control-Max-Age", "1728000")
res:set_header("Content-Length", "0")
return
end
if req:is_found() ~= true then
if string_find(req.headers["Accept"], "application/json") then
res:status(404):json({
success = false,
msg = "404! sorry, not found."
})
else
res:status(404):send("404! sorry, not found. " .. (req.path or ""))
end
else
if string_find(req.headers["Accept"], "application/json") then
res:status(500):json({
success = false,
msg = "500! internal error, please check the log."
})
else
res:status(500):send("internal error, please check the log.")
end
end
end)
return app

@ -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,174 +0,0 @@
### v0.3.4 2017.08.30
- 修复默认session插件的`session_aes_secret`长度问题
- 此问题存在于OpenResty v1.11.2.5版本及可能之后的版本中
- lua-resty-string v0.10开始AES salt必须是[8个字符](https://github.com/openresty/lua-resty-string/commit/69df3dcc2230364a54761a0d5a65327c6a4e256a)
- 使用内置的session插件时`session_aes_secret`不再是必须配置
- 若不填则默认为`12345678`
- 若不足8个字符则以`0`补足
- 若超过8个字符则只使用前8个
### v0.3.3 2017.08.05
- 使用严格的路由节点id策略,避免潜在冲突
### v0.3.2 2017.06.10
- 关于内置session插件的更改
- 修复session过期时间bug
- 移除lua-resty-session依赖
- 内置session插件替换为基于cookie的简单实现
- 接口仍然保持与之前版本兼容
- 关于session处理仍然建议根据具体业务需求和安全考量自行实现
- 支持URI中含有字符'-'
### v0.3.1 2017.04.16
- 支持路由中包含`~`字符from [@XadillaX](https://github.com/XadillaX))
- 支持组路由(group router)的多级路由写法
- 支持组路由下直接挂载中间件(see [issues#40](https://github.com/sumory/lor/issues/40))
### v0.3.0 2017.02.11
此版本为性能优化和内部实现重构版本API使用上保持与之前版本兼容详细描述如下
**特性**
- 中间件middlewares重构支持任意级别、多种方式挂载中间件这些中间件包括
- 预处理中间件(use)
- 错误处理中间件(erroruse)
- 业务处理中间件(get/post/put/delete...)
- 提高路由性能
- 路由匹配次数不再随路由数增多而正比例增长
- 全面支持正则路由和通配符路由
- use、get/put/delete/post等API优化如支持数组参数、支持单独挂载中间件等改进
- 路由匹配更加灵活: 优先匹配精确路由,其次再匹配正则路由或通配符路由
**Break Changes**
与之前版本相比break changes主要有以下几点(基本都是一些比较少用到的特性)
- 路由执行顺序不再与路由定义顺序相关, 如错误路由不用必须定义在最下方
- 如果一个请求最终匹配不到已定义的任何路由,则不会执行任何中间件代码(之前的版本会执行,这浪费了一些性能)
### v0.2.6 2016.11.26
- 升级内部集成的session中间件
- lua-resty-session升级到2.13版本
- 添加一个session过期参数timeout,默认为3600秒
- 添加一个refresh_cookie参数用于控制否在有新请求时刷新session和cookie过期时间默认“是”
- 更新`lord new`项目模板
- 缓存`app`对象,提高性能
- 调整CRUD示例, 详细请参看脚手架代码中的app/routes/user.lua
- 删除默认响应头X-Powered-By
### v0.2.4 2016.11.16
- 支持"application/json"类型请求
### v0.2.2 2016.10.15
- 支持opm 可通过`opm install sumory/lor`安装
- 注意opm暂不支持命令安装 所以这种方式无法安装`lord`命令
- 若仍想使用`lord`命令,建议使用`sh install.sh`方式安装
### v0.1.6 2016.10.14
- `lord`工具改为使用resty-cli实现不再依赖luajit
### v0.1.5 2016.10.01
- Path URI支持"."
- 使用xpcall替换pcall以记录更多出错日志
- 更新了测试用例
### v0.1.4 2016.07.30
- 删除一些无用代码和引用
- 升级测试依赖库
- 修改文档和注释
- 修改一些小bug
### v0.1.0 2016.03.15
- 增加一配置项是否启用模板功能app:conf("view enable", true), 默认为关闭
- view.lua中ngx.var.template_root存在性判断
- 增加docker支持
- 命令`lord --path`变更为`lord path`用于查看当前lor的安装路径
- 官网文档更新[http://lor.sumory.com](http://lor.sumory.com)
### v0.0.9 2016.03.02
- 使用install.sh安装lor时如果有指定安装目录则在指定的目录后面拼上"lor",避免文件误删的问题
- TODO: debug时列出整个路由表供参考
### v0.0.8 2016.02.26
- 支持multipart/form文件上传
- 修复了一个group router被多次app:use时出现404的bug
- 支持Response:json(data, flag)方法传入第二个bool类型参数flag指明序列化json时默认的空table是否编码为{}
- true 作为{}处理
- false 作为[]处理
- 不传入第二个参数则当作[]处理
### v0.0.7 2016.02.02
- 统一代码风格
- 优化部分代码比如使用ngx.re代替string对应方法、尽量使用local等
- Break API: req:isFound() -> req:is_found()
- Fix bug: 修复了在lua_code_cache on时的一些404问题
### v0.0.6 2016.01.30
- 修改了lor的默认安装路径到/usr/local/lor
- 命令行工具`lord`生成的项目模板更改
- 加入了nginx.conf配置方便之后维护自定义的nginx配置
- 加入start/stop/restart脚本方便之后项目的灵活部署
- 改善了路由pattern支持path variable含有"-"字符
- 增加了几个测试用例
- 修复了上一个请求的path variable会污染之后请求的bug
- 完善了res:redirect API
- 修复了请求体为空时解析的bug
- 给lor对象添加了版本号
- 添加了静态文件支持通过在nginx.conf里配置
- 编写了lor框架示例项目[lor-example](https://github.com/lorlabs/lor-example)
### v0.0.5 2016.01.28
- 完善了Documents和API文档详见[lor官网](http://lor.sumory.com)
- `lor new`命令生成的项目模板增加了一个middleware目录用于存放自定义插件
- 该目录的命名和位置都是非强制的,用户可按需要将自定义的插件放在任何地方
- 修改了lor new产生的项目模板增加了几个基本API的使用方式
### v0.0.4 2016.01.25
- 以默认插件的形式添加cookie支持(lua-resty-cookie)
- 以默认插件的形式添加session支持(lua-resty-session)
### v0.0.3 2016.01.23
- 修复上版本路由bug
- 添加模板支持lua-resty-template
- 完善了40余个常规测试用例
- 完善了命令行工具`lord`
- 常规API使用方法添加到默认项目模板
### v0.0.2 2016.01.21
- 完全重构v0.0.1路由
- Sinatra风格路由
- 主要API设计完成并实现
### v0.0.1 2016.01.15
- 原型设计和实验

@ -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.svg?branch=master](https://travis-ci.org/sumory/lor.svg?branch=master)](https://travis-ci.org/sumory/lor) [![GitHub release](https://img.shields.io/github/release/sumory/lor.svg)](https://github.com/sumory/lor/releases/latest) [![license](https://img.shields.io/github/license/sumory/lor.svg)](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,168 +0,0 @@
# Lor
[![https://travis-ci.org/sumory/lor.svg?branch=master](https://travis-ci.org/sumory/lor.svg?branch=master)](https://travis-ci.org/sumory/lor) [![GitHub release](https://img.shields.io/github/release/sumory/lor.svg)](https://github.com/sumory/lor/releases/latest) [![license](https://img.shields.io/github/license/sumory/lor.svg)](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>
**Lor**是一个运行在[OpenResty](http://openresty.org)上的基于Lua编写的Web框架.
- 路由采用[Sinatra](http://www.sinatrarb.com/)风格,结构清晰,易于编码和维护.
- API借鉴了[Express](http://expressjs.com)的思路和设计Node.js跨界开发者可以很快上手.
- 支持多种路由,路由可分组,路由匹配支持正则模式.
- 支持middleware机制可在任意路由上挂载中间件.
- 可作为HTTP API Server也可用于构建传统的Web应用.
### 文档
[http://lor.sumory.com](http://lor.sumory.com)
#### 示例项目
- 简单示例项目[lor-example](https://github.com/lorlabs/lor-example)
- 全站示例项目[openresty-china](https://github.com/sumory/openresty-china)
### 快速开始
**特别注意:** 在使用lor之前请首先确保OpenResty已安装并将`nginx`/`resty`命令配置到环境变量中。即在命令行直接输入`nginx -v`、`resty -v`能正确执行。
一个简单示例(更复杂的示例或项目模板请使用`lord`命令生成)
```lua
local lor = require("lor.index")
local app = lor()
app:get("/", function(req, res, next)
res:send("hello world!")
end)
-- 路由示例: 匹配/query/123?foo=bar
app:get("/query/:id", function(req, res, next)
local foo = req.query.foo
local path_id = req.params.id
res:json({
foo = foo,
id = path_id
})
end)
-- 错误处理插件,可根据需要定义多个
app:erroruse(function(err, req, res, next)
-- err是错误对象
ngx.log(ngx.ERR, err)
if req:is_found() ~= true then
return res:status(404):send("sorry, not found.")
end
res:status(500):send("server error")
end)
app:run()
```
### 安装
#### 1使用脚本安装(推荐)
使用Makefile安装lor框架:
```shell
git clone https://github.com/sumory/lor
cd lor
make install
```
默认`lor`的运行时lua文件会被安装到`/usr/local/lor`下, 命令行工具`lord`被安装在`/usr/local/bin`下。
如果希望自定义安装目录, 可参考如下命令自定义路径:
```shell
make install LOR_HOME=/path/to/lor LORD_BIN=/path/to/lord
```
执行**默认安装**后, lor的命令行工具`lord`就被安装在了`/usr/local/bin`下, 通过`which lord`查看:
```
$ which lord
/usr/local/bin/lord
```
`lor`的运行时包安装在了指定目录下, 可通过`lord path`命令查看。
#### 2使用opm安装
`opm`是OpenResty即将推出的官方包管理器从v0.2.2开始lor支持通过opm安装
```
opm install sumory/lor
```
注意: 目前opm不支持安装命令行工具所以此种方式安装后不能使用`lord`命令。
#### 3使用homebrew安装
除使用以上方式安装外, Mac用户还可使用homebrew来安装lor, 该方式由[@syhily](https://github.com/syhily)提供, 更详尽的使用方法请参见[这里](https://github.com/syhily/homebrew-lor)。
```
$ brew tap syhily/lor
$ brew install lor
```
至此, `lor`框架已经安装完毕,接下来使用`lord`命令行工具快速开始一个项目骨架.
### 使用
```
$ 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
```
执行`lord new lor_demo`则会生成一个名为lor_demo的示例项目然后执行
```
cd lor_demo
lord start
```
之后访问[http://localhost:8888/](http://localhost:8888/) 即可。
更多使用方法,请参考[use cases](./spec/cases)测试用例。
### Homebrew
[https://github.com/syhily/homebrew-lor](https://github.com/syhily/homebrew-lor)由[@syhily](https://github.com/syhily)维护。
### 贡献者
- [@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)
### 讨论交流
有一个QQ群用于在线讨论: 522410959
### 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,628 +0,0 @@
local sgmatch = string.gmatch
local utils = require 'bin.scaffold.utils'
local gitignore = [[
# 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
]]
local mime_types = [[
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;
}
]]
local index_view_tpl = [[
<!DOCTYPE html>
<html>
<style>
body {
font: 400 14px/1.6 "Open Sans",sans-serif;
color: #555;
}
.lor {
margin: 100px auto;
width: 800px;
}
.name {
display: block;
font: 100 4.5em "Helvetica Neue","Open Sans",sans-serif;
margin-bottom: 0.25em;
}
a {
color: #259DFF;
text-decoration: none;
}
.description {
position: relative;
top: -5px;
font: 100 3em "Helvetica Neue","Open Sans",sans-serif;
color: #AEAEAE;
}
</style>
<body>
<div class="lor">
<a href="#" class="name">
{{name}}
{% if locals.app_version then %}
<span class="version">{{locals.app_version}}</span>
{% end %}
</a>
<span class="description">{{desc}}</span>
</div>
</body>
</html>
]]
local user_info_view_tpl = [[
<!DOCTYPE html>
<html>
<style>
body {
font: 400 14px/1.6 "Open Sans",sans-serif;
color: #555;
}
.lor {
margin: 100px auto;
width: 800px;
}
.desc {
position: relative;
bottom: 15px;
font: 100 3em "Helvetica Neue","Open Sans",sans-serif;
color: #AEAEAE;
}
.id {
display: block;
font: 100 3em "Helvetica Neue","Open Sans", sans-serif;
}
.name {
display: block;
font: 100 3em "Helvetica Neue","Open Sans", sans-serif;
margin-bottom: 0.25em;
}
</style>
<body>
<div class="lor">
<span class="desc">{{desc}}</span><br>
<span class="id">{{id}}</span>
<span class="name">{{name}}</span>
</div>
</body>
</html>
]]
local main_tpl = [[
local app = require("app.server")
app:run()
]]
local server_tpl = [[
local string_find = string.find
local lor = require("lor.index")
local router = require("app.router")
local app = lor()
-- 模板配置
app:conf("view enable", true)
app:conf("view engine", "tmpl")
app:conf("view ext", "html")
app:conf("view layout", "")
app:conf("views", "./app/views")
-- session和cookie支持如果不需要可注释以下配置
local mw_cookie = require("lor.lib.middleware.cookie")
local mw_session = require("lor.lib.middleware.session")
app:use(mw_cookie())
app:use(mw_session({
session_key = "__app__", -- the key injected in cookie
session_aes_key = "aes_key_for_session", -- should set by yourself
timeout = 3600 -- default session timeout is 3600 seconds
}))
-- 自定义中间件1: 注入一些全局变量供模板渲染使用
local mw_inject_version = require("app.middleware.inject_app_info")
app:use(mw_inject_version())
-- 自定义中间件2: 设置响应头
app:use(function(req, res, next)
res:set_header("X-Powered-By", "Lor framework")
next()
end)
router(app) -- 业务路由处理
-- 错误处理插件,可根据需要定义多个
app:erroruse(function(err, req, res, next)
ngx.log(ngx.ERR, err)
if req:is_found() ~= true then
if string_find(req.headers["Accept"], "application/json") then
res:status(404):json({
success = false,
msg = "404! sorry, not found."
})
else
res:status(404):send("404! sorry, not found. " .. (req.path or ""))
end
else
if string_find(req.headers["Accept"], "application/json") then
res:status(500):json({
success = false,
msg = "500! internal error, please check the log."
})
else
res:status(500):send("internal error, please check the log.")
end
end
end)
return app
]]
local router_tpl = [[
-- 业务路由管理
local userRouter = require("app.routes.user")
return function(app)
-- simple router: hello world!
app:get("/hello", function(req, res, next)
res:send("hi! welcome to lor framework.")
end)
-- simple router: render html, visit "/" or "/?name=foo&desc=bar
app:get("/", function(req, res, next)
local data = {
name = req.query.name or "lor",
desc = req.query.desc or 'a framework of lua based on OpenResty'
}
res:render("index", data)
end)
-- group router: 对以`/user`开始的请求做过滤处理
app:use("/user", userRouter())
end
]]
local user_router_tpl = [[
local lor = require("lor.index")
local userRouter = lor:Router() -- 生成一个group router对象
-- 按id查找用户
-- e.g. /query/123
userRouter:get("/query/:id", function(req, res, next)
local query_id = tonumber(req.params.id) -- 从req.params取参数
if not query_id then
return res:render("user/info", {
desc = "Error to find user, path variable `id` should be a number. e.g. /user/query/123"
})
end
-- 渲染页面
res:render("user/info", {
id = query_id,
name = "user" .. query_id,
desc = "User Information"
})
end)
-- 删除用户
-- e.g. /delete?id=123
userRouter:delete("/delete", function(req, res, next)
local id = req.query.id -- 从req.query取参数
if not id then
return res:html("<h2 style='color:red'>Error: query param id is required.</h2>")
end
-- 返回html
res:html("<span>succeed to delete user</span><br/>user id is:<b style='color:red'>" .. id .. "</b>")
end)
-- 修改用户
-- e.g. /put/123?name=sumory
userRouter:put("/put/:id", function(req, res, next)
local id = req.params.id -- 从req.params取参数
local name = req.query.name -- 从req.query取参数
if not id or not name then
return res:send("error params: id and name are required.")
end
-- 返回文本格式的响应结果
res:send("succeed to modify user[" .. id .. "] with new name:" .. name)
end)
-- 创建用户
userRouter:post("/post", function(req, res, next)
local content_type = req.headers['Content-Type']
-- 如果请求类型为form表单或json请求体
if string.find(content_type, "application/x-www-form-urlencoded",1, true) or
string.find(content_type, "application/json",1, true) then
local id = req.body.id -- 从请求体取参数
local name = req.body.name -- 从请求体取参数
if not id or not name then
return res:json({
success = false,
msg = "error params: id and name are required."
})
end
res:json({-- 返回json格式的响应体
success = true,
data = {
id = id,
name = name,
desc = "succeed to create new user" .. id
}
})
else -- 不支持其他请求体
res:status(500):send("not supported request Content-Type[" .. content_type .. "]")
end
end)
return userRouter
]]
local middleware_tpl = [[
### (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
```
]]
local middleware_example_tpl = [[
--- 中间件示例: 为每个请求注入一些通用的变量
local lor = require("lor.index")
return function()
return function(req, res, next)
-- res.locals是一个table, 可以在这里注入一些“全局”变量
-- 这个示例里注入app的名称和版本号 在渲染页面时即可使用
res.locals.app_name = "lor application"
res.locals.app_version = lor.version or ""
next()
end
end
]]
local static_tpl = [[
### (static files directory)
nginx
```
location /static {
alias app/static;
}
```
]]
local ngx_conf_directory = [[
### nginx configuration directory
]]
local ngx_config = require 'bin.scaffold.nginx.config'
local ngx_conf_template = require 'bin.scaffold.nginx.conf_template'
local function 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
local ngx_conf_tpl = nginx_conf_content()
local start_sh = [[
#!/bin/sh
#####################################################################
# usage:
# sh start.sh -- start application @dev
# sh start.sh ${env} -- start application @${env}
# examples:
# sh start.sh prod -- use conf/nginx-prod.conf to start OpenResty
# sh start.sh -- use conf/nginx-dev.conf to start OpenResty
#####################################################################
if [ -n "$1" ];then
PROFILE="$1"
else
PROFILE=dev
fi
mkdir -p logs & mkdir -p tmp
echo "start lor application with profile: "${PROFILE}
nginx -p `pwd`/ -c conf/nginx-${PROFILE}.conf
]]
local stop_sh = [[
#!/bin/sh
#####################################################################
# usage:
# sh stop.sh -- stop application @dev
# sh stop.sh ${env} -- stop application @${env}
# examples:
# sh stop.sh prod -- use conf/nginx-prod.conf to stop OpenResty
# sh stop.sh -- use conf/nginx-dev.conf to stop OpenResty
#####################################################################
if [ -n "$1" ];then
PROFILE="$1"
else
PROFILE=dev
fi
mkdir -p logs & mkdir -p tmp
echo "stop lor application with profile: "${PROFILE}
nginx -s stop -p `pwd`/ -c conf/nginx-${PROFILE}.conf
]]
local reload_sh = [[
#!/bin/sh
#####################################################################
# usage:
# sh reload.sh -- reload application @dev
# sh reload.sh ${env} -- reload application @${env}
# examples:
# sh reload.sh prod -- use conf/nginx-prod.conf to reload OpenResty
# sh reload.sh -- use conf/nginx-dev.conf to reload OpenResty
#####################################################################
if [ -n "$1" ];then
PROFILE="$1"
else
PROFILE=dev
fi
mkdir -p logs & mkdir -p tmp
echo "reload lor application with profile: "${PROFILE}
nginx -s reload -p `pwd`/ -c conf/nginx-${PROFILE}.conf
]]
local Generator = {}
Generator.files = {
['.gitignore'] = gitignore,
['app/main.lua'] = main_tpl,
['app/server.lua'] = server_tpl,
['app/router.lua'] = router_tpl,
['app/routes/user.lua'] = user_router_tpl,
['app/views/index.html'] = index_view_tpl,
['app/views/user/info.html'] = user_info_view_tpl,
['app/middleware/README.md'] = middleware_tpl,
['app/middleware/inject_app_info.lua'] = middleware_example_tpl,
['app/static/README.md'] = static_tpl, -- static files directory,e.g. js/css/img
['conf/README.md'] = ngx_conf_directory, -- nginx config directory
['conf/nginx-dev.conf'] = ngx_conf_tpl, -- nginx config file
['conf/mime.types'] = mime_types, -- nginx mime
['start.sh'] = start_sh,
['stop.sh'] = stop_sh,
['reload.sh'] = reload_sh
}
function Generator.new(name)
print('Creating app: ' .. name .. '...')
Generator.create_files(name)
end
function Generator.create_files(parent)
for file_path, file_content in pairs(Generator.files) do
local full_file_path = parent .. '/' .. file_path
local full_file_dirname = utils.dirname(full_file_path)
os.execute('mkdir -p ' .. full_file_dirname .. ' > /dev/null')
local fw = io.open(full_file_path, 'w')
fw:write(file_content)
fw:close()
print(' created file ' .. full_file_path)
end
end
return Generator

@ -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,47 +0,0 @@
local _M = {}
function _M:get_ngx_conf_template()
return [[
# user www www;
pid tmp/{{LOR_ENV}}-nginx.pid;
# This number should be at maxium the number of CPU on the server
worker_processes 4;
events {
# Number of connections per worker
worker_connections 4096;
}
http {
sendfile on;
include ./mime.types;
{{LUA_PACKAGE_PATH}}
lua_code_cache on;
server {
# List port
listen {{PORT}};
# Access log
access_log logs/{{LOR_ENV}}-access.log;
# Error log
error_log logs/{{LOR_ENV}}-error.log;
# this variable is for view renderlua-resty-template)
set $template_root '';
location /static {
alias {{STATIC_FILE_DIRECTORY}}; #app/static;
}
# lor runtime
{{CONTENT_BY_LUA_FILE}}
}
}
]]
end
return _M

@ -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,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 = {
["&"] = "&amp;",
["<"] = "&lt;",
[">"] = "&gt;",
['"'] = "&quot;",
["'"] = "&#39;",
["/"] = "&#47;"
}
local CODE_ENTITIES = {
["{"] = "&#123;",
["}"] = "&#125;",
["&"] = "&amp;",
["<"] = "&lt;",
[">"] = "&gt;",
['"'] = "&quot;",
["'"] = "&#39;",
["/"] = "&#47;"
}
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…
Cancel
Save