mirror of
https://github.com/lxsang/silk.git
synced 2024-12-26 04:48:21 +01:00
Add unit tests and correct bugs detected by the tests
This commit is contained in:
parent
d4bb12c6b5
commit
7711526a7c
@ -22,16 +22,14 @@ silk_DATA = silkmvc/router.lua.tpl \
|
|||||||
silkmvc/Template.lua \
|
silkmvc/Template.lua \
|
||||||
silkmvc/Logger.lua \
|
silkmvc/Logger.lua \
|
||||||
silkmvc/BaseObject.lua \
|
silkmvc/BaseObject.lua \
|
||||||
silkmvc/DBHelper.lua \
|
|
||||||
silkmvc/api.lua
|
silkmvc/api.lua
|
||||||
|
|
||||||
coredir = $(libdir)/lua/silk/core
|
coredir = $(libdir)/lua/silk/core
|
||||||
core_DATA = silkmvc/core/OOP.lua \
|
core_DATA = silkmvc/core/OOP.lua \
|
||||||
silkmvc/core/std.lua \
|
silkmvc/core/std.lua \
|
||||||
silkmvc/core/extra_mime.lua \
|
silkmvc/core/mimes.lua \
|
||||||
silkmvc/core/utils.lua \
|
silkmvc/core/utils.lua \
|
||||||
silkmvc/core/api.lua \
|
silkmvc/core/hook.lua \
|
||||||
silkmvc/core/cif.lua \
|
|
||||||
silkmvc/core/sqlite.lua
|
silkmvc/core/sqlite.lua
|
||||||
|
|
||||||
# lua libraris & modules
|
# lua libraris & modules
|
||||||
|
@ -208,7 +208,7 @@ static int l_base64_decode(lua_State *L)
|
|||||||
// decode data to a byte array
|
// decode data to a byte array
|
||||||
lua_new_slice(L, len);
|
lua_new_slice(L, len);
|
||||||
slice_t *vec = NULL;
|
slice_t *vec = NULL;
|
||||||
vec = lua_check_slice(L, 2);
|
vec = lua_check_slice(L, -1);
|
||||||
len = Base64decode((char *)vec->data, s);
|
len = Base64decode((char *)vec->data, s);
|
||||||
vec->len = len;
|
vec->len = len;
|
||||||
// lua_pushstring(L,dst);
|
// lua_pushstring(L,dst);
|
||||||
|
@ -14,15 +14,43 @@
|
|||||||
#define SLICE "slice"
|
#define SLICE "slice"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
size_t magic;
|
||||||
size_t len;
|
size_t len;
|
||||||
uint8_t* data;
|
uint8_t* data;
|
||||||
} slice_t;
|
} slice_t;
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef LUA_SLICE_MAGIC
|
||||||
|
/**
|
||||||
|
* @brief Send data to the server via fastCGI protocol
|
||||||
|
* This function is defined by the luad fcgi server
|
||||||
|
*
|
||||||
|
* @param fd the socket fd
|
||||||
|
* @param id the request id
|
||||||
|
* @param ptr data pointer
|
||||||
|
* @param size data size
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
int fcgi_send_slice(int fd, uint16_t id, uint8_t* ptr, size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the magic number of the slice
|
||||||
|
* This value is defined by the luad fastCGI server
|
||||||
|
*
|
||||||
|
* @return size_t
|
||||||
|
*/
|
||||||
|
size_t lua_slice_magic();
|
||||||
|
#else
|
||||||
|
#define lua_slice_magic() (LUA_SLICE_MAGIC)
|
||||||
|
#define fcgi_send_slice(fd,id,ptr,size) (-1)
|
||||||
|
#endif
|
||||||
|
|
||||||
void lua_new_slice(lua_State*L, int n)
|
void lua_new_slice(lua_State*L, int n)
|
||||||
{
|
{
|
||||||
size_t nbytes = sizeof(slice_t) + n * 1U;
|
size_t nbytes = sizeof(slice_t) + n * 1U;
|
||||||
slice_t *a = (slice_t *)lua_newuserdata(L, nbytes);
|
slice_t *a = (slice_t *)lua_newuserdata(L, nbytes);
|
||||||
a->data = &((char *)a)[sizeof(slice_t)];
|
a->data = &((char *)a)[sizeof(slice_t)];
|
||||||
|
a->magic = lua_slice_magic();
|
||||||
luaL_getmetatable(L, SLICE);
|
luaL_getmetatable(L, SLICE);
|
||||||
lua_setmetatable(L, -2);
|
lua_setmetatable(L, -2);
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include "lua/lualib.h"
|
#include "lua/lualib.h"
|
||||||
|
static int l_slice_send_to(lua_State* L);
|
||||||
|
|
||||||
void lua_new_light_slice(lua_State *L, int n, char *ptr)
|
void lua_new_light_slice(lua_State *L, int n, char *ptr)
|
||||||
{
|
{
|
||||||
@ -6,6 +7,7 @@ void lua_new_light_slice(lua_State *L, int n, char *ptr)
|
|||||||
slice_t *a = (slice_t *)lua_newuserdata(L, nbytes);
|
slice_t *a = (slice_t *)lua_newuserdata(L, nbytes);
|
||||||
a->len = n;
|
a->len = n;
|
||||||
a->data = ptr;
|
a->data = ptr;
|
||||||
|
a->magic = lua_slice_magic();
|
||||||
luaL_getmetatable(L, SLICE);
|
luaL_getmetatable(L, SLICE);
|
||||||
lua_setmetatable(L, -2);
|
lua_setmetatable(L, -2);
|
||||||
}
|
}
|
||||||
@ -78,6 +80,14 @@ static int l_slice_write(lua_State *L)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int l_slice_ptr(lua_State *L)
|
||||||
|
{
|
||||||
|
slice_t *a = lua_check_slice(L, 1);
|
||||||
|
lua_pushnumber(L, (size_t)a);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static int l_slice_index(lua_State *L)
|
static int l_slice_index(lua_State *L)
|
||||||
{
|
{
|
||||||
if(lua_isnumber(L,2))
|
if(lua_isnumber(L,2))
|
||||||
@ -91,10 +101,18 @@ static int l_slice_index(lua_State *L)
|
|||||||
{
|
{
|
||||||
lua_pushcfunction(L, l_get_slice_size);
|
lua_pushcfunction(L, l_get_slice_size);
|
||||||
}
|
}
|
||||||
else if(strcmp(string, "write") == 0)
|
else if(strcmp(string, "fileout") == 0)
|
||||||
{
|
{
|
||||||
lua_pushcfunction(L, l_slice_write);
|
lua_pushcfunction(L, l_slice_write);
|
||||||
}
|
}
|
||||||
|
else if(strcmp(string,"out") == 0)
|
||||||
|
{
|
||||||
|
lua_pushcfunction(L, l_slice_send_to);
|
||||||
|
}
|
||||||
|
else if(strcmp(string,"ptr") == 0)
|
||||||
|
{
|
||||||
|
lua_pushcfunction(L, l_slice_ptr);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
lua_pushnil(L);
|
lua_pushnil(L);
|
||||||
@ -120,6 +138,16 @@ static int l_slice_to_string(lua_State *L)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int l_slice_send_to(lua_State* L)
|
||||||
|
{
|
||||||
|
slice_t *a = lua_check_slice(L, 1);
|
||||||
|
int fd = (int) luaL_checknumber(L, 2);
|
||||||
|
uint16_t id = (uint16_t) luaL_checknumber(L, 3);
|
||||||
|
|
||||||
|
lua_pushboolean(L, fcgi_send_slice(fd, id, a->data, a->len) == 0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
static const struct luaL_Reg slicemetalib[] = {
|
static const struct luaL_Reg slicemetalib[] = {
|
||||||
{"unew", l_new_lightslice},
|
{"unew", l_new_lightslice},
|
||||||
{"new", l_new_slice},
|
{"new", l_new_slice},
|
||||||
|
@ -83,6 +83,9 @@ static int l_db_query(lua_State *L)
|
|||||||
int cols = sqlite3_column_count(statement);
|
int cols = sqlite3_column_count(statement);
|
||||||
int result = 0;
|
int result = 0;
|
||||||
int cnt = 1;
|
int cnt = 1;
|
||||||
|
uint8_t* data = NULL;
|
||||||
|
size_t len = 0;
|
||||||
|
slice_t* vec = NULL;
|
||||||
// new table for data
|
// new table for data
|
||||||
lua_newtable(L);
|
lua_newtable(L);
|
||||||
while ((result = sqlite3_step(statement)) == SQLITE_ROW)
|
while ((result = sqlite3_step(statement)) == SQLITE_ROW)
|
||||||
@ -91,10 +94,37 @@ static int l_db_query(lua_State *L)
|
|||||||
lua_newtable(L);
|
lua_newtable(L);
|
||||||
for (int col = 0; col < cols; col++)
|
for (int col = 0; col < cols; col++)
|
||||||
{
|
{
|
||||||
const char *value = (const char *)sqlite3_column_text(statement, col);
|
|
||||||
const char *name = sqlite3_column_name(statement, col);
|
const char *name = sqlite3_column_name(statement, col);
|
||||||
lua_pushstring(L, name);
|
lua_pushstring(L, name);
|
||||||
lua_pushstring(L, value);
|
int type = sqlite3_column_type(statement,col);
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case SQLITE_INTEGER:
|
||||||
|
lua_pushnumber(L, sqlite3_column_int64(statement,col));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SQLITE_FLOAT:
|
||||||
|
lua_pushnumber(L, sqlite3_column_double(statement,col));
|
||||||
|
break;
|
||||||
|
case SQLITE_BLOB:
|
||||||
|
data = (uint8_t*)sqlite3_column_blob(statement, col);
|
||||||
|
len = sqlite3_column_bytes(statement, col);
|
||||||
|
if(len > 0)
|
||||||
|
{
|
||||||
|
lua_new_slice(L, len);
|
||||||
|
vec = lua_check_slice(L, -1);
|
||||||
|
(void)memcpy(vec->data, data, len);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lua_pushnil(L);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
lua_pushstring(L, (const char *)sqlite3_column_text(statement, col));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
lua_settable(L, -3);
|
lua_settable(L, -3);
|
||||||
}
|
}
|
||||||
lua_settable(L, -3);
|
lua_settable(L, -3);
|
||||||
|
@ -41,11 +41,12 @@ function BaseController:switchLayout(name)
|
|||||||
if self.main then
|
if self.main then
|
||||||
self.registry.layout = name
|
self.registry.layout = name
|
||||||
else
|
else
|
||||||
self:log("Cannot switch layout since the controller "..self.class.." is not the main controller")
|
self:warn("Cannot switch layout since the controller "..self.class.." is not the main controller")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function BaseController:setSession(key, value)
|
function BaseController:setSession(key, value)
|
||||||
|
|
||||||
SESSION[key] = value
|
SESSION[key] = value
|
||||||
end
|
end
|
||||||
function BaseController:getSession(key)
|
function BaseController:getSession(key)
|
||||||
|
@ -30,9 +30,7 @@ function BaseModel:find(cond)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function BaseModel:get(id)
|
function BaseModel:get(id)
|
||||||
local data, order = self:find({exp = {["="] = {id = id}}})
|
return self.db:get(self.name, id)
|
||||||
if not data or #order == 0 then return false end
|
|
||||||
return data[1]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function BaseModel:findAll()
|
function BaseModel:findAll()
|
||||||
@ -46,6 +44,7 @@ function BaseModel:query(sql)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function BaseModel:select(sel, sql_cnd)
|
function BaseModel:select(sel, sql_cnd)
|
||||||
if self.db then return self.db:select(self.name, sel, sql_cnd) end
|
local sql = string.format("SELECT %s FROM %s WHERE %s;", sel, self.name, sql_cnd)
|
||||||
|
if self.db then return self.db:query(sql) end
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
@ -4,31 +4,34 @@ function BaseObject:subclass(name, args)
|
|||||||
_G[name].class = name
|
_G[name].class = name
|
||||||
end
|
end
|
||||||
|
|
||||||
function BaseObject:log(msg, level)
|
function BaseObject:log(level,msg,...)
|
||||||
level = level or "INFO"
|
|
||||||
if self.registry.logger then
|
if self.registry.logger then
|
||||||
self.registry.logger:log(msg,level)
|
self.registry.logger:log(level, msg,...)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function BaseObject:debug(msg)
|
function BaseObject:debug(msg,...)
|
||||||
self:log(msg, "DEBUG")
|
self:log(Logger.DEBUG, msg,...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function BaseObject:info(msg,...)
|
||||||
|
self:log(Logger.INFO, msg,...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function BaseObject:warn(msg,...)
|
||||||
|
self:log(Logger.WARN, msg,...)
|
||||||
end
|
end
|
||||||
|
|
||||||
function BaseObject:print()
|
function BaseObject:print()
|
||||||
print(self.class)
|
self:debug(self.class)
|
||||||
end
|
end
|
||||||
|
|
||||||
function BaseObject:error(msg, trace)
|
function BaseObject:error(msg,...)
|
||||||
html()
|
html()
|
||||||
--local line = debug.getinfo(1).currentline
|
--local line = debug.getinfo(1).currentline
|
||||||
echo(msg)
|
local emsg = string.format(msg or "ERROR",...)
|
||||||
self:log(msg,"ERROR")
|
echo(emsg)
|
||||||
if trace then
|
self:log(Logger.ERROR, msg,...)
|
||||||
debug.traceback=nil
|
error(emsg)
|
||||||
error(msg)
|
|
||||||
else
|
|
||||||
error(msg)
|
|
||||||
end
|
|
||||||
return false
|
return false
|
||||||
end
|
end
|
@ -1,144 +0,0 @@
|
|||||||
sqlite = modules.sqlite()
|
|
||||||
|
|
||||||
if sqlite == nil then return 0 end
|
|
||||||
-- create class
|
|
||||||
BaseObject:subclass("DBHelper", {db = {}})
|
|
||||||
|
|
||||||
function DBHelper:createTable(tbl, m)
|
|
||||||
if self:available(tbl) then return true end
|
|
||||||
local sql = "CREATE TABLE " .. tbl .. "(id INTEGER PRIMARY KEY"
|
|
||||||
for k, v in pairs(m) do
|
|
||||||
if k ~= "id" then sql = sql .. "," .. k .. " " .. v end
|
|
||||||
end
|
|
||||||
sql = sql .. ");"
|
|
||||||
return sqlite.query(self.db, sql) == 1
|
|
||||||
end
|
|
||||||
|
|
||||||
function DBHelper:insert(tbl, m)
|
|
||||||
local keys = {}
|
|
||||||
local values = {}
|
|
||||||
for k, v in pairs(m) do
|
|
||||||
if k ~= "id" then
|
|
||||||
table.insert(keys, k)
|
|
||||||
if type(v) == "number" then
|
|
||||||
table.insert(values, v)
|
|
||||||
else
|
|
||||||
local t = "\"" .. v:gsub('"', '""') .. "\""
|
|
||||||
table.insert(values, t)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local sql = "INSERT INTO " .. tbl .. " (" .. table.concat(keys, ',') ..
|
|
||||||
') VALUES ('
|
|
||||||
sql = sql .. table.concat(values, ',') .. ');'
|
|
||||||
return sqlite.query(self.db, sql) == 1
|
|
||||||
end
|
|
||||||
|
|
||||||
function DBHelper:get(tbl, id)
|
|
||||||
return sqlite.select(self.db, tbl, "*", "id=" .. id)[1]
|
|
||||||
end
|
|
||||||
|
|
||||||
function DBHelper:getAll(tbl)
|
|
||||||
local data = sqlite.select(self.db, tbl, "*", "1=1")
|
|
||||||
if data == nil then return nil end
|
|
||||||
local a = {}
|
|
||||||
for n in pairs(data) do table.insert(a, n) end
|
|
||||||
table.sort(a)
|
|
||||||
return data, a
|
|
||||||
end
|
|
||||||
|
|
||||||
function DBHelper:find(tbl, cond)
|
|
||||||
local cnd = "1=1"
|
|
||||||
local sel = "*"
|
|
||||||
if cond.exp then cnd = self:gencond(cond.exp) end
|
|
||||||
if cond.order then
|
|
||||||
cnd = cnd .. " ORDER BY "
|
|
||||||
local l = {}
|
|
||||||
local i = 1
|
|
||||||
for k, v in pairs(cond.order) do
|
|
||||||
l[i] = k .. " " .. v
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
cnd = cnd .. table.concat(l, ",")
|
|
||||||
end
|
|
||||||
if cond.limit then cnd = cnd .. " LIMIT " .. cond.limit end
|
|
||||||
if cond.fields then
|
|
||||||
sel = table.concat(cond.fields, ",")
|
|
||||||
-- print(sel)
|
|
||||||
end
|
|
||||||
local data = sqlite.select(self.db, tbl, sel, cnd)
|
|
||||||
if data == nil then return nil end
|
|
||||||
local a = {}
|
|
||||||
for n in pairs(data) do table.insert(a, n) end
|
|
||||||
table.sort(a)
|
|
||||||
return data, a
|
|
||||||
end
|
|
||||||
|
|
||||||
function DBHelper:select(tbl, sel, cnd)
|
|
||||||
local data = sqlite.select(self.db, tbl, sel, cnd)
|
|
||||||
if data == nil then return nil end
|
|
||||||
local a = {}
|
|
||||||
for n in pairs(data) do table.insert(a, n) end
|
|
||||||
table.sort(a)
|
|
||||||
return data, a
|
|
||||||
end
|
|
||||||
|
|
||||||
function DBHelper:query(sql) return sqlite.query(self.db, sql) == 1 end
|
|
||||||
|
|
||||||
function DBHelper:update(tbl, m)
|
|
||||||
local id = m['id']
|
|
||||||
if id ~= nil then
|
|
||||||
local lst = {}
|
|
||||||
for k, v in pairs(m) do
|
|
||||||
if (type(v) == "number") then
|
|
||||||
table.insert(lst, k .. "=" .. v)
|
|
||||||
else
|
|
||||||
table.insert(lst, k .. "=\"" .. v:gsub('"', '""') .. "\"")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local sql = "UPDATE " .. tbl .. " SET " .. table.concat(lst, ",") ..
|
|
||||||
" WHERE id=" .. id .. ";"
|
|
||||||
return sqlite.query(self.db, sql) == 1
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function DBHelper:available(tbl) return sqlite.hasTable(self.db, tbl) == 1 end
|
|
||||||
function DBHelper:deleteByID(tbl, id)
|
|
||||||
local sql = "DELETE FROM " .. tbl .. " WHERE id=" .. id .. ";"
|
|
||||||
return sqlite.query(self.db, sql) == 1
|
|
||||||
end
|
|
||||||
function DBHelper:gencond(o)
|
|
||||||
for k, v in pairs(o) do
|
|
||||||
if k == "and" or k == "or" then
|
|
||||||
local cnd = {}
|
|
||||||
local i = 1
|
|
||||||
for k1, v1 in pairs(v) do
|
|
||||||
cnd[i] = self:gencond(v1)
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
return " (" .. table.concat(cnd, " " .. k .. " ") .. ") "
|
|
||||||
else
|
|
||||||
for k1, v1 in pairs(v) do
|
|
||||||
local t = type(v1)
|
|
||||||
if (t == "string") then
|
|
||||||
return
|
|
||||||
" (" .. k1 .. " " .. k .. ' "' .. v1:gsub('"', '""') ..
|
|
||||||
'") '
|
|
||||||
end
|
|
||||||
return " (" .. k1 .. " " .. k .. " " .. v1 .. ") "
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
function DBHelper:delete(tbl, cond)
|
|
||||||
local sql = "DELETE FROM " .. tbl .. " WHERE " .. self:gencond(cond) .. ";"
|
|
||||||
return sqlite.query(self.db, sql) == 1
|
|
||||||
end
|
|
||||||
|
|
||||||
function DBHelper:lastInsertID() return sqlite.lastInsertID(self.db) end
|
|
||||||
|
|
||||||
function DBHelper:close() if self.db then sqlite.dbclose(self.db) end end
|
|
||||||
function DBHelper:open()
|
|
||||||
if self.db ~= nil then self.db = sqlite.getdb(self.db) end
|
|
||||||
end
|
|
@ -1,30 +1,43 @@
|
|||||||
Logger = Object:extends{levels = {}}
|
Logger = Object:extends{}
|
||||||
|
|
||||||
|
Logger.ERROR = 1
|
||||||
|
Logger.WARN = 2
|
||||||
|
Logger.INFO = 3
|
||||||
|
Logger.DEBUG = 4
|
||||||
|
Logger.handles = {
|
||||||
|
[1] = LOG_ERROR,
|
||||||
|
[2] = LOG_WARN,
|
||||||
|
[3] = LOG_INFO,
|
||||||
|
[4] = LOG_DEBUG
|
||||||
|
}
|
||||||
|
|
||||||
function Logger:initialize()
|
function Logger:initialize()
|
||||||
end
|
if not self.level then
|
||||||
|
self.level = Logger.INFO
|
||||||
function Logger:log(msg,level)
|
|
||||||
if self.levels[level] and ulib.exists(LOG_ROOT) then
|
|
||||||
local path = LOG_ROOT..DIR_SEP..level..'.txt'
|
|
||||||
local f = io.open(path, 'a')
|
|
||||||
local text = '['..level.."]: "..msg
|
|
||||||
if f then
|
|
||||||
f:write(text..'\n')
|
|
||||||
f:close()
|
|
||||||
end
|
|
||||||
print(text)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Logger:info(msg)
|
function Logger:log(verb,msg,...)
|
||||||
self:log(msg, "INFO")
|
local level = verb
|
||||||
|
if level > self.level then return end
|
||||||
|
if level > Logger.DEBUG then
|
||||||
|
level = Logger.DEBUG
|
||||||
|
end
|
||||||
|
Logger.handles[level](msg,...)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Logger:debug(msg)
|
function Logger:info(msg,...)
|
||||||
self:log(msg, "DEBUG")
|
self:log(Logger.INFO, msg,...)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Logger:debug(msg,...)
|
||||||
function Logger:error(msg)
|
self:log(Logger.DEBUG, msg,...)
|
||||||
self:log(msg, "ERROR")
|
end
|
||||||
|
|
||||||
|
function Logger:error(msg,...)
|
||||||
|
self:log(Logger.ERROR, msg,...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Logger:warn(msg,...)
|
||||||
|
self:log(Logger.WARN, msg,...)
|
||||||
end
|
end
|
@ -7,6 +7,7 @@ end
|
|||||||
function Router:initialize()
|
function Router:initialize()
|
||||||
self.routes = {}
|
self.routes = {}
|
||||||
self.remaps = {}
|
self.remaps = {}
|
||||||
|
self.path = CONTROLLER_ROOT
|
||||||
end
|
end
|
||||||
|
|
||||||
--function Router:setArgs(args)
|
--function Router:setArgs(args)
|
||||||
@ -25,7 +26,7 @@ function Router:infer(url)
|
|||||||
-- if user dont provide the url, try to infer it
|
-- if user dont provide the url, try to infer it
|
||||||
-- from the REQUEST
|
-- from the REQUEST
|
||||||
url = url or REQUEST.r or ""
|
url = url or REQUEST.r or ""
|
||||||
url = std.trim(url, "/")
|
url = ulib.trim(url, "/")
|
||||||
local args = explode(url, "/")
|
local args = explode(url, "/")
|
||||||
local data = {
|
local data = {
|
||||||
name = "index",
|
name = "index",
|
||||||
@ -79,7 +80,7 @@ function Router:infer(url)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
self:log("Controller: " .. data.controller.class .. ", action: "..data.action..", args: ".. JSON.encode(data.args))
|
self:info("Controller: " .. data.controller.class .. ", action: "..data.action..", args: ".. JSON.encode(data.args))
|
||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -90,7 +91,7 @@ function Router:delegate()
|
|||||||
data.controller.main = true
|
data.controller.main = true
|
||||||
views.__main__ = self:call(data)
|
views.__main__ = self:call(data)
|
||||||
if not views.__main__ then
|
if not views.__main__ then
|
||||||
--self:error("No view available for this action")
|
self:info("No view available for action: %s:%s", data.controller.class, data.action)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
-- get all visible routes
|
-- get all visible routes
|
||||||
@ -106,8 +107,8 @@ function Router:delegate()
|
|||||||
table.insert( view_args, k )
|
table.insert( view_args, k )
|
||||||
table.insert( view_argv, v )
|
table.insert( view_argv, v )
|
||||||
end
|
end
|
||||||
|
local script_path = VIEW_ROOT .. DIR_SEP .. self.registry.layout .. DIR_SEP .. "layout.ls"
|
||||||
local fn, e = loadscript(VIEW_ROOT .. DIR_SEP .. self.registry.layout .. DIR_SEP .. "layout.ls", view_args)
|
local fn, e = loadscript(script_path, view_args)
|
||||||
html()
|
html()
|
||||||
if fn then
|
if fn then
|
||||||
local r, o = pcall(fn, table.unpack(view_argv))
|
local r, o = pcall(fn, table.unpack(view_argv))
|
||||||
@ -116,7 +117,7 @@ function Router:delegate()
|
|||||||
end
|
end
|
||||||
else
|
else
|
||||||
e = e or ""
|
e = e or ""
|
||||||
self:error("The index page is not found for layout: " .. self.registry.layout..": "..e)
|
self:error("The index page is not found for layout (%s: %s): %s" ,self.registry.layout, script_path,e)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -125,9 +126,8 @@ function Router:dependencies(url)
|
|||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
local list = {}
|
local list = {}
|
||||||
--self:log("comparing "..url)
|
|
||||||
for k, v in pairs(self.routes[self.registry.layout]) do
|
for k, v in pairs(self.routes[self.registry.layout]) do
|
||||||
v.url = std.trim(v.url, "/")
|
v.url = ulib.trim(v.url, "/")
|
||||||
if v.visibility == "ALL" then
|
if v.visibility == "ALL" then
|
||||||
list[k] = v.url
|
list[k] = v.url
|
||||||
elseif v.visibility.routes then
|
elseif v.visibility.routes then
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
require("OOP")
|
require("silk.core.hook")
|
||||||
ulib = require("ulib")
|
require("silk.core.OOP")
|
||||||
require(BASE_FRW.."silk.BaseObject")
|
require("silk.core.sqlite")
|
||||||
require(BASE_FRW.."silk.DBHelper")
|
|
||||||
require(BASE_FRW.."silk.Router")
|
require("silk.BaseObject")
|
||||||
require(BASE_FRW.."silk.BaseController")
|
require("silk.Router")
|
||||||
require(BASE_FRW.."silk.BaseModel")
|
require("silk.BaseController")
|
||||||
require(BASE_FRW.."silk.Logger")
|
require("silk.BaseModel")
|
||||||
require(BASE_FRW.."silk.Template")
|
require("silk.Logger")
|
||||||
|
require("silk.Template")
|
||||||
|
|
||||||
|
DIR_SEP = "/"
|
||||||
|
|
||||||
-- mime type allows
|
-- mime type allows
|
||||||
-- this will bypass the default server security
|
-- this will bypass the default server security
|
||||||
-- the default list is from the server setting
|
-- the default list is from the server setting
|
||||||
POLICY = {}
|
POLICY = {}
|
||||||
POLICY.mimes = {
|
POLICY.mimes = {
|
||||||
["application/javascript"] = true,
|
|
||||||
["image/bmp"] = true,
|
["image/bmp"] = true,
|
||||||
["image/jpeg"] = true,
|
["image/jpeg"] = true,
|
||||||
["image/png"] = true,
|
["image/png"] = true,
|
||||||
@ -38,20 +40,12 @@ POLICY.mimes = {
|
|||||||
["application/vnd.ms-fontobject"] = true,
|
["application/vnd.ms-fontobject"] = true,
|
||||||
["application/x-font-woff"] = true,
|
["application/x-font-woff"] = true,
|
||||||
["application/x-font-otf"] = true,
|
["application/x-font-otf"] = true,
|
||||||
["audio/mpeg"] = true,
|
["audio/mpeg"] = true
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
HEADER_FLAG = false
|
|
||||||
|
|
||||||
function html()
|
function html()
|
||||||
if not HEADER_FLAG then
|
if not RESPONSE_HEADER.sent then
|
||||||
std.chtml(SESSION)
|
std.html()
|
||||||
HEADER_FLAG = true
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function import(module)
|
|
||||||
return require(BASE_FRW.."silk.api."..module)
|
|
||||||
end
|
|
@ -4,6 +4,7 @@ function Object:prototype(o)
|
|||||||
o = o or {} -- create table if user does not provide one
|
o = o or {} -- create table if user does not provide one
|
||||||
setmetatable(o, self)
|
setmetatable(o, self)
|
||||||
self.__index = self
|
self.__index = self
|
||||||
|
self.__tostring = o:tostring()
|
||||||
return o
|
return o
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -13,8 +14,12 @@ function Object:new(o)
|
|||||||
return obj
|
return obj
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Object:tostring()
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
|
||||||
function Object:print()
|
function Object:print()
|
||||||
print('an Object')
|
print(self:tostring())
|
||||||
end
|
end
|
||||||
|
|
||||||
function Object:initialize()
|
function Object:initialize()
|
||||||
@ -28,13 +33,7 @@ function Object:inherit(o)
|
|||||||
return self:prototype(o)
|
return self:prototype(o)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Object:extends(o)
|
function Object:extends(o)
|
||||||
return self:inherit(o)
|
return self:inherit(o)
|
||||||
end
|
end
|
||||||
|
|
||||||
Test = Object:inherit{dummy = 0}
|
|
||||||
|
|
||||||
function Test:toWeb()
|
|
||||||
wio.t(self.dummy)
|
|
||||||
end
|
|
@ -1,260 +0,0 @@
|
|||||||
math.randomseed(os.clock())
|
|
||||||
package.cpath = __api__.apiroot..'/?.so'
|
|
||||||
require("antd")
|
|
||||||
std = modules.std()
|
|
||||||
local read_header =function()
|
|
||||||
local l
|
|
||||||
repeat
|
|
||||||
l = std.antd_recv(HTTP_REQUEST.id)
|
|
||||||
if l and l ~= '\r' then
|
|
||||||
if l == "HTTP_REQUEST" or l == "request" or l == "COOKIE" or l == "REQUEST_HEADER" or l == "REQUEST_DATA" then
|
|
||||||
coroutine.yield(l, "LUA_TABLE")
|
|
||||||
else
|
|
||||||
local l1 = std.antd_recv(HTTP_REQUEST.id)
|
|
||||||
if l1 ~= '\r' then
|
|
||||||
coroutine.yield(l, l1)
|
|
||||||
end
|
|
||||||
l = l1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
until not l or l == '\r'
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local read_headers = function()
|
|
||||||
local co = coroutine.create(function () read_header() end)
|
|
||||||
return function () -- iterator
|
|
||||||
local code, k, v = coroutine.resume(co)
|
|
||||||
return k,v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local parse_headers =function()
|
|
||||||
local lut = {
|
|
||||||
HTTP_REQUEST = HTTP_REQUEST
|
|
||||||
}
|
|
||||||
local curr_tbl = "HTTP_REQUEST"
|
|
||||||
for k,v in read_headers() do
|
|
||||||
if v == "LUA_TABLE" then
|
|
||||||
if not lut[k] then
|
|
||||||
lut[k] = {}
|
|
||||||
end
|
|
||||||
curr_tbl = k
|
|
||||||
else
|
|
||||||
lut[curr_tbl][k] = v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
HTTP_REQUEST.request = lut.request
|
|
||||||
HTTP_REQUEST.request.COOKIE = lut.COOKIE
|
|
||||||
HTTP_REQUEST.request.REQUEST_HEADER = lut.REQUEST_HEADER
|
|
||||||
HTTP_REQUEST.request.REQUEST_DATA = lut.REQUEST_DATA
|
|
||||||
end
|
|
||||||
|
|
||||||
-- parsing the header
|
|
||||||
parse_headers()
|
|
||||||
-- root dir
|
|
||||||
__ROOT__ = HTTP_REQUEST.request.SERVER_WWW_ROOT
|
|
||||||
-- set require path
|
|
||||||
package.path = __ROOT__ .. '/?.lua;'..__api__.apiroot..'/?.lua'
|
|
||||||
require("std")
|
|
||||||
require("utils")
|
|
||||||
require("extra_mime")
|
|
||||||
ulib = require("ulib")
|
|
||||||
-- set session
|
|
||||||
SESSION = {}
|
|
||||||
|
|
||||||
REQUEST = HTTP_REQUEST.request.REQUEST_DATA
|
|
||||||
REQUEST.method = HTTP_REQUEST.request.METHOD
|
|
||||||
if HTTP_REQUEST.request.COOKIE then
|
|
||||||
SESSION = HTTP_REQUEST.request.COOKIE
|
|
||||||
end
|
|
||||||
HEADER = HTTP_REQUEST.request.REQUEST_HEADER
|
|
||||||
HEADER.mobile = false
|
|
||||||
|
|
||||||
if HEADER["User-Agent"] and HEADER["User-Agent"]:match("Mobi") then
|
|
||||||
HEADER.mobile = true
|
|
||||||
end
|
|
||||||
|
|
||||||
function LOG_INFO(fmt,...)
|
|
||||||
ulib.syslog(5,string.format(fmt or "LOG",...))
|
|
||||||
end
|
|
||||||
|
|
||||||
function LOG_ERROR(fmt,...)
|
|
||||||
ulib.syslog(3,string.format(fmt or "ERROR",...))
|
|
||||||
end
|
|
||||||
|
|
||||||
function has_module(m)
|
|
||||||
if utils.file_exists(__ROOT__..'/'..m) then
|
|
||||||
if m:find("%.ls$") then
|
|
||||||
return true, true, __ROOT__..'/'..m
|
|
||||||
else
|
|
||||||
return true, false, m:gsub(".lua$","")
|
|
||||||
end
|
|
||||||
elseif utils.file_exists(__ROOT__..'/'..string.gsub(m,'%.','/')..'.lua') then
|
|
||||||
return true, false, m
|
|
||||||
elseif utils.file_exists(__ROOT__..'/'..string.gsub(m,'%.','/')..'.ls') then
|
|
||||||
return true, true, __ROOT__..'/'..string.gsub(m,'%.','/')..'.ls'
|
|
||||||
end
|
|
||||||
return false, false, nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function echo(m)
|
|
||||||
if m then std.t(m) else std.t("Undefined value") end
|
|
||||||
end
|
|
||||||
|
|
||||||
function loadscript(file, args)
|
|
||||||
local f = io.open(file, "rb")
|
|
||||||
local content = ""
|
|
||||||
if f then
|
|
||||||
local html = ""
|
|
||||||
local pro = "local fn = function(...)"
|
|
||||||
local s,e, mt
|
|
||||||
local mtbegin = true -- find begin of scrit, 0 end of scrit
|
|
||||||
local i = 1
|
|
||||||
if args then
|
|
||||||
pro = "local fn = function("..table.concat( args, ",")..")"
|
|
||||||
end
|
|
||||||
for line in io.lines(file) do
|
|
||||||
line = std.trim(line, " ")
|
|
||||||
if(line ~= "") then
|
|
||||||
if(mtbegin) then
|
|
||||||
mt = "^%s*<%?lua"
|
|
||||||
else
|
|
||||||
mt = "%?>%s*$"
|
|
||||||
end
|
|
||||||
s,e = line:find(mt)
|
|
||||||
if(s) then
|
|
||||||
if mtbegin then
|
|
||||||
if html ~= "" then
|
|
||||||
pro= pro.."echo(\""..utils.escape(html).."\")\n"
|
|
||||||
html = ""
|
|
||||||
end
|
|
||||||
local b,f = line:find("%?>%s*$")
|
|
||||||
if b then
|
|
||||||
pro = pro..line:sub(e+1,b-1).."\n"
|
|
||||||
else
|
|
||||||
pro = pro..line:sub(e+1).."\n"
|
|
||||||
mtbegin = not mtbegin
|
|
||||||
end
|
|
||||||
else
|
|
||||||
pro = pro..line:sub(0,s-1).."\n"
|
|
||||||
mtbegin = not mtbegin
|
|
||||||
end
|
|
||||||
else -- no match
|
|
||||||
if mtbegin then
|
|
||||||
-- detect if we have inline lua with format <?=..?>
|
|
||||||
local b,f = line:find("<%?=")
|
|
||||||
if b then
|
|
||||||
local tmp = line
|
|
||||||
pro= pro.."echo("
|
|
||||||
while(b) do
|
|
||||||
-- find the close
|
|
||||||
local x,y = tmp:find("%?>")
|
|
||||||
if x then
|
|
||||||
pro = pro.."\""..utils.escape(html..tmp:sub(0,b-1):gsub("%%","%%%%")).."\".."
|
|
||||||
pro = pro..tmp:sub(f+1,x-1)..".."
|
|
||||||
html = ""
|
|
||||||
tmp = tmp:sub(y+1)
|
|
||||||
b,f = tmp:find("<%?=")
|
|
||||||
else
|
|
||||||
error("Syntax error near line "..i)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
pro = pro.."\""..utils.escape(tmp:gsub("%%","%%%%")).."\")\n"
|
|
||||||
else
|
|
||||||
html = html..std.trim(line," "):gsub("%%","%%%%").."\n"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if line ~= "" then pro = pro..line.."\n" end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
i = i+ 1
|
|
||||||
end
|
|
||||||
f:close()
|
|
||||||
if(html ~= "") then
|
|
||||||
pro = pro.."echo(\""..utils.escape(html).."\")\n"
|
|
||||||
end
|
|
||||||
pro = pro.."\nend \n return fn"
|
|
||||||
local r,e = load(pro)
|
|
||||||
if r then return r(), e else return nil,e end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- decode post data if any
|
|
||||||
local decode_request_data = function()
|
|
||||||
if (not REQUEST.method)
|
|
||||||
or (REQUEST.method ~= "POST"
|
|
||||||
and REQUEST.method ~= "PUT"
|
|
||||||
and REQUEST.method ~= "PATCH")
|
|
||||||
or (not REQUEST.HAS_RAW_BODY) then
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
local ctype = HEADER['Content-Type']
|
|
||||||
local clen = HEADER['Content-Length'] or -1
|
|
||||||
if clen then
|
|
||||||
clen = tonumber(clen)
|
|
||||||
end
|
|
||||||
if not ctype or clen == -1 then
|
|
||||||
LOG_ERROR("Invalid content type %s or content length %d", ctype, clen)
|
|
||||||
return 400, "Bad Request, missing content description"
|
|
||||||
end
|
|
||||||
local raw_data, len = std.antd_recv(HTTP_REQUEST.id, clen)
|
|
||||||
if len ~= clen then
|
|
||||||
LOG_ERROR("Unable to read all data: read %d expected %d", len, clen)
|
|
||||||
return 400, "Bad Request, missing content data"
|
|
||||||
end
|
|
||||||
if ctype:find("application/json") then
|
|
||||||
REQUEST.json = bytes.__tostring(raw_data)
|
|
||||||
else
|
|
||||||
REQUEST[ctype] = raw_data
|
|
||||||
end
|
|
||||||
REQUEST.HAS_RAW_BODY = nil
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
|
|
||||||
-- set compression level
|
|
||||||
local accept_encoding = HEADER["Accept-Encoding"]
|
|
||||||
if accept_encoding then
|
|
||||||
if accept_encoding:find("gzip") then
|
|
||||||
std.antd_set_zlevel(HTTP_REQUEST.id, "gzip")
|
|
||||||
elseif accept_encoding:find("deflate") then
|
|
||||||
std.antd_set_zlevel(HTTP_REQUEST.id, "deflate")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local code, error = decode_request_data()
|
|
||||||
|
|
||||||
if code ~= 0 then
|
|
||||||
LOG_ERROR(error)
|
|
||||||
std.error(code, error)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- LOG_INFO(JSON.encode(REQUEST))
|
|
||||||
|
|
||||||
-- OOP support
|
|
||||||
--require("OOP")
|
|
||||||
-- load sqlite helper
|
|
||||||
--require("sqlite")
|
|
||||||
-- enable extra mime
|
|
||||||
|
|
||||||
-- run the file
|
|
||||||
|
|
||||||
|
|
||||||
local m, s, p = has_module(HTTP_REQUEST.request.RESOURCE_PATH)
|
|
||||||
if m then
|
|
||||||
-- run the correct module
|
|
||||||
if s then
|
|
||||||
local r,e = loadscript(p)
|
|
||||||
if r then r() else unknow(e) end
|
|
||||||
else
|
|
||||||
LOG_INFO("RUNNING MODULE %s", p)
|
|
||||||
require(p)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
unknow("Resource not found for request "..HTTP_REQUEST.request.RESOURCE_PATH)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
--require('router')
|
|
@ -1,63 +0,0 @@
|
|||||||
FFI = require("ffi")
|
|
||||||
FFI.type = {}
|
|
||||||
FFI.type.VOID = 0
|
|
||||||
FFI.type.UINT8 = 1
|
|
||||||
FFI.type.SINT8 = 2
|
|
||||||
FFI.type.UINT16 = 3
|
|
||||||
FFI.type.SINT16 = 4
|
|
||||||
FFI.type.UINT32 = 5
|
|
||||||
FFI.type.SINT32 = 6
|
|
||||||
FFI.type.UINT64 = 7
|
|
||||||
FFI.type.SINT64 = 8
|
|
||||||
FFI.type.FLOAT = 9
|
|
||||||
FFI.type.DOUBLE = 10
|
|
||||||
FFI.type.UCHAR = 11
|
|
||||||
FFI.type.SCHAR = 12
|
|
||||||
FFI.type.USHORT = 13
|
|
||||||
FFI.type.SSHORT = 14
|
|
||||||
FFI.type.UINT = 15
|
|
||||||
FFI.type.SINT = 16
|
|
||||||
FFI.type.ULONG = 17
|
|
||||||
FFI.type.SLONG = 18
|
|
||||||
FFI.type.LONGDOUBLE = 19
|
|
||||||
FFI.type.POINTER = 20
|
|
||||||
FFI.cache = {}
|
|
||||||
|
|
||||||
FFI.load = function(path)
|
|
||||||
if FFI.cache[path] then
|
|
||||||
return FFI.cache[path]
|
|
||||||
else
|
|
||||||
print("Loading: "..path)
|
|
||||||
local lib = FFI.dlopen(path)
|
|
||||||
if lib then
|
|
||||||
FFI.cache[path] = {ref = lib, fn= {}}
|
|
||||||
end
|
|
||||||
return FFI.cache[path]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
FFI.unload = function(path)
|
|
||||||
local lib = FFI.cache[path]
|
|
||||||
if lib then
|
|
||||||
FFI.dlclose(lib.ref)
|
|
||||||
FFI.cache[path] = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
FFI.unloadAll = function()
|
|
||||||
for k,v in pairs(FFI.cache) do
|
|
||||||
FFI.dlclose(v.ref)
|
|
||||||
end
|
|
||||||
FFI.cache = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
FFI.lookup = function(lib, name)
|
|
||||||
local fn = lib.fn[name]
|
|
||||||
if fn then return fn end
|
|
||||||
fn = FFI.dlsym(lib.ref, name)
|
|
||||||
if fn then
|
|
||||||
lib.fn[name] = fn
|
|
||||||
return fn
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
@ -1,79 +0,0 @@
|
|||||||
function std.extra_mime(name)
|
|
||||||
local ext = std.ext(name)
|
|
||||||
local mpath = __ROOT__.."/".."mimes.json"
|
|
||||||
local xmimes = {}
|
|
||||||
if utils.file_exists(mpath) then
|
|
||||||
local f = io.open(mpath, "r")
|
|
||||||
if f then
|
|
||||||
xmimes = JSON.decodeString(f:read("*all"))
|
|
||||||
f:close()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if(name:find("Makefile$")) then return "text/makefile",false
|
|
||||||
elseif ext == "php" then return "text/php",false
|
|
||||||
elseif ext == "c" or ext == "h" then return "text/c",false
|
|
||||||
elseif ext == "cpp" or ext == "hpp" then return "text/cpp",false
|
|
||||||
elseif ext == "md" then return "text/markdown",false
|
|
||||||
elseif ext == "lua" then return "text/lua",false
|
|
||||||
elseif ext == "yml" then return "application/x-yaml", false
|
|
||||||
elseif xmimes[ext] then return xmimes[ext].mime, xmimes[ext].binary
|
|
||||||
--elseif ext == "pgm" then return "image/x-portable-graymap", true
|
|
||||||
else
|
|
||||||
return "application/octet-stream",true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function std.mimeOf(name)
|
|
||||||
local mime = std.mime(name)
|
|
||||||
if mime ~= "application/octet-stream" then
|
|
||||||
return mime
|
|
||||||
else
|
|
||||||
return std.extra_mime(name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[ function std.isBinary(name)
|
|
||||||
local mime = std.mime(name)
|
|
||||||
if mime ~= "application/octet-stream" then
|
|
||||||
return std.is_bin(name)
|
|
||||||
else
|
|
||||||
local xmime,bin = std.extra_mime(name)
|
|
||||||
return bin
|
|
||||||
end
|
|
||||||
end ]]
|
|
||||||
|
|
||||||
function std.sendFile(m)
|
|
||||||
local mime = std.mimeOf(m)
|
|
||||||
local finfo = ulib.file_stat(m)
|
|
||||||
local len = tostring(math.floor(finfo.size))
|
|
||||||
local len1 = tostring(math.floor(finfo.size - 1))
|
|
||||||
if mime == "audio/mpeg" then
|
|
||||||
std.status(200)
|
|
||||||
std.header("Pragma", "public")
|
|
||||||
std.header("Expires", "0")
|
|
||||||
std.header("Content-Type", mime)
|
|
||||||
std.header("Content-Length", len)
|
|
||||||
std.header("Content-Disposition", "inline; filename=" .. std.basename(m))
|
|
||||||
std.header("Content-Range:", "bytes 0-" .. len1 .. "/" .. len)
|
|
||||||
std.header("Accept-Ranges", "bytes")
|
|
||||||
std.header("X-Pad", "avoid browser bug")
|
|
||||||
std.header("Content-Transfer-Encoding", "binary")
|
|
||||||
std.header("Cache-Control", "no-cache, no-store")
|
|
||||||
std.header("Connection", "Keep-Alive")
|
|
||||||
std.header_flush()
|
|
||||||
std.f(m)
|
|
||||||
else
|
|
||||||
if HEADER['If-Modified-Since'] and HEADER['If-Modified-Since'] == finfo.ctime then
|
|
||||||
std.status(304)
|
|
||||||
std.header_flush()
|
|
||||||
else
|
|
||||||
std.status(200)
|
|
||||||
std.header("Content-Type", mime)
|
|
||||||
--std.header("Content-Length", len)
|
|
||||||
std.header("Cache-Control", "no-cache")
|
|
||||||
std.header("Last-Modified", finfo.ctime)
|
|
||||||
std.header_flush()
|
|
||||||
std.f(m)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
210
silkmvc/core/hook.lua
Normal file
210
silkmvc/core/hook.lua
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
math.randomseed(os.time())
|
||||||
|
-- define some legacy global variables to provide backward support
|
||||||
|
-- for some old web application
|
||||||
|
__api__ = {
|
||||||
|
apiroot = string.format("%s/lua", _SERVER["LIB_DIR"]),
|
||||||
|
tmpdir = _SERVER["TMP_DIR"],
|
||||||
|
dbpath = _SERVER["DB_DIR"]
|
||||||
|
}
|
||||||
|
-- root dir
|
||||||
|
__ROOT__ = _SERVER["DOCUMENT_ROOT"]
|
||||||
|
-- set require path
|
||||||
|
package.cpath = __api__.apiroot .. '/?.so'
|
||||||
|
package.path = string.format("%s/?.lua;%s/?.lua",__api__.apiroot,__ROOT__)
|
||||||
|
|
||||||
|
ulib = require("ulib")
|
||||||
|
slice = require("slice")
|
||||||
|
require("silk.core.utils")
|
||||||
|
require("silk.core.std")
|
||||||
|
|
||||||
|
|
||||||
|
-- global helper functions for lua page script
|
||||||
|
function has_module(m)
|
||||||
|
if utils.file_exists(__ROOT__ .. '/' .. m) then
|
||||||
|
if m:find("%.ls$") then
|
||||||
|
return true, true, __ROOT__ .. '/' .. m
|
||||||
|
else
|
||||||
|
return true, false, m:gsub(".lua$", "")
|
||||||
|
end
|
||||||
|
elseif utils.file_exists(__ROOT__ .. '/' .. string.gsub(m, '%.', '/') .. '.lua') then
|
||||||
|
return true, false, m
|
||||||
|
elseif utils.file_exists(__ROOT__ .. '/' .. string.gsub(m, '%.', '/') .. '.ls') then
|
||||||
|
return true, true, __ROOT__ .. '/' .. string.gsub(m, '%.', '/') .. '.ls'
|
||||||
|
end
|
||||||
|
return false, false, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Send data to client
|
||||||
|
function echo(...)
|
||||||
|
fcgio:echo(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- luad lua page script
|
||||||
|
function loadscript(file, args)
|
||||||
|
local f = io.open(file, "rb")
|
||||||
|
local content = ""
|
||||||
|
if f then
|
||||||
|
local html = ""
|
||||||
|
local pro = "local fn = function(...)"
|
||||||
|
local s, e, mt
|
||||||
|
local mtbegin = true -- find begin of scrit, 0 end of scrit
|
||||||
|
local i = 1
|
||||||
|
if args then
|
||||||
|
pro = "local fn = function(" .. table.concat(args, ",") .. ")"
|
||||||
|
end
|
||||||
|
for line in io.lines(file) do
|
||||||
|
line = ulib.trim(line, " ")
|
||||||
|
if (line ~= "") then
|
||||||
|
if (mtbegin) then
|
||||||
|
mt = "^%s*<%?lua"
|
||||||
|
else
|
||||||
|
mt = "%?>%s*$"
|
||||||
|
end
|
||||||
|
s, e = line:find(mt)
|
||||||
|
if (s) then
|
||||||
|
if mtbegin then
|
||||||
|
if html ~= "" then
|
||||||
|
pro = pro .. "echo(\"" .. utils.escape(html) .. "\")\n"
|
||||||
|
html = ""
|
||||||
|
end
|
||||||
|
local b, f = line:find("%?>%s*$")
|
||||||
|
if b then
|
||||||
|
pro = pro .. line:sub(e + 1, b - 1) .. "\n"
|
||||||
|
else
|
||||||
|
pro = pro .. line:sub(e + 1) .. "\n"
|
||||||
|
mtbegin = not mtbegin
|
||||||
|
end
|
||||||
|
else
|
||||||
|
pro = pro .. line:sub(0, s - 1) .. "\n"
|
||||||
|
mtbegin = not mtbegin
|
||||||
|
end
|
||||||
|
else -- no match
|
||||||
|
if mtbegin then
|
||||||
|
-- detect if we have inline lua with format <?=..?>
|
||||||
|
local b, f = line:find("<%?=")
|
||||||
|
if b then
|
||||||
|
local tmp = line
|
||||||
|
pro = pro .. "echo("
|
||||||
|
while (b) do
|
||||||
|
-- find the close
|
||||||
|
local x, y = tmp:find("%?>")
|
||||||
|
if x then
|
||||||
|
pro = pro .. "\"" .. utils.escape(html .. tmp:sub(0, b - 1):gsub("%%", "%%%%")) ..
|
||||||
|
"\".."
|
||||||
|
pro = pro .. tmp:sub(f + 1, x - 1) .. ".."
|
||||||
|
html = ""
|
||||||
|
tmp = tmp:sub(y + 1)
|
||||||
|
b, f = tmp:find("<%?=")
|
||||||
|
else
|
||||||
|
error("Syntax error near line " .. i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
pro = pro .. "\"" .. utils.escape(tmp:gsub("%%", "%%%%")) .. "\")\n"
|
||||||
|
else
|
||||||
|
html = html .. ulib.trim(line, " "):gsub("%%", "%%%%") .. "\n"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if line ~= "" then
|
||||||
|
pro = pro .. line .. "\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
f:close()
|
||||||
|
if (html ~= "") then
|
||||||
|
pro = pro .. "echo(\"" .. utils.escape(html) .. "\")\n"
|
||||||
|
end
|
||||||
|
pro = pro .. "\nend \n return fn"
|
||||||
|
local r, e = load(pro)
|
||||||
|
if r then
|
||||||
|
return r(), e
|
||||||
|
else
|
||||||
|
return nil, e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- logging helpers
|
||||||
|
function LOG_INFO(fmt, ...)
|
||||||
|
fcgio:log_info(string.format(fmt or "LOG", ...))
|
||||||
|
end
|
||||||
|
|
||||||
|
function LOG_ERROR(fmt, ...)
|
||||||
|
fcgio:log_error(string.format(fmt or "ERROR", ...))
|
||||||
|
end
|
||||||
|
|
||||||
|
function LOG_DEBUG(fmt, ...)
|
||||||
|
fcgio:log_debug(string.format(fmt or "ERROR", ...))
|
||||||
|
end
|
||||||
|
|
||||||
|
function LOG_WARN(fmt, ...)
|
||||||
|
fcgio:log_warn(string.format(fmt or "ERROR", ...))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- decode post data if any
|
||||||
|
local decode_request_data = function()
|
||||||
|
-- decode POST request data
|
||||||
|
if _SERVER["RAW_DATA"] then
|
||||||
|
if REQUEST.method == "POST" and HEADER["Content-Type"] == "application/x-www-form-urlencoded" then
|
||||||
|
for k, v in pairs(utils.parse_query(tostring(_SERVER["RAW_DATA"]))) do
|
||||||
|
REQUEST[k] = v
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local ctype = HEADER['Content-Type']
|
||||||
|
local clen = HEADER['Content-Length'] or -1
|
||||||
|
if clen then
|
||||||
|
clen = tonumber(clen)
|
||||||
|
end
|
||||||
|
if not ctype or clen == -1 then
|
||||||
|
LOG_ERROR("Invalid content type %s or content length %d", ctype, clen)
|
||||||
|
return 400, "Bad Request, missing content description"
|
||||||
|
end
|
||||||
|
if ctype:find("application/json") then
|
||||||
|
REQUEST.json = tostring(_SERVER["RAW_DATA"])
|
||||||
|
else
|
||||||
|
REQUEST[ctype] = _SERVER["RAW_DATA"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
-- define old fashion global request object
|
||||||
|
HEADER = {}
|
||||||
|
setmetatable(HEADER, {
|
||||||
|
__index = function(o, data)
|
||||||
|
local key = "HTTP_" .. string.upper(data:gsub("-", "_"))
|
||||||
|
return _SERVER[key]
|
||||||
|
end
|
||||||
|
})
|
||||||
|
HEADER.mobile = false
|
||||||
|
if HEADER["User-Agent"] and HEADER["User-Agent"]:match("Mobi") then
|
||||||
|
HEADER.mobile = true
|
||||||
|
end
|
||||||
|
|
||||||
|
REQUEST = {}
|
||||||
|
-- decode GET request
|
||||||
|
if _SERVER["QUERY_STRING"] then
|
||||||
|
REQUEST = utils.parse_query(_SERVER["QUERY_STRING"])
|
||||||
|
end
|
||||||
|
REQUEST.method = _SERVER["REQUEST_METHOD"]
|
||||||
|
|
||||||
|
-- set session
|
||||||
|
SESSION = {}
|
||||||
|
if HEADER["Cookie"] then
|
||||||
|
for key, val in HEADER["Cookie"]:gmatch("([^;=]+)=*([^;]*)") do
|
||||||
|
SESSION[ulib.trim(key," ")] = ulib.trim(val, " ")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local code, error = decode_request_data()
|
||||||
|
|
||||||
|
if code ~= 0 then
|
||||||
|
LOG_ERROR(error)
|
||||||
|
std.error(code, error)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
115
silkmvc/core/mimes.lua
Normal file
115
silkmvc/core/mimes.lua
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
local default_mimes = {
|
||||||
|
["bmp"] = "image/bmp",
|
||||||
|
["jpg"] = "image/jpeg",
|
||||||
|
["jpeg"] = "image/jpeg",
|
||||||
|
["css"] = "text/css",
|
||||||
|
["md"] = "text/markdown",
|
||||||
|
["csv"] = "text/csv",
|
||||||
|
["pdf"] = "application/pdf",
|
||||||
|
["gif"] = "image/gif",
|
||||||
|
["html"] = "text/html",
|
||||||
|
["htm"] = "text/html",
|
||||||
|
["chtml"] = "text/html",
|
||||||
|
["json"] = "application/json",
|
||||||
|
["js"] = "application/javascript",
|
||||||
|
["png"] = "image/png",
|
||||||
|
["ppm"] = "image/x-portable-pixmap",
|
||||||
|
["rar"] = "application/x-rar-compressed",
|
||||||
|
["tiff"] = "image/tiff",
|
||||||
|
["tar"] = "application/x-tar",
|
||||||
|
["txt"] = "text/plain",
|
||||||
|
["ttf"] = "application/x-font-ttf",
|
||||||
|
["xhtml"] = "application/xhtml+xml",
|
||||||
|
["xml"] = "application/xml",
|
||||||
|
["zip"] = "application/zip",
|
||||||
|
["svg"] = "image/svg+xml",
|
||||||
|
["eot"] = "application/vnd.ms-fontobject",
|
||||||
|
["woff"] = "application/x-font-woff",
|
||||||
|
["woff2"] = "application/x-font-woff",
|
||||||
|
["otf"] = "application/x-font-otf",
|
||||||
|
["mp3"] = "audio/mpeg",
|
||||||
|
["mpeg"] = "audio/mpeg"
|
||||||
|
}
|
||||||
|
|
||||||
|
setmetatable(default_mimes, {
|
||||||
|
__index = function(this, key)
|
||||||
|
return "application/octet-stream"
|
||||||
|
end
|
||||||
|
})
|
||||||
|
function std.mime(ext)
|
||||||
|
return default_mimes[ext]
|
||||||
|
end
|
||||||
|
function std.extra_mime(name)
|
||||||
|
local ext = utils.ext(name)
|
||||||
|
local mpath = __ROOT__ .. "/" .. "mimes.json"
|
||||||
|
local xmimes = {}
|
||||||
|
if utils.file_exists(mpath) then
|
||||||
|
xmimes = JSON.decodeFile(mpath)
|
||||||
|
end
|
||||||
|
if (name:find("Makefile$")) then
|
||||||
|
return "text/makefile", false
|
||||||
|
elseif ext == "php" then
|
||||||
|
return "text/php", false
|
||||||
|
elseif ext == "c" or ext == "h" then
|
||||||
|
return "text/c", false
|
||||||
|
elseif ext == "cpp" or ext == "hpp" then
|
||||||
|
return "text/cpp", false
|
||||||
|
elseif ext == "md" then
|
||||||
|
return "text/markdown", false
|
||||||
|
elseif ext == "lua" then
|
||||||
|
return "text/lua", false
|
||||||
|
elseif ext == "yml" then
|
||||||
|
return "application/x-yaml", false
|
||||||
|
elseif xmimes[ext] then
|
||||||
|
return xmimes[ext].mime, xmimes[ext].binary
|
||||||
|
-- elseif ext == "pgm" then return "image/x-portable-graymap", true
|
||||||
|
else
|
||||||
|
return "application/octet-stream", true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function std.mimeOf(name)
|
||||||
|
local mime = std.mime(utils.ext(name))
|
||||||
|
if mime ~= "application/octet-stream" then
|
||||||
|
return mime
|
||||||
|
else
|
||||||
|
return std.extra_mime(name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function std.sendFile(m)
|
||||||
|
local mime = std.mimeOf(m)
|
||||||
|
local finfo = ulib.file_stat(m)
|
||||||
|
local len = tostring(math.floor(finfo.size))
|
||||||
|
local len1 = tostring(math.floor(finfo.size - 1))
|
||||||
|
if mime == "audio/mpeg" then
|
||||||
|
std.status(200)
|
||||||
|
std.header("Pragma", "public")
|
||||||
|
std.header("Expires", "0")
|
||||||
|
std.header("Content-Type", mime)
|
||||||
|
std.header("Content-Length", len)
|
||||||
|
std.header("Content-Disposition", "inline; filename=" .. utils.basename(m))
|
||||||
|
std.header("Content-Range:", "bytes 0-" .. len1 .. "/" .. len)
|
||||||
|
std.header("Accept-Ranges", "bytes")
|
||||||
|
std.header("X-Pad", "avoid browser bug")
|
||||||
|
std.header("Content-Transfer-Encoding", "binary")
|
||||||
|
std.header("Cache-Control", "no-cache, no-store")
|
||||||
|
std.header("Connection", "Keep-Alive")
|
||||||
|
std.header_flush()
|
||||||
|
std.f(m)
|
||||||
|
else
|
||||||
|
if HEADER['If-Modified-Since'] and HEADER['If-Modified-Since'] == finfo.ctime then
|
||||||
|
std.status(304)
|
||||||
|
std.header_flush()
|
||||||
|
else
|
||||||
|
std.status(200)
|
||||||
|
std.header("Content-Type", mime)
|
||||||
|
-- std.header("Content-Length", len)
|
||||||
|
std.header("Cache-Control", "no-cache")
|
||||||
|
std.header("Last-Modified", finfo.ctime)
|
||||||
|
std.header_flush()
|
||||||
|
std.f(m)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -1,23 +1,42 @@
|
|||||||
sqlite = modules.sqlite()
|
sqlite = require("sqlitedb")
|
||||||
|
|
||||||
|
if sqlite == nil then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
require("silk.core.OOP")
|
||||||
|
|
||||||
|
sqlite.getdb = function(name)
|
||||||
|
if name:find("%.db$") then
|
||||||
|
return sqlite.db(name)
|
||||||
|
elseif name:find("/") then
|
||||||
|
LOG_ERROR("Invalid database name %s", name)
|
||||||
|
return nil
|
||||||
|
else
|
||||||
|
return sqlite.db(__api__.dbpath .. "/" .. name .. ".db")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if sqlite == nil then return 0 end
|
|
||||||
require("OOP")
|
|
||||||
-- create class
|
-- create class
|
||||||
DBModel = Object:inherit{db=nil, name=''}
|
DBModel = Object:inherit{
|
||||||
|
db = nil,
|
||||||
|
}
|
||||||
|
|
||||||
function DBModel:createTable(m)
|
function DBModel:createTable(name, m)
|
||||||
if self:available() then return true end
|
if self:available() then
|
||||||
local sql = "CREATE TABLE "..self.name.."(id INTEGER PRIMARY KEY"
|
return true
|
||||||
|
end
|
||||||
|
local sql = "CREATE TABLE " .. name .. "(id INTEGER PRIMARY KEY"
|
||||||
for k, v in pairs(m) do
|
for k, v in pairs(m) do
|
||||||
if k ~= "id" then
|
if k ~= "id" then
|
||||||
sql = sql .. "," .. k .. " " .. v
|
sql = sql .. "," .. k .. " " .. v
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
sql = sql .. ");"
|
sql = sql .. ");"
|
||||||
return sqlite.query(self.db,sql) == 1
|
return self:exec(sql)
|
||||||
end
|
end
|
||||||
|
|
||||||
function DBModel:insert(m)
|
function DBModel:insert(name, m)
|
||||||
local keys = {}
|
local keys = {}
|
||||||
local values = {}
|
local values = {}
|
||||||
for k, v in pairs(m) do
|
for k, v in pairs(m) do
|
||||||
@ -33,27 +52,33 @@ function DBModel:insert(m)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local sql = "INSERT INTO "..self.name.." ("..table.concat(keys,',')..') VALUES ('
|
local sql = "INSERT INTO " .. name .. " (" .. table.concat(keys, ',') .. ') VALUES ('
|
||||||
sql = sql .. table.concat(values, ',') .. ');'
|
sql = sql .. table.concat(values, ',') .. ');'
|
||||||
return sqlite.query(self.db, sql) == 1
|
return self:exec(sql)
|
||||||
end
|
end
|
||||||
|
|
||||||
function DBModel:get(id)
|
function DBModel:get(name, id)
|
||||||
return sqlite.select(self.db, self.name, "*","id="..id)[1]
|
local records = self:query( string.format("SELECT * FROM %s WHERE id=%d", name, id))
|
||||||
|
if records and #records == 1 then
|
||||||
|
return records[1]
|
||||||
|
end
|
||||||
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
function DBModel:getAll()
|
function DBModel:getAll(name)
|
||||||
--local sql = "SELECT * FROM "..self.name
|
local data = self:query( "SELECT * FROM " .. name)
|
||||||
--return sqlite.select(self.db, self.name, "1=1")
|
if not data then
|
||||||
local data = sqlite.select(self.db, self.name, "*", "1=1")
|
return nil
|
||||||
if data == nil then return nil end
|
end
|
||||||
local a = {}
|
local a = {}
|
||||||
for n in pairs(data) do table.insert(a, n) end
|
for n in pairs(data) do
|
||||||
|
table.insert(a, n)
|
||||||
|
end
|
||||||
table.sort(a)
|
table.sort(a)
|
||||||
return data, a
|
return data, a
|
||||||
end
|
end
|
||||||
|
|
||||||
function DBModel:find(cond)
|
function DBModel:find(name, cond)
|
||||||
local cnd = "1=1"
|
local cnd = "1=1"
|
||||||
local sel = "*"
|
local sel = "*"
|
||||||
if cond.exp then
|
if cond.exp then
|
||||||
@ -77,19 +102,38 @@ function DBModel:find(cond)
|
|||||||
-- print(sel)
|
-- print(sel)
|
||||||
end
|
end
|
||||||
-- print(cnd)
|
-- print(cnd)
|
||||||
local data = sqlite.select(self.db, self.name, sel, cnd)
|
local data = self:query( string.format("SELECT %s FROM %s WHERE %s", sel, name, cnd))
|
||||||
if data == nil then return nil end
|
if data == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
local a = {}
|
local a = {}
|
||||||
for n in pairs(data) do table.insert(a, n) end
|
for n in pairs(data) do
|
||||||
|
table.insert(a, n)
|
||||||
|
end
|
||||||
table.sort(a)
|
table.sort(a)
|
||||||
return data, a
|
return data, a
|
||||||
end
|
end
|
||||||
|
|
||||||
function DBModel:query(sql)
|
function DBModel:query(sql)
|
||||||
return sqlite.query(self.db, sql) == 1
|
local data, error = sqlite.query(self.db, sql)
|
||||||
|
--LOG_DEBUG(sql)
|
||||||
|
if not data then
|
||||||
|
LOG_ERROR("Error querying recorda SQL[%s]: %s", sql, error or "")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
function DBModel:update(m)
|
function DBModel:exec(sql)
|
||||||
|
--LOG_DEBUG(sql)
|
||||||
|
local ret, err = sqlite.exec(self.db, sql)
|
||||||
|
if not ret then
|
||||||
|
LOG_ERROR("Error execute [%s]: %s", sql, err or "")
|
||||||
|
end
|
||||||
|
return ret == true
|
||||||
|
end
|
||||||
|
|
||||||
|
function DBModel:update(name, m)
|
||||||
local id = m['id']
|
local id = m['id']
|
||||||
if id ~= nil then
|
if id ~= nil then
|
||||||
local lst = {}
|
local lst = {}
|
||||||
@ -102,18 +146,19 @@ function DBModel:update(m)
|
|||||||
table.insert(lst, k .. "=\"" .. v:gsub('"', '""') .. "\"")
|
table.insert(lst, k .. "=\"" .. v:gsub('"', '""') .. "\"")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local sql = "UPDATE "..self.name.." SET "..table.concat(lst,",").." WHERE id="..id..";"
|
local sql = "UPDATE " .. name .. " SET " .. table.concat(lst, ",") .. " WHERE id=" .. id .. ";"
|
||||||
return sqlite.query(self.db, sql) == 1
|
return self:exec(sql)
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
function DBModel:available()
|
function DBModel:available(name)
|
||||||
return sqlite.hasTable(self.db, self.name) == 1
|
local records = self:query(string.format("SELECT * FROM sqlite_master WHERE type='table' and name='%s'", name))
|
||||||
|
return #records == 1
|
||||||
end
|
end
|
||||||
function DBModel:deleteByID(id)
|
function DBModel:deleteByID(name, id)
|
||||||
local sql = "DELETE FROM "..self.name.." WHERE id="..id..";"
|
local sql = "DELETE FROM " .. name .. " WHERE id=" .. id .. ";"
|
||||||
return sqlite.query(self.db, sql) == 1
|
return self:exec(sql)
|
||||||
end
|
end
|
||||||
function DBModel:gencond(o)
|
function DBModel:gencond(o)
|
||||||
for k, v in pairs(o) do
|
for k, v in pairs(o) do
|
||||||
@ -136,13 +181,13 @@ function DBModel:gencond(o)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
function DBModel:delete(cond)
|
function DBModel:delete(name, cond)
|
||||||
local sql = "DELETE FROM "..self.name.." WHERE "..self:gencond(cond)..";"
|
local sql = "DELETE FROM " .. name .. " WHERE " .. self:gencond(cond) .. ";"
|
||||||
return sqlite.query(self.db, sql) == 1
|
return self:exec(sql)
|
||||||
end
|
end
|
||||||
|
|
||||||
function DBModel:lastInsertID()
|
function DBModel:lastInsertID()
|
||||||
return sqlite.lastInsertID(self.db)
|
return sqlite.last_insert_id(self.db)
|
||||||
end
|
end
|
||||||
|
|
||||||
function DBModel:close()
|
function DBModel:close()
|
||||||
|
@ -1,22 +1,5 @@
|
|||||||
bytes = modules.bytes()
|
std = {}
|
||||||
array = modules.array()
|
require("silk.core.mimes")
|
||||||
|
|
||||||
modules.sqlite = function()
|
|
||||||
if not sqlite then
|
|
||||||
sqlite = require("sqlitedb")
|
|
||||||
sqlite.getdb = function(name)
|
|
||||||
if name:find("%.db$") then
|
|
||||||
return sqlite._getdb(name)
|
|
||||||
elseif name:find("/") then
|
|
||||||
LOG_ERROR("Invalid database name %s", name)
|
|
||||||
return nil
|
|
||||||
else
|
|
||||||
return sqlite._getdb(__api__.dbpath.."/"..name..".db")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return sqlite
|
|
||||||
end
|
|
||||||
|
|
||||||
RESPONSE_HEADER = {
|
RESPONSE_HEADER = {
|
||||||
status = 200,
|
status = 200,
|
||||||
@ -25,74 +8,135 @@ RESPONSE_HEADER = {
|
|||||||
sent = false
|
sent = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local http_status = {
|
||||||
|
[100] = "Continue",
|
||||||
|
[101] = "Switching Protocols",
|
||||||
|
[102] = "Processing",
|
||||||
|
[103] = "Early Hints",
|
||||||
|
|
||||||
|
[200] = "OK",
|
||||||
|
[201] = "Created",
|
||||||
|
[202] = "Accepted",
|
||||||
|
[203] = "Non-Authoritative Information",
|
||||||
|
[204] = "No Content",
|
||||||
|
[205] = "Reset Content",
|
||||||
|
[206] = "Partial Content",
|
||||||
|
[207] = "Multi-Status",
|
||||||
|
[208] = "Already Reported",
|
||||||
|
[226] = "IM Used",
|
||||||
|
|
||||||
|
[300] = "Multiple Choices",
|
||||||
|
[301] = "Moved Permanently",
|
||||||
|
[302] = "Found",
|
||||||
|
[303] = "See Other",
|
||||||
|
[304] = "Not Modified",
|
||||||
|
[305] = "Use Proxy",
|
||||||
|
[306] = "Switch Proxy",
|
||||||
|
[307] = "Temporary Redirect",
|
||||||
|
[308] = "Permanent Redirect",
|
||||||
|
|
||||||
|
[400] = "Bad Request",
|
||||||
|
[401] = "Unauthorized",
|
||||||
|
[402] = "Payment Required",
|
||||||
|
[403] = "Forbidden",
|
||||||
|
[404] = "Not Found",
|
||||||
|
[405] = "Method Not Allowed",
|
||||||
|
[406] = "Not Acceptable",
|
||||||
|
[407] = "Proxy Authentication Required",
|
||||||
|
[408] = "Request Timeout",
|
||||||
|
[409] = "Conflict",
|
||||||
|
[410] = "Gone",
|
||||||
|
[411] = "Length Required",
|
||||||
|
[412] = "Precondition Failed",
|
||||||
|
[413] = "Payload Too Large",
|
||||||
|
[414] = "URI Too Long",
|
||||||
|
[415] = "Unsupported Media Type",
|
||||||
|
[416] = "Range Not Satisfiable",
|
||||||
|
[417] = "Expectation Failed",
|
||||||
|
[421] = "Misdirected Request",
|
||||||
|
[422] = "Unprocessable Entity",
|
||||||
|
[423] = "Locked",
|
||||||
|
[424] = "Failed Dependency",
|
||||||
|
[425] = "Too Early",
|
||||||
|
[426] = "Upgrade Required",
|
||||||
|
[428] = "Precondition Required",
|
||||||
|
[429] = "Too Many Requests",
|
||||||
|
[431] = "Request Header Fields Too Large",
|
||||||
|
[451] = "Unavailable For Legal Reasons",
|
||||||
|
|
||||||
|
[500] = "Internal Server Error",
|
||||||
|
[501] = "Not Implemented",
|
||||||
|
[502] = "Bad Gateway",
|
||||||
|
[503] = "Service Unavailable",
|
||||||
|
[504] = "Gateway Timeout",
|
||||||
|
[505] = "HTTP Version Not Supported",
|
||||||
|
[506] = "Variant Also Negotiates",
|
||||||
|
[507] = "Insufficient Storage",
|
||||||
|
[508] = "Loop Detected",
|
||||||
|
[510] = "Not Extended",
|
||||||
|
[511] = "Network Authentication Required"
|
||||||
|
}
|
||||||
|
setmetatable(http_status, {
|
||||||
|
__index = function(this, key)
|
||||||
|
return "Unofficial Status"
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
function std.status(code)
|
function std.status(code)
|
||||||
RESPONSE_HEADER.status = code
|
RESPONSE_HEADER.status = code
|
||||||
end
|
end
|
||||||
|
|
||||||
function std.custom_header(k, v)
|
function std.custom_header(k, v)
|
||||||
std.header(k, v)
|
std.header(k, v)
|
||||||
end
|
end
|
||||||
function std.header_flush()
|
|
||||||
std._send_header(HTTP_REQUEST.id,RESPONSE_HEADER.status, RESPONSE_HEADER.header, RESPONSE_HEADER.cookie)
|
|
||||||
RESPONSE_HEADER.sent = true
|
|
||||||
end
|
|
||||||
|
|
||||||
function std.header(k, v)
|
function std.header(k, v)
|
||||||
RESPONSE_HEADER.header[k] = v
|
RESPONSE_HEADER.header[k] = v
|
||||||
end
|
end
|
||||||
|
|
||||||
function std.cjson(ck)
|
function std.header_flush()
|
||||||
for k,v in pairs(ck) do
|
-- send out status
|
||||||
std.setCookie(k.."="..v.."; Path=/")
|
echo("Status: ", RESPONSE_HEADER.status, " ", http_status[RESPONSE_HEADER.status], "\r\n")
|
||||||
|
-- send out header
|
||||||
|
for key, val in pairs(RESPONSE_HEADER.header) do
|
||||||
|
echo(key, ": ", val, "\r\n")
|
||||||
end
|
end
|
||||||
std.header("Content-Type","application/json; charset=utf-8")
|
-- send out cookie
|
||||||
std.header_flush()
|
for key, val in ipairs(RESPONSE_HEADER.cookie) do
|
||||||
|
echo("Set-Cookie: ", val, "\r\n")
|
||||||
end
|
end
|
||||||
function std.chtml(ck)
|
echo("\r\n")
|
||||||
for k,v in pairs(ck) do
|
RESPONSE_HEADER.sent = true
|
||||||
std.setCookie(k.."="..v.."; Path=/")
|
RESPONSE_HEADER.header = {}
|
||||||
end
|
RESPONSE_HEADER.cookie = {}
|
||||||
std.header("Content-Type","text/html; charset=utf-8")
|
|
||||||
std.header_flush()
|
|
||||||
end
|
|
||||||
function std.t(s)
|
|
||||||
if RESPONSE_HEADER.sent == false then
|
|
||||||
std.header_flush()
|
|
||||||
end
|
|
||||||
std._t(HTTP_REQUEST.id,s)
|
|
||||||
end
|
|
||||||
function std.b(s)
|
|
||||||
if RESPONSE_HEADER.sent == false then
|
|
||||||
std.header_flush()
|
|
||||||
end
|
|
||||||
std._b(HTTP_REQUEST.id,s)
|
|
||||||
end
|
|
||||||
function std.f(v)
|
|
||||||
std._f(HTTP_REQUEST.id,v)
|
|
||||||
--ulib.send_file(v, HTTP_REQUEST.socket)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function std.setCookie(v)
|
function std.setCookie(...)
|
||||||
RESPONSE_HEADER.cookie[#RESPONSE_HEADER.cookie] = v
|
local args = table.pack(...)
|
||||||
|
cookie = table.concat(args,";")
|
||||||
|
RESPONSE_HEADER.cookie[#RESPONSE_HEADER.cookie + 1] = cookie
|
||||||
end
|
end
|
||||||
|
|
||||||
function std.error(status, msg)
|
function std.error(status, msg)
|
||||||
std._error(HTTP_REQUEST.id, status, msg)
|
std.status(status)
|
||||||
|
std.header("Content-Type", "text/html")
|
||||||
|
std.header_flush()
|
||||||
|
echo(string.format("<HTML><HEAD><TITLE>%s</TITLE></HEAD><BODY><h2>%s</h2></BODY></HTML>",msg, msg))
|
||||||
end
|
end
|
||||||
--_upload
|
|
||||||
--_route
|
|
||||||
function std.unknow(s)
|
function std.unknow(s)
|
||||||
std.error(404, "Unknown request")
|
std.error(404, "Unknown request")
|
||||||
end
|
end
|
||||||
|
|
||||||
--_redirect
|
function std.f(path)
|
||||||
--[[ function std.redirect(s)
|
fcgio:send_file(path)
|
||||||
std._redirect(HTTP_REQUEST.id,s)
|
end
|
||||||
end ]]
|
|
||||||
|
|
||||||
function std.html()
|
function std.html()
|
||||||
std.header("Content-Type", "text/html; charset=utf-8")
|
std.header("Content-Type", "text/html; charset=utf-8")
|
||||||
std.header_flush()
|
std.header_flush()
|
||||||
end
|
end
|
||||||
|
|
||||||
function std.text()
|
function std.text()
|
||||||
std.header("Content-Type", "text/plain; charset=utf-8")
|
std.header("Content-Type", "text/plain; charset=utf-8")
|
||||||
std.header_flush()
|
std.header_flush()
|
||||||
@ -102,34 +146,24 @@ function std.json()
|
|||||||
std.header("Content-Type", "application/json; charset=utf-8")
|
std.header("Content-Type", "application/json; charset=utf-8")
|
||||||
std.header_flush()
|
std.header_flush()
|
||||||
end
|
end
|
||||||
|
|
||||||
function std.jpeg()
|
function std.jpeg()
|
||||||
std.header("Content-Type", "image/jpeg")
|
std.header("Content-Type", "image/jpeg")
|
||||||
std.header_flush()
|
std.header_flush()
|
||||||
end
|
end
|
||||||
|
|
||||||
function std.octstream(s)
|
function std.octstream(s)
|
||||||
std.header("Content-Type", "application/octet-stream")
|
std.header("Content-Type", "application/octet-stream")
|
||||||
std.header("Content-Disposition", 'attachment; filename="' .. s .. '"')
|
std.header("Content-Disposition", 'attachment; filename="' .. s .. '"')
|
||||||
std.header_flush()
|
std.header_flush()
|
||||||
end
|
end
|
||||||
--[[ function std.textstream()
|
|
||||||
std._textstream(HTTP_REQUEST.id)
|
|
||||||
end ]]
|
|
||||||
|
|
||||||
|
function std.is_file(f)
|
||||||
function std.readOnly(t) -- bugging
|
return ulib.is_dir(f) == false
|
||||||
local proxy = {}
|
|
||||||
local mt = { -- create metatable
|
|
||||||
__index = t,
|
|
||||||
__newindex = function (t,k,v)
|
|
||||||
error("attempt to update a read-only table", 2)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
setmetatable(proxy, mt)
|
|
||||||
return proxy
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- TODO provide web socket support
|
||||||
-- web socket
|
-- use coroutine to read socket message
|
||||||
std.ws = {}
|
std.ws = {}
|
||||||
function std.ws.header()
|
function std.ws.header()
|
||||||
local h = std.ws_header(HTTP_REQUEST.id)
|
local h = std.ws_header(HTTP_REQUEST.id)
|
||||||
@ -158,14 +192,6 @@ end
|
|||||||
function std.ws.close(code)
|
function std.ws.close(code)
|
||||||
std.ws_close(HTTP_REQUEST.id, code)
|
std.ws_close(HTTP_REQUEST.id, code)
|
||||||
end
|
end
|
||||||
function std.basename(str)
|
|
||||||
local name = string.gsub(std.trim(str,"/"), "(.*/)(.*)", "%2")
|
|
||||||
return name
|
|
||||||
end
|
|
||||||
function std.is_file(f)
|
|
||||||
return std.is_dir(f) == false
|
|
||||||
end
|
|
||||||
|
|
||||||
std.ws.TEXT = 1
|
std.ws.TEXT = 1
|
||||||
std.ws.BIN = 2
|
std.ws.BIN = 2
|
||||||
std.ws.CLOSE = 8
|
std.ws.CLOSE = 8
|
||||||
|
@ -5,7 +5,9 @@ function utils.is_array(table)
|
|||||||
local count = 0
|
local count = 0
|
||||||
for k, v in pairs(table) do
|
for k, v in pairs(table) do
|
||||||
if type(k) == "number" then
|
if type(k) == "number" then
|
||||||
if k > max then max = k end
|
if k > max then
|
||||||
|
max = k
|
||||||
|
end
|
||||||
count = count + 1
|
count = count + 1
|
||||||
else
|
else
|
||||||
return false
|
return false
|
||||||
@ -45,14 +47,20 @@ function utils.hex_to_char(x)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function utils.decodeURI(url)
|
function utils.decodeURI(url)
|
||||||
return url:gsub("%%(%x%x)", utils.hex_to_char)
|
return url:gsub("%%(%x%x)", utils.hex_to_char):gsub('+', ' ')
|
||||||
end
|
end
|
||||||
|
|
||||||
function utils.unescape(s)
|
function utils.unescape(s)
|
||||||
local str = ""
|
local str = ""
|
||||||
local escape = false
|
local escape = false
|
||||||
local esc_map = {b = '\b', f = '\f', n = '\n', r = '\r', t = '\t'}
|
local esc_map = {
|
||||||
for c in s:gmatch"." do
|
b = '\b',
|
||||||
|
f = '\f',
|
||||||
|
n = '\n',
|
||||||
|
r = '\r',
|
||||||
|
t = '\t'
|
||||||
|
}
|
||||||
|
for c in s:gsub("%%%%", "%%"):gmatch "." do
|
||||||
if c ~= '\\' then
|
if c ~= '\\' then
|
||||||
if escape then
|
if escape then
|
||||||
if esc_map[c] then
|
if esc_map[c] then
|
||||||
@ -78,7 +86,67 @@ end
|
|||||||
|
|
||||||
function utils.file_exists(name)
|
function utils.file_exists(name)
|
||||||
local f = io.open(name, "r")
|
local f = io.open(name, "r")
|
||||||
if f~=nil then io.close(f) return true else return false end
|
if f ~= nil then
|
||||||
|
io.close(f)
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function utils.parse_query(str, sep)
|
||||||
|
if not sep then
|
||||||
|
sep = '&'
|
||||||
|
end
|
||||||
|
|
||||||
|
local values = {}
|
||||||
|
for key, val in str:gmatch(string.format('([^%q=]+)(=*[^%q=]*)', sep, sep)) do
|
||||||
|
local key = utils.decodeURI(key)
|
||||||
|
local keys = {}
|
||||||
|
key = key:gsub('%[([^%]]*)%]', function(v)
|
||||||
|
-- extract keys between balanced brackets
|
||||||
|
if string.find(v, "^-?%d+$") then
|
||||||
|
v = tonumber(v)
|
||||||
|
else
|
||||||
|
v = utils.decodeURI(v)
|
||||||
|
end
|
||||||
|
table.insert(keys, v)
|
||||||
|
return "="
|
||||||
|
end)
|
||||||
|
key = key:gsub('=+.*$', "")
|
||||||
|
key = key:gsub('%s', "_") -- remove spaces in parameter name
|
||||||
|
val = val:gsub('^=+', "")
|
||||||
|
|
||||||
|
if not values[key] then
|
||||||
|
values[key] = {}
|
||||||
|
end
|
||||||
|
if #keys > 0 and type(values[key]) ~= 'table' then
|
||||||
|
values[key] = {}
|
||||||
|
elseif #keys == 0 and type(values[key]) == 'table' then
|
||||||
|
values[key] = utils.decodeURI(val)
|
||||||
|
elseif type(values[key]) == 'string' then
|
||||||
|
values[key] = {values[key]}
|
||||||
|
table.insert(values[key], utils.decodeURI(val))
|
||||||
|
end
|
||||||
|
|
||||||
|
local t = values[key]
|
||||||
|
for i, k in ipairs(keys) do
|
||||||
|
if type(t) ~= 'table' then
|
||||||
|
t = {}
|
||||||
|
end
|
||||||
|
if k == "" then
|
||||||
|
k = #t + 1
|
||||||
|
end
|
||||||
|
if not t[k] then
|
||||||
|
t[k] = {}
|
||||||
|
end
|
||||||
|
if i == #keys then
|
||||||
|
t[k] = val
|
||||||
|
end
|
||||||
|
t = t[k]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return values
|
||||||
end
|
end
|
||||||
|
|
||||||
function utils.url_parser(uri)
|
function utils.url_parser(uri)
|
||||||
@ -89,8 +157,14 @@ function utils.url_parser(uri)
|
|||||||
obj.port = uri:gsub(pattern, "%3")
|
obj.port = uri:gsub(pattern, "%3")
|
||||||
obj.query = uri:gsub(pattern, "%4")
|
obj.query = uri:gsub(pattern, "%4")
|
||||||
|
|
||||||
if obj.port == "" then obj.port = 80 else obj.port = tonumber(obj.port) end
|
if obj.port == "" then
|
||||||
if obj.query == "" then obj.query="/" end
|
obj.port = 80
|
||||||
|
else
|
||||||
|
obj.port = tonumber(obj.port)
|
||||||
|
end
|
||||||
|
if obj.query == "" then
|
||||||
|
obj.query = "/"
|
||||||
|
end
|
||||||
return obj
|
return obj
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -109,7 +183,9 @@ function JSON.encode(obj)
|
|||||||
else
|
else
|
||||||
local lst = {}
|
local lst = {}
|
||||||
local a = {}
|
local a = {}
|
||||||
for n in pairs(obj) do table.insert(a, n) end
|
for n in pairs(obj) do
|
||||||
|
table.insert(a, n)
|
||||||
|
end
|
||||||
table.sort(a)
|
table.sort(a)
|
||||||
for i, v in pairs(a) do
|
for i, v in pairs(a) do
|
||||||
table.insert(lst, JSON.encode(obj[v]))
|
table.insert(lst, JSON.encode(obj[v]))
|
||||||
@ -129,10 +205,14 @@ function JSON.encode(obj)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function explode(str, div) -- credit: http://richard.warburton.it
|
function explode(str, div) -- credit: http://richard.warburton.it
|
||||||
if (div=='') then return false end
|
if (div == '') then
|
||||||
|
return false
|
||||||
|
end
|
||||||
local pos, arr = 0, {}
|
local pos, arr = 0, {}
|
||||||
-- for each divider found
|
-- for each divider found
|
||||||
for st,sp in function() return string.find(str,div,pos,true) end do
|
for st, sp in function()
|
||||||
|
return string.find(str, div, pos, true)
|
||||||
|
end do
|
||||||
table.insert(arr, string.sub(str, pos, st - 1)) -- Attach chars left of current divider
|
table.insert(arr, string.sub(str, pos, st - 1)) -- Attach chars left of current divider
|
||||||
pos = sp + 1 -- Jump past current divider
|
pos = sp + 1 -- Jump past current divider
|
||||||
end
|
end
|
||||||
@ -147,7 +227,6 @@ function firstToUpper(str)
|
|||||||
return (str:gsub("^%l", string.upper))
|
return (str:gsub("^%l", string.upper))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local charset = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890"
|
local charset = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890"
|
||||||
|
|
||||||
function utils.generate_salt(length)
|
function utils.generate_salt(length)
|
||||||
@ -159,3 +238,12 @@ function utils.generate_salt(length)
|
|||||||
end
|
end
|
||||||
return table.concat(ret)
|
return table.concat(ret)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function utils.ext(path)
|
||||||
|
return path:match("%.([^%.]*)$")
|
||||||
|
end
|
||||||
|
|
||||||
|
function utils.basename(str)
|
||||||
|
local name = string.gsub(ulib.trim(str, "/"), "(.*/)(.*)", "%2")
|
||||||
|
return name
|
||||||
|
end
|
@ -2,26 +2,26 @@
|
|||||||
-- the rewrite rule for the framework
|
-- the rewrite rule for the framework
|
||||||
-- should be something like this
|
-- should be something like this
|
||||||
-- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query>
|
-- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query>
|
||||||
|
|
||||||
|
-- require needed library
|
||||||
|
BASE_FRW = ""
|
||||||
|
require(BASE_FRW.."api")
|
||||||
|
|
||||||
-- some global variables
|
-- some global variables
|
||||||
DIR_SEP = "/"
|
|
||||||
WWW_ROOT = "/opt/www/htdocs/apps"
|
WWW_ROOT = "/opt/www/htdocs/apps"
|
||||||
HTTP_ROOT = "https://apps.localhost:9195/"
|
HTTP_ROOT = "https://apps.localhost:9195/"
|
||||||
-- class path: path.to.class
|
|
||||||
BASE_FRW = ""
|
|
||||||
-- class path: path.to.class
|
-- class path: path.to.class
|
||||||
CONTROLLER_ROOT = BASE_FRW.."apps.controllers"
|
CONTROLLER_ROOT = BASE_FRW.."apps.controllers"
|
||||||
MODEL_ROOT = BASE_FRW.."apps.models"
|
MODEL_ROOT = BASE_FRW.."apps.models"
|
||||||
-- file path: path/to/file
|
-- file path: path/to/file
|
||||||
VIEW_ROOT = WWW_ROOT..DIR_SEP.."views"
|
VIEW_ROOT = WWW_ROOT..DIR_SEP.."views"
|
||||||
LOG_ROOT = WWW_ROOT..DIR_SEP.."logs"
|
|
||||||
|
|
||||||
-- require needed library
|
|
||||||
require(BASE_FRW.."silk.api")
|
|
||||||
|
|
||||||
-- registry object store global variables
|
-- registry object store global variables
|
||||||
local REGISTRY = {}
|
local REGISTRY = {}
|
||||||
-- set logging level
|
-- set logging level
|
||||||
REGISTRY.logger = Logger:new{ levels = {INFO = true, ERROR = true, DEBUG = true}}
|
REGISTRY.logger = Logger:new{ level = Logger.INFO }
|
||||||
REGISTRY.db = DBHelper:new{db="iosapps"}
|
REGISTRY.db = DBHelper:new{db="iosapps"}
|
||||||
REGISTRY.layout = 'default'
|
REGISTRY.layout = 'default'
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ REGISTRY.router = router
|
|||||||
router:setPath(CONTROLLER_ROOT)
|
router:setPath(CONTROLLER_ROOT)
|
||||||
--router:route('edit', 'post/edit', "ALL" )
|
--router:route('edit', 'post/edit', "ALL" )
|
||||||
|
|
||||||
-- example of depedencies to the current main route
|
-- example of dependencies to the current main route
|
||||||
-- each layout may have different dependencies
|
-- each layout may have different dependencies
|
||||||
local default_routes_dependencies = {
|
local default_routes_dependencies = {
|
||||||
edit = {
|
edit = {
|
||||||
|
3
test/ad.ls
Normal file
3
test/ad.ls
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<?lua
|
||||||
|
echo(ad)
|
||||||
|
?>
|
3
test/detail.ls
Normal file
3
test/detail.ls
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<?lua
|
||||||
|
echo("Post ID:", data.id, "\n", "Content:", data.content,"\n")
|
||||||
|
?>
|
4
test/layout.ls
Normal file
4
test/layout.ls
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?lua
|
||||||
|
__main__:render()
|
||||||
|
ad:render()
|
||||||
|
?>
|
46
test/lunit.lua
Normal file
46
test/lunit.lua
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
TESTS = {}
|
||||||
|
|
||||||
|
function test(description, fn)
|
||||||
|
TESTS[#TESTS + 1] = {description = description, test = fn }
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function run()
|
||||||
|
local report = {
|
||||||
|
ok = 0,
|
||||||
|
fail= 0,
|
||||||
|
total = #TESTS
|
||||||
|
}
|
||||||
|
for l,ts in ipairs(TESTS) do
|
||||||
|
io.write(string.format("Executing: %s...",ts.description))
|
||||||
|
local status,err = pcall(ts.test)
|
||||||
|
if status then
|
||||||
|
io.write("\27[32mOK\27[0m\n")
|
||||||
|
report.ok = report.ok + 1
|
||||||
|
else
|
||||||
|
io.write("\27[31mFAIL\27[0m\n")
|
||||||
|
print(err)
|
||||||
|
report.fail = report.fail + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
print("----------------------------")
|
||||||
|
print(string.format("Total tests: %d", report.total))
|
||||||
|
print(string.format("Tests passed: %d", report.ok))
|
||||||
|
print(string.format("Tests failed: %d", report.fail))
|
||||||
|
TESTS = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function assert(b, e,...)
|
||||||
|
if not b then
|
||||||
|
error(string.format(e,...))
|
||||||
|
print(debug.traceback())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function expect(v1,v2)
|
||||||
|
assert(v1 == v2, "Expect: [%s] get: [%s]", tostring(v2), tostring(v1))
|
||||||
|
end
|
||||||
|
|
||||||
|
function unexpect(v1,v2)
|
||||||
|
assert(v1 ~= v2, "Unexpect value", tostring(v2))
|
||||||
|
end
|
37
test/request.json
Normal file
37
test/request.json
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"PATH_INFO": "luacgi/lua/test.lua",
|
||||||
|
"REDIRECT_STATUS": "200",
|
||||||
|
"SCRIPT_NAME": "test.lua",
|
||||||
|
"HTTP_ACCEPT_ENCODING": "gzip, deflate",
|
||||||
|
"DOCUMENT_ROOT": "/tmp/www",
|
||||||
|
"REMOTE_ADDR": "192.168.1.44",
|
||||||
|
"REQUEST_URI": "/luacgi/lua/test.lua?r=1&id=3&name=John",
|
||||||
|
"SERVER_PROTOCOL": "HTTP/1.1",
|
||||||
|
"SERVER_NAME": "Antd",
|
||||||
|
"PATH_TRANSLATED": "/opt/www/htdocs/lua/test.lua",
|
||||||
|
"RAW_DATA": "firstname=Dany&lastname=LE&form_submitted=1",
|
||||||
|
"CONTENT_TYPE": "application/x-www-form-urlencoded",
|
||||||
|
"HTTP_ORIGIN": "http://192.168.1.27",
|
||||||
|
"HTTP_ACCEPT": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
|
||||||
|
"REMOTE_HOST": "192.168.1.44",
|
||||||
|
"HTTP_ACCEPT_LANGUAGE": "en-US,en;q=0.9",
|
||||||
|
"HTTP_CONTENT_LENGTH": "43",
|
||||||
|
"HTTP_CONNECTION": "keep-alive",
|
||||||
|
"HTTP_COOKIE": "PHPSESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1",
|
||||||
|
"LIB_DIR": "/tmp/lib",
|
||||||
|
"REQUEST_METHOD": "POST",
|
||||||
|
"HTTP_REFERER": "http://192.168.1.27/php/sign.html",
|
||||||
|
"SERVER_SOFTWARE": "Antd",
|
||||||
|
"QUERY_STRING": "r=post/id/1&id=3&name=John",
|
||||||
|
"TMP_DIR": "/tmp",
|
||||||
|
"HTTP_USER_AGENT": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36",
|
||||||
|
"HTTP_CONTENT_TYPE": "application/x-www-form-urlencoded",
|
||||||
|
"HTTP_UPGRADE_INSECURE_REQUESTS": "1",
|
||||||
|
"CONTENT_LENGTH": "43",
|
||||||
|
"GATEWAY_INTERFACE": "CGI/1.1",
|
||||||
|
"HTTP_HOST": "192.168.1.27",
|
||||||
|
"SERVER_PORT": "80",
|
||||||
|
"SCRIPT_FILENAME": "/opt/www/htdocs/lua/test.lua",
|
||||||
|
"DB_DIR": "/tmp",
|
||||||
|
"HTTP_CACHE_CONTROL": "max-age=0"
|
||||||
|
}
|
396
test/test_core.lua
Normal file
396
test/test_core.lua
Normal file
@ -0,0 +1,396 @@
|
|||||||
|
--- the binary shall be compiled with
|
||||||
|
--- make CFLAGS=-DLUA_SLICE_MAGIC=0x8AD73B9F
|
||||||
|
--- otherwise the tests unable to load C modules
|
||||||
|
|
||||||
|
require("lunit")
|
||||||
|
|
||||||
|
package.cpath = "/tmp/lib/lua/?.so"
|
||||||
|
package.path = ""
|
||||||
|
|
||||||
|
test("IO setup", function()
|
||||||
|
fcgio = { OUTPUT = "",
|
||||||
|
LOG = {
|
||||||
|
INFO = "",
|
||||||
|
ERROR = "",
|
||||||
|
DEBUG = "",
|
||||||
|
WARN = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function fcgio:flush()
|
||||||
|
fcgio.OUTPUT = ""
|
||||||
|
end
|
||||||
|
function fcgio:echo(...)
|
||||||
|
local args = table.pack(...)
|
||||||
|
for i=1,args.n do
|
||||||
|
-- do something with args[i], careful, it might be nil!
|
||||||
|
fcgio.OUTPUT = fcgio.OUTPUT..tostring(args[i])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
function fcgio:log_info(fmt,...)
|
||||||
|
fcgio.LOG.INFO = string.format(fmt,...)
|
||||||
|
io.stderr:write("INFO: ", fcgio.LOG.INFO)
|
||||||
|
io.stderr:write("\n")
|
||||||
|
end
|
||||||
|
function fcgio:log_error(fmt,...)
|
||||||
|
fcgio.LOG.ERROR = string.format(fmt,...)
|
||||||
|
io.stderr:write("ERROR: ",fcgio.LOG.ERROR)
|
||||||
|
io.stderr:write("\n")
|
||||||
|
end
|
||||||
|
function fcgio:log_debug(fmt,...)
|
||||||
|
fcgio.LOG.DEBUG = string.format(fmt,...)
|
||||||
|
io.stderr:write("DEBUG: ", fcgio.LOG.DEBUG)
|
||||||
|
io.stderr:write("\n")
|
||||||
|
end
|
||||||
|
function fcgio:log_warn(fmt,...)
|
||||||
|
fcgio.LOG.WARN = string.format(fmt,...)
|
||||||
|
io.stderr:write("WARN: ", fcgio.LOG.WARN)
|
||||||
|
io.stderr:write("\n")
|
||||||
|
end
|
||||||
|
function fcgio:send_file(path)
|
||||||
|
local f = io.open(path, "rb")
|
||||||
|
local content = f:read("*all")
|
||||||
|
f:close()
|
||||||
|
fcgio.OUTPUT = fcgio.OUTPUT..content
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("Setup request", function()
|
||||||
|
local json = require("json")
|
||||||
|
_SERVER = json.decodeFile("request.json")
|
||||||
|
assert(_SERVER ~= nil, "Global _SERVER object not found")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("SEVER PATH", function()
|
||||||
|
expect(_SERVER["LIB_DIR"], "/tmp/lib")
|
||||||
|
expect(_SERVER["TMP_DIR"], "/tmp")
|
||||||
|
expect(_SERVER["DB_DIR"], "/tmp")
|
||||||
|
expect(_SERVER["DOCUMENT_ROOT"], "/tmp/www")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("Import the hook", function()
|
||||||
|
package.path = "../silkmvc/?.lua"
|
||||||
|
local ret = require("core.hook")
|
||||||
|
expect(ret, true)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("Lua path", function()
|
||||||
|
expect(package.cpath, "/tmp/lib/lua/?.so")
|
||||||
|
expect(package.path, "/tmp/lib/lua/?.lua;/tmp/www/?.lua")
|
||||||
|
unexpect(ulib, nil)
|
||||||
|
unexpect(utils, nil)
|
||||||
|
unexpect(std, nil)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("HTTP Headers", function()
|
||||||
|
expect(HEADER["mobile"], false)
|
||||||
|
for k,v in pairs(_SERVER) do
|
||||||
|
if k:match("^HTTP_.*") then
|
||||||
|
local key = (k:gsub("HTTP_",""):gsub("_","-")):lower()
|
||||||
|
expect(HEADER[key],v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("HTTP request", function()
|
||||||
|
expect(REQUEST.method, "POST")
|
||||||
|
expect(REQUEST.r, "post/id/1")
|
||||||
|
expect(REQUEST.id, "3")
|
||||||
|
expect(REQUEST.name, "John")
|
||||||
|
expect(REQUEST.firstname, "Dany")
|
||||||
|
expect(REQUEST.lastname, "LE")
|
||||||
|
expect(REQUEST.form_submitted, "1")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('HTTP COOKIE', function()
|
||||||
|
unexpect(SESSION, nil)
|
||||||
|
expect(SESSION.PHPSESSID, "298zf09hf012fh2")
|
||||||
|
expect(SESSION.csrftoken, "u32t4o3tb3gg43")
|
||||||
|
expect(SESSION._gat, "1")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("Echo", function()
|
||||||
|
echo("Hello ", "World: ", 10, true)
|
||||||
|
expect(fcgio.OUTPUT, "Hello World: 10true")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("STD response", function()
|
||||||
|
std.status(500)
|
||||||
|
expect(RESPONSE_HEADER.status, 500)
|
||||||
|
std.header("Content-Type", "text/html")
|
||||||
|
expect(RESPONSE_HEADER.header["Content-Type"], "text/html")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("STD Error", function()
|
||||||
|
fcgio:flush()
|
||||||
|
std.error(404, "No page found")
|
||||||
|
expect(fcgio.OUTPUT, "Status: 404 Not Found\r\nContent-Type: text/html\r\n\r\n<HTML><HEAD><TITLE>No page found</TITLE></HEAD><BODY><h2>No page found</h2></BODY></HTML>")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("STD header with cookie", function()
|
||||||
|
RESPONSE_HEADER.sent = false
|
||||||
|
fcgio:flush()
|
||||||
|
|
||||||
|
std.status(200)
|
||||||
|
std.header("Content-Type", "text/html")
|
||||||
|
std.setCookie("sessionid=12345;user=dany; path=/")
|
||||||
|
std.setCookie("date=now", "_gcat=1")
|
||||||
|
--print(JSON.encode(RESPONSE_HEADER))
|
||||||
|
std.header_flush()
|
||||||
|
echo("hello")
|
||||||
|
expect(fcgio.OUTPUT, "Status: 200 OK\r\nContent-Type: text/html\r\nSet-Cookie: sessionid=12345;user=dany; path=/\r\nSet-Cookie: date=now;_gcat=1\r\n\r\nhello")
|
||||||
|
end)
|
||||||
|
--- mimes test
|
||||||
|
test("STD Mime", function()
|
||||||
|
expect(std.mimeOf("request.json"), "application/json")
|
||||||
|
expect(std.mimeOf("test.exe"), "application/octet-stream")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("STD send file", function()
|
||||||
|
RESPONSE_HEADER.sent = false
|
||||||
|
fcgio:flush()
|
||||||
|
std.sendFile("request.json")
|
||||||
|
print(fcgio.OUTPUT)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("utils.is_array", function()
|
||||||
|
local tb = { name = "Dany", test = true}
|
||||||
|
expect(utils.is_array(tb), false)
|
||||||
|
local arr = {[1] = "Dany", [2] = true}
|
||||||
|
expect(utils.is_array(arr), true)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("utils.escape and utils.unescape", function()
|
||||||
|
local before = 'this is a escape string \\ " % \n \t \r'
|
||||||
|
local escaped = utils.escape(before)
|
||||||
|
expect(escaped, 'this is a escape string \\\\ \\" %% \\n \\t \\r')
|
||||||
|
expect(utils.unescape(escaped), before)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("utils.decodeURI", function()
|
||||||
|
local uri = "https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B"
|
||||||
|
local decoded = utils.decodeURI(uri)
|
||||||
|
expect(decoded, "https://mozilla.org/?x=шеллы")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("utils.file_exists", function()
|
||||||
|
expect(utils.file_exists("request.json"), true)
|
||||||
|
expect(utils.file_exists("test1.json"), false)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("utils.parse_query", function()
|
||||||
|
local query = "r=1&id=3&name=John&desc=some%20thing&enc=this+is+encode"
|
||||||
|
local tb = utils.parse_query(query)
|
||||||
|
expect(tb.r, "1")
|
||||||
|
expect(tb.id, "3")
|
||||||
|
expect(tb.desc, "some thing")
|
||||||
|
expect(tb.enc, "this is encode")
|
||||||
|
expect(tb.name, "John")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("utils.url_parser", function()
|
||||||
|
local uri = "https://mozilla.org:9000/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B"
|
||||||
|
local obj = utils.url_parser(uri)
|
||||||
|
expect(obj.query, "/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B")
|
||||||
|
expect(obj.hostname, "mozilla.org")
|
||||||
|
expect(obj.protocol, "https")
|
||||||
|
expect(obj.port, 9000)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("utils explode/implode", function()
|
||||||
|
local str = "this is a test"
|
||||||
|
tbl = explode(str, " ")
|
||||||
|
expect(tbl[1], "this")
|
||||||
|
expect(tbl[2], "is")
|
||||||
|
expect(tbl[3], "a")
|
||||||
|
expect(tbl[4], "test")
|
||||||
|
local str1 = implode(tbl, "|")
|
||||||
|
expect(str1, "this|is|a|test")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("utils firstToUpper", function()
|
||||||
|
local str = "this is a test"
|
||||||
|
expect(firstToUpper(str), "This is a test")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("utils.ext", function()
|
||||||
|
expect(utils.ext("foo.bar"), "bar")
|
||||||
|
expect(utils.ext("foo.bar.baz"), "baz")
|
||||||
|
expect(utils.ext("foo"), nil)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("utils.basename", function()
|
||||||
|
expect(utils.basename("path/to/foo.bar"), "foo.bar")
|
||||||
|
end)
|
||||||
|
--- Test for sqlite database
|
||||||
|
test("sqlite.getdb", function()
|
||||||
|
require("silk.core.sqlite")
|
||||||
|
local path = "/tmp/test.db"
|
||||||
|
local db = sqlite.getdb("/tmp/test.db")
|
||||||
|
sqlite.dbclose(db)
|
||||||
|
expect(ulib.exists(path), true)
|
||||||
|
|
||||||
|
db = sqlite.getdb("system")
|
||||||
|
sqlite.dbclose(db)
|
||||||
|
expect(ulib.exists("/tmp/system.db"), true)
|
||||||
|
|
||||||
|
ulib.delete("/tmp/secret.db")
|
||||||
|
expect(ulib.exists("/tmp/secret.db"), false)
|
||||||
|
DB = DBModel:new{db="secret"}
|
||||||
|
DB:open()
|
||||||
|
|
||||||
|
expect(ulib.exists("/tmp/secret.db"), true)
|
||||||
|
unexpect(DB.db, nil)
|
||||||
|
unexpect(DB.db,"secret")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("DBModel:createTable", function()
|
||||||
|
ret = DB:createTable("test", {
|
||||||
|
first_name = "TEXT NOT NULL",
|
||||||
|
last_name = "TEXT NOT NULL",
|
||||||
|
age = "INTEGER"
|
||||||
|
})
|
||||||
|
expect(ret, true)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("DBModel:available", function()
|
||||||
|
expect(DB:available("test"), true)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("DBModel:insert", function()
|
||||||
|
local data = {
|
||||||
|
first_name = "Dany",
|
||||||
|
last_name = "LE",
|
||||||
|
age = 30,
|
||||||
|
phone = "Unknown"
|
||||||
|
}
|
||||||
|
expect(DB:insert("test",data), false)
|
||||||
|
data.phone = nil
|
||||||
|
expect(DB:insert("test",data), true)
|
||||||
|
data = {
|
||||||
|
first_name = "Lisa",
|
||||||
|
last_name = "LE",
|
||||||
|
age = 5
|
||||||
|
}
|
||||||
|
expect(DB:insert("test",data), true)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("DBModel:lastInsertID", function()
|
||||||
|
local id = DB:lastInsertID()
|
||||||
|
expect(id, 2)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("DBModel:get", function()
|
||||||
|
local record = DB:get("test", 2)
|
||||||
|
expect(record.id, 2)
|
||||||
|
expect(record.first_name, "Lisa")
|
||||||
|
expect(record.last_name, "LE")
|
||||||
|
expect(record.age, 5)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("DBModel:getAll", function()
|
||||||
|
local records = DB:getAll("test")
|
||||||
|
expect(#records, 2)
|
||||||
|
|
||||||
|
expect(records[1].id, 1)
|
||||||
|
expect(records[1].first_name, "Dany")
|
||||||
|
expect(records[1].last_name, "LE")
|
||||||
|
expect(records[1].age, 30)
|
||||||
|
|
||||||
|
expect(records[2].id, 2)
|
||||||
|
expect(records[2].first_name, "Lisa")
|
||||||
|
expect(records[2].last_name, "LE")
|
||||||
|
expect(records[2].age, 5)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("DBModel:find", function()
|
||||||
|
local cond = {
|
||||||
|
exp = {
|
||||||
|
["and"] = {
|
||||||
|
{
|
||||||
|
["="] = {
|
||||||
|
first_name = "Dany"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
["="] = {
|
||||||
|
age = 25
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
local records = DB:find("test", cond)
|
||||||
|
expect(#records, 0)
|
||||||
|
|
||||||
|
cond.exp["and"][2]["="].age = 30
|
||||||
|
records = DB:find("test", cond)
|
||||||
|
expect(#records, 1)
|
||||||
|
|
||||||
|
cond = {
|
||||||
|
exp = {
|
||||||
|
["="] = {
|
||||||
|
last_name = "LE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
order = {
|
||||||
|
id = "DESC"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
records = DB:find("test", cond)
|
||||||
|
expect(#records, 2)
|
||||||
|
expect(records[1].id, 2)
|
||||||
|
expect(records[1].first_name, "Lisa")
|
||||||
|
expect(records[1].last_name, "LE")
|
||||||
|
expect(records[1].age, 5)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("DBModel:update", function()
|
||||||
|
local data = {
|
||||||
|
id = 1,
|
||||||
|
first_name = "Dany Xuan-Sang",
|
||||||
|
age = 35,
|
||||||
|
}
|
||||||
|
expect(DB:update("test", data), true)
|
||||||
|
local record = DB:get("test", 1)
|
||||||
|
unexpect(record, nil)
|
||||||
|
expect(record.age , 35)
|
||||||
|
expect(record.first_name, "Dany Xuan-Sang")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("DBModel:deleteByID", function()
|
||||||
|
expect(DB:deleteByID("test", 1), true)
|
||||||
|
local record = DB:get("test", 1)
|
||||||
|
expect(record, nil)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("DBModel:delete", function()
|
||||||
|
local cond = {
|
||||||
|
["="] = {
|
||||||
|
last_name = "LE"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(DB:delete("test", cond), true)
|
||||||
|
local records = DB:getAll("test")
|
||||||
|
expect(#records, 0)
|
||||||
|
end)
|
||||||
|
|
||||||
|
--- test enc module
|
||||||
|
test("Base64 encode/decode", function()
|
||||||
|
enc = require("enc")
|
||||||
|
local string = "this is the test"
|
||||||
|
local encode = enc.b64encode(string)
|
||||||
|
expect(encode,"dGhpcyBpcyB0aGUgdGVzdA==")
|
||||||
|
local buf = enc.b64decode(encode)
|
||||||
|
unexpect(buf,nil)
|
||||||
|
expect(tostring(buf), string)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("md5 encode", function()
|
||||||
|
expect(enc.md5("this is a test"), "54b0c58c7ce9f2a8b551351102ee0938")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("sha1 encode", function()
|
||||||
|
expect(enc.sha1("this is a test"), "fa26be19de6bff93f70bc2308434e4a440bbad02")
|
||||||
|
end)
|
||||||
|
--- run all unit tests
|
||||||
|
run()
|
270
test/test_silk.lua
Normal file
270
test/test_silk.lua
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
--- the binary shall be compiled with
|
||||||
|
--- make CFLAGS=-DLUA_SLICE_MAGIC=0x8AD73B9F
|
||||||
|
--- otherwise the tests unable to load C modules
|
||||||
|
|
||||||
|
require("lunit")
|
||||||
|
|
||||||
|
package.cpath = "/tmp/lib/lua/?.so"
|
||||||
|
package.path = "/tmp/lib/lua/?.lua"
|
||||||
|
|
||||||
|
test("IO setup", function()
|
||||||
|
fcgio = { OUTPUT = "",
|
||||||
|
LOG = {
|
||||||
|
INFO = "",
|
||||||
|
ERROR = "",
|
||||||
|
DEBUG = "",
|
||||||
|
WARN = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function fcgio:flush()
|
||||||
|
fcgio.OUTPUT = ""
|
||||||
|
fcgio.LOG.INFO = ""
|
||||||
|
fcgio.LOG.DEBUG = ""
|
||||||
|
fcgio.LOG.WARN = ""
|
||||||
|
fcgio.LOG.ERROR = ""
|
||||||
|
RESPONSE_HEADER.sent = false
|
||||||
|
end
|
||||||
|
function fcgio:echo(...)
|
||||||
|
local args = table.pack(...)
|
||||||
|
for i=1,args.n do
|
||||||
|
-- do something with args[i], careful, it might be nil!
|
||||||
|
fcgio.OUTPUT = fcgio.OUTPUT..tostring(args[i])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
function fcgio:log_info(fmt,...)
|
||||||
|
fcgio.LOG.INFO = string.format(fmt,...)
|
||||||
|
io.stderr:write("INFO: ", fcgio.LOG.INFO)
|
||||||
|
io.stderr:write("\n")
|
||||||
|
end
|
||||||
|
function fcgio:log_error(fmt,...)
|
||||||
|
fcgio.LOG.ERROR = string.format(fmt,...)
|
||||||
|
io.stderr:write("ERROR: ",fcgio.LOG.ERROR)
|
||||||
|
io.stderr:write("\n")
|
||||||
|
end
|
||||||
|
function fcgio:log_debug(fmt,...)
|
||||||
|
fcgio.LOG.DEBUG = string.format(fmt,...)
|
||||||
|
io.stderr:write("DEBUG: ", fcgio.LOG.DEBUG)
|
||||||
|
io.stderr:write("\n")
|
||||||
|
end
|
||||||
|
function fcgio:log_warn(fmt,...)
|
||||||
|
fcgio.LOG.WARN = string.format(fmt,...)
|
||||||
|
io.stderr:write("WARN: ", fcgio.LOG.WARN)
|
||||||
|
io.stderr:write("\n")
|
||||||
|
end
|
||||||
|
function fcgio:send_file(path)
|
||||||
|
local f = io.open(path, "rb")
|
||||||
|
local content = f:read("*all")
|
||||||
|
f:close()
|
||||||
|
fcgio.OUTPUT = fcgio.OUTPUT..content
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("Setup request", function()
|
||||||
|
local json = require("json")
|
||||||
|
_SERVER = json.decodeFile("request.json")
|
||||||
|
assert(_SERVER ~= nil, "Global _SERVER object not found")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("SEVER PATH", function()
|
||||||
|
expect(_SERVER["LIB_DIR"], "/tmp/lib")
|
||||||
|
expect(_SERVER["TMP_DIR"], "/tmp")
|
||||||
|
expect(_SERVER["DB_DIR"], "/tmp")
|
||||||
|
expect(_SERVER["DOCUMENT_ROOT"], "/tmp/www")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("Import the api", function()
|
||||||
|
local ret = require("silk.api")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("Lua path", function()
|
||||||
|
expect(package.cpath, "/tmp/lib/lua/?.so")
|
||||||
|
expect(package.path, "/tmp/lib/lua/?.lua;/tmp/www/?.lua")
|
||||||
|
unexpect(ulib, nil)
|
||||||
|
unexpect(utils, nil)
|
||||||
|
unexpect(std, nil)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("Logger", function()
|
||||||
|
local logger = Logger:new{ level = Logger.ERROR}
|
||||||
|
logger:info("Info message")
|
||||||
|
logger:debug("Debug message")
|
||||||
|
logger:warn("Warning message")
|
||||||
|
logger:error("Error message")
|
||||||
|
expect(fcgio.LOG.INFO, "")
|
||||||
|
expect(fcgio.LOG.DEBUG, "")
|
||||||
|
expect(fcgio.LOG.WARN, "")
|
||||||
|
expect(fcgio.LOG.ERROR, "Error message")
|
||||||
|
logger.level = Logger.INFO
|
||||||
|
|
||||||
|
logger:info("Info message")
|
||||||
|
logger:debug("Debug message")
|
||||||
|
logger:warn("Warning message")
|
||||||
|
logger:error("Error message")
|
||||||
|
expect(fcgio.LOG.ERROR, "Error message")
|
||||||
|
expect(fcgio.LOG.DEBUG, "")
|
||||||
|
expect(fcgio.LOG.WARN, "Warning message")
|
||||||
|
expect(fcgio.LOG.INFO, "Info message")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("BaseObject", function()
|
||||||
|
fcgio:flush()
|
||||||
|
local obj = BaseObject:new{ registry = {
|
||||||
|
logger = Logger:new{level = Logger.DEBUG}
|
||||||
|
} }
|
||||||
|
obj:info('Info message')
|
||||||
|
expect(fcgio.LOG.INFO, "Info message")
|
||||||
|
obj:debug("Debug message")
|
||||||
|
expect(fcgio.LOG.DEBUG, "Debug message")
|
||||||
|
obj:warn("Warning message")
|
||||||
|
expect(fcgio.LOG.WARN, "Warning message")
|
||||||
|
obj:print()
|
||||||
|
expect(fcgio.LOG.DEBUG, "BaseObject")
|
||||||
|
--obj:error("Error message")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("Silk define env", function()
|
||||||
|
DIR_SEP = "/"
|
||||||
|
BASE_FRW = ""
|
||||||
|
WWW_ROOT = "/tmp/www"
|
||||||
|
HTTP_ROOT = "https://apps.localhost:9195/"
|
||||||
|
CONTROLLER_ROOT = ""
|
||||||
|
-- class path: path.to.class
|
||||||
|
MODEL_ROOT = BASE_FRW
|
||||||
|
-- file path: path/to/file
|
||||||
|
VIEW_ROOT = WWW_ROOT
|
||||||
|
ulib.delete(WWW_ROOT)
|
||||||
|
expect(ulib.mkdir(WWW_ROOT), true)
|
||||||
|
expect(ulib.mkdir(WWW_ROOT.."/post"), true)
|
||||||
|
expect(ulib.send_file("request.json", WWW_ROOT.."/rq.json"), true)
|
||||||
|
expect(ulib.send_file("layout.ls", WWW_ROOT.."/layout.ls"), true)
|
||||||
|
expect(ulib.send_file("detail.ls", WWW_ROOT.."/post/detail.ls"), true)
|
||||||
|
expect(ulib.send_file("ad.ls", WWW_ROOT.."/post/ad.ls"), true)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("Define model", function()
|
||||||
|
BaseModel:subclass("NewsModel",{
|
||||||
|
registry = {},
|
||||||
|
name = "news",
|
||||||
|
fields = {
|
||||||
|
content = "TEXT"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
local REGISTRY = {}
|
||||||
|
ulib.delete("/tmp/news.db")
|
||||||
|
-- set logging level
|
||||||
|
REGISTRY.logger = Logger:new{ level = Logger.INFO }
|
||||||
|
REGISTRY.layout = '/'
|
||||||
|
REGISTRY.db = DBModel:new {db = "news"}
|
||||||
|
REGISTRY.db:open()
|
||||||
|
local model = NewsModel:new{registry = REGISTRY}
|
||||||
|
-- insert data
|
||||||
|
expect(model:create({content = "Hello HELL"}), true)
|
||||||
|
expect(model:create({content = "Goodbye"}), true)
|
||||||
|
local records = model:findAll()
|
||||||
|
expect(#records, 2)
|
||||||
|
expect(model:update({id =1, content = "Hello World"}), true)
|
||||||
|
expect(model:delete({ ["="] = {id = 2} }), true)
|
||||||
|
records = model:findAll()
|
||||||
|
expect(#records, 1)
|
||||||
|
local record = model:get(1)
|
||||||
|
unexpect(record, nil)
|
||||||
|
expect(record.content, "Hello World")
|
||||||
|
records = model:select("id as ID, content", "1=1")
|
||||||
|
unexpect(records, nil)
|
||||||
|
expect(#records, 1)
|
||||||
|
expect(records[1].ID,1)
|
||||||
|
REGISTRY.db:close()
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("Define controller", function()
|
||||||
|
BaseController:subclass("PostController",{
|
||||||
|
registry = {},
|
||||||
|
models = {"news"}
|
||||||
|
})
|
||||||
|
function PostController:id(n)
|
||||||
|
local record = self.news:get(n)
|
||||||
|
self.template:set("data", record)
|
||||||
|
--self.template:set("id", n)
|
||||||
|
self.template:setView("detail")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function PostController:ad()
|
||||||
|
self.template:set("ad", "AD HERE")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("Router infer controller", function()
|
||||||
|
local router = Router:new{}
|
||||||
|
local action = router:infer()
|
||||||
|
expect(action.controller.class, "PostController")
|
||||||
|
expect(action.action, "id")
|
||||||
|
expect(action.args[1], "1")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("Router infer asset", function()
|
||||||
|
fcgio:flush()
|
||||||
|
local router = Router:new{registry = {
|
||||||
|
fileaccess = true
|
||||||
|
}}
|
||||||
|
local action = router:infer("/rq.json")
|
||||||
|
expect(action.controller.class, "AssetController")
|
||||||
|
expect(action.action, "get")
|
||||||
|
expect(action.args[1], "rq.json")
|
||||||
|
local ret = router:call(action)
|
||||||
|
expect(ret, false)
|
||||||
|
io.stderr:write(fcgio.OUTPUT)
|
||||||
|
io.stderr:write("\n")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("Router fetch views with dependencies", function()
|
||||||
|
fcgio:flush()
|
||||||
|
local REGISTRY = {}
|
||||||
|
REGISTRY.db = DBModel:new {db = "news"}
|
||||||
|
REGISTRY.db:open()
|
||||||
|
-- set logging level
|
||||||
|
REGISTRY.logger = Logger:new{ level = Logger.INFO }
|
||||||
|
REGISTRY.layout = '/'
|
||||||
|
local default_routes_dependencies = {
|
||||||
|
ad = {
|
||||||
|
url = "post/ad",
|
||||||
|
visibility = {
|
||||||
|
shown = true,
|
||||||
|
routes = {
|
||||||
|
["post/id"] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
local router = Router:new{registry = REGISTRY}
|
||||||
|
router:route('/', default_routes_dependencies )
|
||||||
|
router:delegate()
|
||||||
|
REGISTRY.db:close()
|
||||||
|
expect(fcgio.OUTPUT, "Status: 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\nPost ID:1.0\nContent:Hello World\nAD HERE")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("Controller action not found", function()
|
||||||
|
fcgio:flush()
|
||||||
|
REQUEST.r = "/post/all"
|
||||||
|
local router = Router:new{registry = {}}
|
||||||
|
local s,e = pcall(router.delegate, router)
|
||||||
|
expect(s, false)
|
||||||
|
expect(fcgio.OUTPUT,"Status: 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n#action all is not found in controller PostController")
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("Controller not found", function()
|
||||||
|
fcgio:flush()
|
||||||
|
REQUEST.r = "/user/dany"
|
||||||
|
local REGISTRY = {}
|
||||||
|
-- set logging level
|
||||||
|
--REGISTRY.logger = Logger:new{ level = Logger.INFO }
|
||||||
|
REGISTRY.layout = '/'
|
||||||
|
local router = Router:new{registry = REGISTRY}
|
||||||
|
local s,e = pcall(router.delegate, router)
|
||||||
|
expect(s, false)
|
||||||
|
print(fcgio.OUTPUT)
|
||||||
|
end)
|
||||||
|
-- run all test
|
||||||
|
run()
|
Loading…
Reference in New Issue
Block a user