mirror of
https://github.com/lxsang/silk.git
synced 2024-11-13 00:38:23 +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/Logger.lua \
|
||||
silkmvc/BaseObject.lua \
|
||||
silkmvc/DBHelper.lua \
|
||||
silkmvc/api.lua
|
||||
|
||||
coredir = $(libdir)/lua/silk/core
|
||||
core_DATA = silkmvc/core/OOP.lua \
|
||||
silkmvc/core/std.lua \
|
||||
silkmvc/core/extra_mime.lua \
|
||||
silkmvc/core/mimes.lua \
|
||||
silkmvc/core/utils.lua \
|
||||
silkmvc/core/api.lua \
|
||||
silkmvc/core/cif.lua \
|
||||
silkmvc/core/hook.lua \
|
||||
silkmvc/core/sqlite.lua
|
||||
|
||||
# lua libraris & modules
|
||||
|
@ -208,7 +208,7 @@ static int l_base64_decode(lua_State *L)
|
||||
// decode data to a byte array
|
||||
lua_new_slice(L, len);
|
||||
slice_t *vec = NULL;
|
||||
vec = lua_check_slice(L, 2);
|
||||
vec = lua_check_slice(L, -1);
|
||||
len = Base64decode((char *)vec->data, s);
|
||||
vec->len = len;
|
||||
// lua_pushstring(L,dst);
|
||||
|
@ -14,15 +14,43 @@
|
||||
#define SLICE "slice"
|
||||
|
||||
typedef struct {
|
||||
size_t magic;
|
||||
size_t len;
|
||||
uint8_t* data;
|
||||
} 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)
|
||||
{
|
||||
size_t nbytes = sizeof(slice_t) + n * 1U;
|
||||
slice_t *a = (slice_t *)lua_newuserdata(L, nbytes);
|
||||
a->data = &((char *)a)[sizeof(slice_t)];
|
||||
a->magic = lua_slice_magic();
|
||||
luaL_getmetatable(L, SLICE);
|
||||
lua_setmetatable(L, -2);
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
#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)
|
||||
{
|
||||
@ -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);
|
||||
a->len = n;
|
||||
a->data = ptr;
|
||||
a->magic = lua_slice_magic();
|
||||
luaL_getmetatable(L, SLICE);
|
||||
lua_setmetatable(L, -2);
|
||||
}
|
||||
@ -78,6 +80,14 @@ static int l_slice_write(lua_State *L)
|
||||
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)
|
||||
{
|
||||
if(lua_isnumber(L,2))
|
||||
@ -91,10 +101,18 @@ static int l_slice_index(lua_State *L)
|
||||
{
|
||||
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);
|
||||
}
|
||||
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
|
||||
{
|
||||
lua_pushnil(L);
|
||||
@ -120,6 +138,16 @@ static int l_slice_to_string(lua_State *L)
|
||||
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[] = {
|
||||
{"unew", l_new_lightslice},
|
||||
{"new", l_new_slice},
|
||||
|
@ -83,6 +83,9 @@ static int l_db_query(lua_State *L)
|
||||
int cols = sqlite3_column_count(statement);
|
||||
int result = 0;
|
||||
int cnt = 1;
|
||||
uint8_t* data = NULL;
|
||||
size_t len = 0;
|
||||
slice_t* vec = NULL;
|
||||
// new table for data
|
||||
lua_newtable(L);
|
||||
while ((result = sqlite3_step(statement)) == SQLITE_ROW)
|
||||
@ -91,10 +94,37 @@ static int l_db_query(lua_State *L)
|
||||
lua_newtable(L);
|
||||
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);
|
||||
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);
|
||||
|
@ -41,11 +41,12 @@ function BaseController:switchLayout(name)
|
||||
if self.main then
|
||||
self.registry.layout = name
|
||||
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
|
||||
|
||||
function BaseController:setSession(key, value)
|
||||
|
||||
SESSION[key] = value
|
||||
end
|
||||
function BaseController:getSession(key)
|
||||
|
@ -30,9 +30,7 @@ function BaseModel:find(cond)
|
||||
end
|
||||
|
||||
function BaseModel:get(id)
|
||||
local data, order = self:find({exp = {["="] = {id = id}}})
|
||||
if not data or #order == 0 then return false end
|
||||
return data[1]
|
||||
return self.db:get(self.name, id)
|
||||
end
|
||||
|
||||
function BaseModel:findAll()
|
||||
@ -46,6 +44,7 @@ function BaseModel:query(sql)
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
|
@ -4,31 +4,34 @@ function BaseObject:subclass(name, args)
|
||||
_G[name].class = name
|
||||
end
|
||||
|
||||
function BaseObject:log(msg, level)
|
||||
level = level or "INFO"
|
||||
function BaseObject:log(level,msg,...)
|
||||
if self.registry.logger then
|
||||
self.registry.logger:log(msg,level)
|
||||
self.registry.logger:log(level, msg,...)
|
||||
end
|
||||
end
|
||||
|
||||
function BaseObject:debug(msg)
|
||||
self:log(msg, "DEBUG")
|
||||
function BaseObject:debug(msg,...)
|
||||
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
|
||||
|
||||
function BaseObject:print()
|
||||
print(self.class)
|
||||
self:debug(self.class)
|
||||
end
|
||||
|
||||
function BaseObject:error(msg, trace)
|
||||
function BaseObject:error(msg,...)
|
||||
html()
|
||||
--local line = debug.getinfo(1).currentline
|
||||
echo(msg)
|
||||
self:log(msg,"ERROR")
|
||||
if trace then
|
||||
debug.traceback=nil
|
||||
error(msg)
|
||||
else
|
||||
error(msg)
|
||||
end
|
||||
local emsg = string.format(msg or "ERROR",...)
|
||||
echo(emsg)
|
||||
self:log(Logger.ERROR, msg,...)
|
||||
error(emsg)
|
||||
return false
|
||||
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()
|
||||
end
|
||||
|
||||
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)
|
||||
if not self.level then
|
||||
self.level = Logger.INFO
|
||||
end
|
||||
end
|
||||
|
||||
function Logger:info(msg)
|
||||
self:log(msg, "INFO")
|
||||
function Logger:log(verb,msg,...)
|
||||
local level = verb
|
||||
if level > self.level then return end
|
||||
if level > Logger.DEBUG then
|
||||
level = Logger.DEBUG
|
||||
end
|
||||
Logger.handles[level](msg,...)
|
||||
end
|
||||
|
||||
function Logger:debug(msg)
|
||||
self:log(msg, "DEBUG")
|
||||
function Logger:info(msg,...)
|
||||
self:log(Logger.INFO, msg,...)
|
||||
end
|
||||
|
||||
function Logger:debug(msg,...)
|
||||
self:log(Logger.DEBUG, msg,...)
|
||||
end
|
||||
|
||||
function Logger:error(msg)
|
||||
self:log(msg, "ERROR")
|
||||
function Logger:error(msg,...)
|
||||
self:log(Logger.ERROR, msg,...)
|
||||
end
|
||||
|
||||
function Logger:warn(msg,...)
|
||||
self:log(Logger.WARN, msg,...)
|
||||
end
|
@ -7,6 +7,7 @@ end
|
||||
function Router:initialize()
|
||||
self.routes = {}
|
||||
self.remaps = {}
|
||||
self.path = CONTROLLER_ROOT
|
||||
end
|
||||
|
||||
--function Router:setArgs(args)
|
||||
@ -25,7 +26,7 @@ function Router:infer(url)
|
||||
-- if user dont provide the url, try to infer it
|
||||
-- from the REQUEST
|
||||
url = url or REQUEST.r or ""
|
||||
url = std.trim(url, "/")
|
||||
url = ulib.trim(url, "/")
|
||||
local args = explode(url, "/")
|
||||
local data = {
|
||||
name = "index",
|
||||
@ -79,7 +80,7 @@ function Router:infer(url)
|
||||
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
|
||||
end
|
||||
|
||||
@ -90,7 +91,7 @@ function Router:delegate()
|
||||
data.controller.main = true
|
||||
views.__main__ = self:call(data)
|
||||
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
|
||||
end
|
||||
-- get all visible routes
|
||||
@ -106,8 +107,8 @@ function Router:delegate()
|
||||
table.insert( view_args, k )
|
||||
table.insert( view_argv, v )
|
||||
end
|
||||
|
||||
local fn, e = loadscript(VIEW_ROOT .. DIR_SEP .. self.registry.layout .. DIR_SEP .. "layout.ls", view_args)
|
||||
local script_path = VIEW_ROOT .. DIR_SEP .. self.registry.layout .. DIR_SEP .. "layout.ls"
|
||||
local fn, e = loadscript(script_path, view_args)
|
||||
html()
|
||||
if fn then
|
||||
local r, o = pcall(fn, table.unpack(view_argv))
|
||||
@ -116,7 +117,7 @@ function Router:delegate()
|
||||
end
|
||||
else
|
||||
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
|
||||
|
||||
@ -125,9 +126,8 @@ function Router:dependencies(url)
|
||||
return {}
|
||||
end
|
||||
local list = {}
|
||||
--self:log("comparing "..url)
|
||||
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
|
||||
list[k] = v.url
|
||||
elseif v.visibility.routes then
|
||||
|
@ -1,57 +1,51 @@
|
||||
require("OOP")
|
||||
ulib = require("ulib")
|
||||
require(BASE_FRW.."silk.BaseObject")
|
||||
require(BASE_FRW.."silk.DBHelper")
|
||||
require(BASE_FRW.."silk.Router")
|
||||
require(BASE_FRW.."silk.BaseController")
|
||||
require(BASE_FRW.."silk.BaseModel")
|
||||
require(BASE_FRW.."silk.Logger")
|
||||
require(BASE_FRW.."silk.Template")
|
||||
require("silk.core.hook")
|
||||
require("silk.core.OOP")
|
||||
require("silk.core.sqlite")
|
||||
|
||||
require("silk.BaseObject")
|
||||
require("silk.Router")
|
||||
require("silk.BaseController")
|
||||
require("silk.BaseModel")
|
||||
require("silk.Logger")
|
||||
require("silk.Template")
|
||||
|
||||
DIR_SEP = "/"
|
||||
|
||||
-- mime type allows
|
||||
-- this will bypass the default server security
|
||||
-- the default list is from the server setting
|
||||
POLICY = {}
|
||||
POLICY.mimes = {
|
||||
["application/javascript"] = true,
|
||||
["image/bmp"] = true,
|
||||
["image/jpeg"] = true,
|
||||
["image/png"] = true,
|
||||
["text/css"] = true,
|
||||
["text/markdown"] = true,
|
||||
["text/csv"] = true,
|
||||
["application/pdf"] = true,
|
||||
["image/gif"] = true,
|
||||
["text/html"] = true,
|
||||
["application/json"] = true,
|
||||
["application/javascript"] = true,
|
||||
["image/x-portable-pixmap"] = true,
|
||||
["application/x-rar-compressed"] = true,
|
||||
["image/tiff"] = true,
|
||||
["application/x-tar"] = true,
|
||||
["text/plain"] = true,
|
||||
["application/x-font-ttf"] = true,
|
||||
["application/xhtml+xml"] = true,
|
||||
["application/xml"] = true,
|
||||
["application/zip"] = true,
|
||||
["image/svg+xml"] = true,
|
||||
["application/vnd.ms-fontobject"] = true,
|
||||
["application/x-font-woff"] = true,
|
||||
["application/x-font-otf"] = true,
|
||||
["audio/mpeg"] = true,
|
||||
["image/bmp"] = true,
|
||||
["image/jpeg"] = true,
|
||||
["image/png"] = true,
|
||||
["text/css"] = true,
|
||||
["text/markdown"] = true,
|
||||
["text/csv"] = true,
|
||||
["application/pdf"] = true,
|
||||
["image/gif"] = true,
|
||||
["text/html"] = true,
|
||||
["application/json"] = true,
|
||||
["application/javascript"] = true,
|
||||
["image/x-portable-pixmap"] = true,
|
||||
["application/x-rar-compressed"] = true,
|
||||
["image/tiff"] = true,
|
||||
["application/x-tar"] = true,
|
||||
["text/plain"] = true,
|
||||
["application/x-font-ttf"] = true,
|
||||
["application/xhtml+xml"] = true,
|
||||
["application/xml"] = true,
|
||||
["application/zip"] = true,
|
||||
["image/svg+xml"] = true,
|
||||
["application/vnd.ms-fontobject"] = true,
|
||||
["application/x-font-woff"] = true,
|
||||
["application/x-font-otf"] = true,
|
||||
["audio/mpeg"] = true
|
||||
|
||||
}
|
||||
|
||||
|
||||
HEADER_FLAG = false
|
||||
|
||||
function html()
|
||||
if not HEADER_FLAG then
|
||||
std.chtml(SESSION)
|
||||
HEADER_FLAG = true
|
||||
end
|
||||
if not RESPONSE_HEADER.sent then
|
||||
std.html()
|
||||
end
|
||||
end
|
||||
|
||||
function import(module)
|
||||
return require(BASE_FRW.."silk.api."..module)
|
||||
end
|
@ -1,40 +1,39 @@
|
||||
Object = {}
|
||||
|
||||
function Object:prototype(o)
|
||||
o = o or {} -- create table if user does not provide one
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
o = o or {} -- create table if user does not provide one
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
self.__tostring = o:tostring()
|
||||
return o
|
||||
end
|
||||
|
||||
function Object:new(o)
|
||||
local obj = self:prototype(o)
|
||||
obj:initialize()
|
||||
return obj
|
||||
local obj = self:prototype(o)
|
||||
obj:initialize()
|
||||
return obj
|
||||
end
|
||||
|
||||
function Object:tostring()
|
||||
return ""
|
||||
end
|
||||
|
||||
function Object:print()
|
||||
print('an Object')
|
||||
print(self:tostring())
|
||||
end
|
||||
|
||||
function Object:initialize()
|
||||
end
|
||||
|
||||
function Object:asJSON()
|
||||
return '{}'
|
||||
return '{}'
|
||||
end
|
||||
|
||||
function Object:inherit(o)
|
||||
return self:prototype(o)
|
||||
return self:prototype(o)
|
||||
end
|
||||
|
||||
|
||||
function Object:extends(o)
|
||||
return self:inherit(o)
|
||||
return self:inherit(o)
|
||||
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,157 +1,202 @@
|
||||
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
|
||||
DBModel = Object:inherit{db=nil, name=''}
|
||||
DBModel = Object:inherit{
|
||||
db = nil,
|
||||
}
|
||||
|
||||
function DBModel:createTable(m)
|
||||
if self:available() then return true end
|
||||
local sql = "CREATE TABLE "..self.name.."(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
|
||||
function DBModel:createTable(name, m)
|
||||
if self:available() then
|
||||
return true
|
||||
end
|
||||
local sql = "CREATE TABLE " .. name .. "(id INTEGER PRIMARY KEY"
|
||||
for k, v in pairs(m) do
|
||||
if k ~= "id" then
|
||||
sql = sql .. "," .. k .. " " .. v
|
||||
end
|
||||
end
|
||||
sql = sql .. ");"
|
||||
return self:exec(sql)
|
||||
end
|
||||
|
||||
function DBModel:insert(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)
|
||||
elseif type(v) == "boolean" then
|
||||
table.insert( values, v and 1 or 0 )
|
||||
else
|
||||
local t = "\""..v:gsub('"', '""').."\""
|
||||
table.insert(values,t)
|
||||
end
|
||||
end
|
||||
end
|
||||
local sql = "INSERT INTO "..self.name.." ("..table.concat(keys,',')..') VALUES ('
|
||||
sql = sql..table.concat(values,',')..');'
|
||||
return sqlite.query(self.db, sql) == 1
|
||||
function DBModel:insert(name, 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)
|
||||
elseif type(v) == "boolean" then
|
||||
table.insert(values, v and 1 or 0)
|
||||
else
|
||||
local t = "\"" .. v:gsub('"', '""') .. "\""
|
||||
table.insert(values, t)
|
||||
end
|
||||
end
|
||||
end
|
||||
local sql = "INSERT INTO " .. name .. " (" .. table.concat(keys, ',') .. ') VALUES ('
|
||||
sql = sql .. table.concat(values, ',') .. ');'
|
||||
return self:exec(sql)
|
||||
end
|
||||
|
||||
function DBModel:get(id)
|
||||
return sqlite.select(self.db, self.name, "*","id="..id)[1]
|
||||
function DBModel:get(name, id)
|
||||
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
|
||||
|
||||
function DBModel:getAll()
|
||||
--local sql = "SELECT * FROM "..self.name
|
||||
--return sqlite.select(self.db, self.name, "1=1")
|
||||
local data = sqlite.select(self.db, self.name, "*", "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
|
||||
function DBModel:getAll(name)
|
||||
local data = self:query( "SELECT * FROM " .. name)
|
||||
if not data 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 DBModel:find(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
|
||||
--print(cnd)
|
||||
local data = sqlite.select(self.db, self.name, 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
|
||||
function DBModel:find(name, 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
|
||||
-- print(cnd)
|
||||
local data = self:query( string.format("SELECT %s FROM %s WHERE %s", sel, name, 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 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
|
||||
|
||||
function DBModel:update(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)
|
||||
elseif type(v) == "boolean" then
|
||||
table.insert( lst, k.."="..(v and 1 or 0) )
|
||||
else
|
||||
table.insert(lst,k.."=\""..v:gsub('"', '""').."\"")
|
||||
end
|
||||
end
|
||||
local sql = "UPDATE "..self.name.." SET "..table.concat(lst,",").." WHERE id="..id..";"
|
||||
return sqlite.query(self.db, sql) == 1
|
||||
end
|
||||
return false
|
||||
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:available()
|
||||
return sqlite.hasTable(self.db, self.name) == 1
|
||||
function DBModel:update(name, 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)
|
||||
elseif type(v) == "boolean" then
|
||||
table.insert(lst, k .. "=" .. (v and 1 or 0))
|
||||
else
|
||||
table.insert(lst, k .. "=\"" .. v:gsub('"', '""') .. "\"")
|
||||
end
|
||||
end
|
||||
local sql = "UPDATE " .. name .. " SET " .. table.concat(lst, ",") .. " WHERE id=" .. id .. ";"
|
||||
return self:exec(sql)
|
||||
end
|
||||
return false
|
||||
end
|
||||
function DBModel:deleteByID(id)
|
||||
local sql = "DELETE FROM "..self.name.." WHERE id="..id..";"
|
||||
return sqlite.query(self.db, sql) == 1
|
||||
|
||||
function DBModel:available(name)
|
||||
local records = self:query(string.format("SELECT * FROM sqlite_master WHERE type='table' and name='%s'", name))
|
||||
return #records == 1
|
||||
end
|
||||
function DBModel:deleteByID(name, id)
|
||||
local sql = "DELETE FROM " .. name .. " WHERE id=" .. id .. ";"
|
||||
return self:exec(sql)
|
||||
end
|
||||
function DBModel: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
|
||||
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 DBModel:delete(cond)
|
||||
local sql = "DELETE FROM "..self.name.." WHERE "..self:gencond(cond)..";"
|
||||
return sqlite.query(self.db, sql) == 1
|
||||
function DBModel:delete(name, cond)
|
||||
local sql = "DELETE FROM " .. name .. " WHERE " .. self:gencond(cond) .. ";"
|
||||
return self:exec(sql)
|
||||
end
|
||||
|
||||
function DBModel:lastInsertID()
|
||||
return sqlite.lastInsertID(self.db)
|
||||
return sqlite.last_insert_id(self.db)
|
||||
end
|
||||
|
||||
function DBModel:close()
|
||||
if self.db then
|
||||
sqlite.dbclose(self.db)
|
||||
end
|
||||
if self.db then
|
||||
sqlite.dbclose(self.db)
|
||||
end
|
||||
end
|
||||
function DBModel:open()
|
||||
if self.db ~= nil then
|
||||
self.db = sqlite.getdb(self.db)
|
||||
end
|
||||
end
|
||||
if self.db ~= nil then
|
||||
self.db = sqlite.getdb(self.db)
|
||||
end
|
||||
end
|
||||
|
@ -1,171 +1,197 @@
|
||||
bytes = modules.bytes()
|
||||
array = modules.array()
|
||||
|
||||
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
|
||||
std = {}
|
||||
require("silk.core.mimes")
|
||||
|
||||
RESPONSE_HEADER = {
|
||||
status = 200,
|
||||
header = {},
|
||||
cookie = {},
|
||||
sent = false
|
||||
status = 200,
|
||||
header = {},
|
||||
cookie = {},
|
||||
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)
|
||||
RESPONSE_HEADER.status=code
|
||||
RESPONSE_HEADER.status = code
|
||||
end
|
||||
function std.custom_header(k,v)
|
||||
std.header(k,v)
|
||||
|
||||
function std.custom_header(k, v)
|
||||
std.header(k, v)
|
||||
end
|
||||
|
||||
function std.header(k, v)
|
||||
RESPONSE_HEADER.header[k] = v
|
||||
end
|
||||
|
||||
function std.header_flush()
|
||||
std._send_header(HTTP_REQUEST.id,RESPONSE_HEADER.status, RESPONSE_HEADER.header, RESPONSE_HEADER.cookie)
|
||||
RESPONSE_HEADER.sent = true
|
||||
-- send out status
|
||||
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
|
||||
-- send out cookie
|
||||
for key, val in ipairs(RESPONSE_HEADER.cookie) do
|
||||
echo("Set-Cookie: ", val, "\r\n")
|
||||
end
|
||||
echo("\r\n")
|
||||
RESPONSE_HEADER.sent = true
|
||||
RESPONSE_HEADER.header = {}
|
||||
RESPONSE_HEADER.cookie = {}
|
||||
end
|
||||
|
||||
function std.header(k,v)
|
||||
RESPONSE_HEADER.header[k] = v
|
||||
end
|
||||
|
||||
function std.cjson(ck)
|
||||
for k,v in pairs(ck) do
|
||||
std.setCookie(k.."="..v.."; Path=/")
|
||||
end
|
||||
std.header("Content-Type","application/json; charset=utf-8")
|
||||
std.header_flush()
|
||||
end
|
||||
function std.chtml(ck)
|
||||
for k,v in pairs(ck) do
|
||||
std.setCookie(k.."="..v.."; Path=/")
|
||||
end
|
||||
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
|
||||
|
||||
function std.setCookie(v)
|
||||
RESPONSE_HEADER.cookie[#RESPONSE_HEADER.cookie] = v
|
||||
function std.setCookie(...)
|
||||
local args = table.pack(...)
|
||||
cookie = table.concat(args,";")
|
||||
RESPONSE_HEADER.cookie[#RESPONSE_HEADER.cookie + 1] = cookie
|
||||
end
|
||||
|
||||
function std.error(status, msg)
|
||||
std._error(HTTP_REQUEST.id, status, msg)
|
||||
end
|
||||
--_upload
|
||||
--_route
|
||||
function std.unknow(s)
|
||||
std.error(404, "Unknown request")
|
||||
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
|
||||
|
||||
--_redirect
|
||||
--[[ function std.redirect(s)
|
||||
std._redirect(HTTP_REQUEST.id,s)
|
||||
end ]]
|
||||
function std.unknow(s)
|
||||
std.error(404, "Unknown request")
|
||||
end
|
||||
|
||||
function std.f(path)
|
||||
fcgio:send_file(path)
|
||||
end
|
||||
|
||||
function std.html()
|
||||
std.header("Content-Type","text/html; charset=utf-8")
|
||||
std.header_flush()
|
||||
std.header("Content-Type", "text/html; charset=utf-8")
|
||||
std.header_flush()
|
||||
end
|
||||
|
||||
function std.text()
|
||||
std.header("Content-Type","text/plain; charset=utf-8")
|
||||
std.header_flush()
|
||||
std.header("Content-Type", "text/plain; charset=utf-8")
|
||||
std.header_flush()
|
||||
end
|
||||
|
||||
function std.json()
|
||||
std.header("Content-Type","application/json; charset=utf-8")
|
||||
std.header_flush()
|
||||
std.header("Content-Type", "application/json; charset=utf-8")
|
||||
std.header_flush()
|
||||
end
|
||||
|
||||
function std.jpeg()
|
||||
std.header("Content-Type","image/jpeg")
|
||||
std.header_flush()
|
||||
std.header("Content-Type", "image/jpeg")
|
||||
std.header_flush()
|
||||
end
|
||||
|
||||
function std.octstream(s)
|
||||
std.header("Content-Type","application/octet-stream")
|
||||
std.header("Content-Disposition",'attachment; filename="'..s..'"')
|
||||
std.header_flush()
|
||||
std.header("Content-Type", "application/octet-stream")
|
||||
std.header("Content-Disposition", 'attachment; filename="' .. s .. '"')
|
||||
std.header_flush()
|
||||
end
|
||||
--[[ function std.textstream()
|
||||
std._textstream(HTTP_REQUEST.id)
|
||||
end ]]
|
||||
|
||||
function std.is_file(f)
|
||||
return ulib.is_dir(f) == false
|
||||
end
|
||||
|
||||
function std.readOnly(t) -- bugging
|
||||
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
|
||||
|
||||
|
||||
-- web socket
|
||||
-- TODO provide web socket support
|
||||
-- use coroutine to read socket message
|
||||
std.ws = {}
|
||||
function std.ws.header()
|
||||
local h = std.ws_header(HTTP_REQUEST.id)
|
||||
if(h) then
|
||||
return h --std.readOnly(h)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
local h = std.ws_header(HTTP_REQUEST.id)
|
||||
if (h) then
|
||||
return h -- std.readOnly(h)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
function std.ws.read(h)
|
||||
return std.ws_read(HTTP_REQUEST.id,h)
|
||||
return std.ws_read(HTTP_REQUEST.id, h)
|
||||
end
|
||||
function std.ws.swrite(s)
|
||||
std.ws_t(HTTP_REQUEST.id,s)
|
||||
std.ws_t(HTTP_REQUEST.id, s)
|
||||
end
|
||||
function std.ws.fwrite(s)
|
||||
std.ws_f(HTTP_REQUEST.id,s)
|
||||
std.ws_f(HTTP_REQUEST.id, s)
|
||||
end
|
||||
function std.ws.write_bytes(arr)
|
||||
std.ws_b(HTTP_REQUEST.id,arr)
|
||||
std.ws_b(HTTP_REQUEST.id, arr)
|
||||
end
|
||||
function std.ws.enable()
|
||||
return HTTP_REQUEST ~= nil and HTTP_REQUEST.request["__web_socket__"] == "1"
|
||||
return HTTP_REQUEST ~= nil and HTTP_REQUEST.request["__web_socket__"] == "1"
|
||||
end
|
||||
function std.ws.close(code)
|
||||
std.ws_close(HTTP_REQUEST.id,code)
|
||||
std.ws_close(HTTP_REQUEST.id, code)
|
||||
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.BIN = 2
|
||||
std.ws.CLOSE = 8
|
||||
|
@ -5,7 +5,9 @@ function utils.is_array(table)
|
||||
local count = 0
|
||||
for k, v in pairs(table) do
|
||||
if type(k) == "number" then
|
||||
if k > max then max = k end
|
||||
if k > max then
|
||||
max = k
|
||||
end
|
||||
count = count + 1
|
||||
else
|
||||
return false
|
||||
@ -19,143 +21,229 @@ function utils.is_array(table)
|
||||
end
|
||||
|
||||
function utils.escape(s)
|
||||
local replacements = {
|
||||
["\\"] = "\\\\" ,
|
||||
['"'] = '\\"',
|
||||
["\n"] = "\\n",
|
||||
["\t"] = "\\t",
|
||||
["\b"] = "\\b",
|
||||
["\f"] = "\\f",
|
||||
["\r"] = "\\r",
|
||||
["%"] = "%%"
|
||||
}
|
||||
return (s:gsub( "[\\'\"\n\t\b\f\r%%]", replacements ))
|
||||
local replacements = {
|
||||
["\\"] = "\\\\",
|
||||
['"'] = '\\"',
|
||||
["\n"] = "\\n",
|
||||
["\t"] = "\\t",
|
||||
["\b"] = "\\b",
|
||||
["\f"] = "\\f",
|
||||
["\r"] = "\\r",
|
||||
["%"] = "%%"
|
||||
}
|
||||
return (s:gsub("[\\'\"\n\t\b\f\r%%]", replacements))
|
||||
end
|
||||
|
||||
function utils.escape_pattern(s)
|
||||
return s:gsub("[%(%)%.%+%-%*%?%[%]%^%$%%]", "%%%1")
|
||||
return s:gsub("[%(%)%.%+%-%*%?%[%]%^%$%%]", "%%%1")
|
||||
end
|
||||
|
||||
function utils.unescape_pattern(s)
|
||||
return s:gsub( "[%%]", "%%%%")
|
||||
return s:gsub("[%%]", "%%%%")
|
||||
end
|
||||
|
||||
function utils.hex_to_char(x)
|
||||
return string.char(tonumber(x, 16))
|
||||
return string.char(tonumber(x, 16))
|
||||
end
|
||||
|
||||
|
||||
function utils.decodeURI(url)
|
||||
return url:gsub("%%(%x%x)", utils.hex_to_char)
|
||||
return url:gsub("%%(%x%x)", utils.hex_to_char):gsub('+', ' ')
|
||||
end
|
||||
|
||||
function utils.unescape(s)
|
||||
local str = ""
|
||||
local escape = false
|
||||
local esc_map = {b = '\b', f = '\f', n = '\n', r = '\r', t = '\t'}
|
||||
for c in s:gmatch"." do
|
||||
if c ~= '\\' then
|
||||
if escape then
|
||||
if esc_map[c] then
|
||||
str = str..esc_map[c]
|
||||
else
|
||||
str = str..c
|
||||
end
|
||||
else
|
||||
str = str..c
|
||||
end
|
||||
escape = false
|
||||
else
|
||||
if escape then
|
||||
str = str..c
|
||||
escape = false
|
||||
else
|
||||
escape = true
|
||||
end
|
||||
end
|
||||
end
|
||||
return str
|
||||
local str = ""
|
||||
local escape = false
|
||||
local esc_map = {
|
||||
b = '\b',
|
||||
f = '\f',
|
||||
n = '\n',
|
||||
r = '\r',
|
||||
t = '\t'
|
||||
}
|
||||
for c in s:gsub("%%%%", "%%"):gmatch "." do
|
||||
if c ~= '\\' then
|
||||
if escape then
|
||||
if esc_map[c] then
|
||||
str = str .. esc_map[c]
|
||||
else
|
||||
str = str .. c
|
||||
end
|
||||
else
|
||||
str = str .. c
|
||||
end
|
||||
escape = false
|
||||
else
|
||||
if escape then
|
||||
str = str .. c
|
||||
escape = false
|
||||
else
|
||||
escape = true
|
||||
end
|
||||
end
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
function utils.file_exists(name)
|
||||
local f=io.open(name,"r")
|
||||
if f~=nil then io.close(f) return true else return false end
|
||||
local f = io.open(name, "r")
|
||||
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
|
||||
|
||||
function utils.url_parser(uri)
|
||||
local pattern = "^(https?)://([%.%w]+):?(%d*)(/?[^#]*)#?.*$"
|
||||
local obj = {}
|
||||
obj.protocol = uri:gsub(pattern, "%1")
|
||||
obj.hostname = uri:gsub(pattern, "%2")
|
||||
obj.port = uri:gsub(pattern, "%3")
|
||||
obj.query = uri:gsub(pattern, "%4")
|
||||
|
||||
if obj.port == "" then obj.port = 80 else obj.port = tonumber(obj.port) end
|
||||
if obj.query == "" then obj.query="/" end
|
||||
return obj
|
||||
local pattern = "^(https?)://([%.%w]+):?(%d*)(/?[^#]*)#?.*$"
|
||||
local obj = {}
|
||||
obj.protocol = uri:gsub(pattern, "%1")
|
||||
obj.hostname = uri:gsub(pattern, "%2")
|
||||
obj.port = uri:gsub(pattern, "%3")
|
||||
obj.query = uri:gsub(pattern, "%4")
|
||||
|
||||
if obj.port == "" then
|
||||
obj.port = 80
|
||||
else
|
||||
obj.port = tonumber(obj.port)
|
||||
end
|
||||
if obj.query == "" then
|
||||
obj.query = "/"
|
||||
end
|
||||
return obj
|
||||
end
|
||||
|
||||
JSON = require("json")
|
||||
|
||||
function JSON.encode(obj)
|
||||
local t = type(obj)
|
||||
if t == 'table' then
|
||||
-- encode object
|
||||
if utils.is_array(obj) == false then
|
||||
local lst = {}
|
||||
for k,v in pairs(obj) do
|
||||
table.insert(lst,'"'..k..'":'..JSON.encode(v))
|
||||
end
|
||||
return "{"..table.concat(lst,",").."}"
|
||||
else
|
||||
local lst = {}
|
||||
local a = {}
|
||||
for n in pairs(obj) do table.insert(a, n) end
|
||||
table.sort(a)
|
||||
for i,v in pairs(a) do
|
||||
table.insert(lst,JSON.encode(obj[v]))
|
||||
end
|
||||
return "["..table.concat(lst,",").."]"
|
||||
end
|
||||
elseif t == 'string' then
|
||||
--print('"'..utils.escape(obj)..'"')
|
||||
return '"'..utils.escape(obj)..'"'
|
||||
elseif t == 'boolean' or t == 'number' then
|
||||
return tostring(obj)
|
||||
elseif obj == nil then
|
||||
return "null"
|
||||
else
|
||||
return '"'..tostring(obj)..'"'
|
||||
end
|
||||
local t = type(obj)
|
||||
if t == 'table' then
|
||||
-- encode object
|
||||
if utils.is_array(obj) == false then
|
||||
local lst = {}
|
||||
for k, v in pairs(obj) do
|
||||
table.insert(lst, '"' .. k .. '":' .. JSON.encode(v))
|
||||
end
|
||||
return "{" .. table.concat(lst, ",") .. "}"
|
||||
else
|
||||
local lst = {}
|
||||
local a = {}
|
||||
for n in pairs(obj) do
|
||||
table.insert(a, n)
|
||||
end
|
||||
table.sort(a)
|
||||
for i, v in pairs(a) do
|
||||
table.insert(lst, JSON.encode(obj[v]))
|
||||
end
|
||||
return "[" .. table.concat(lst, ",") .. "]"
|
||||
end
|
||||
elseif t == 'string' then
|
||||
-- print('"'..utils.escape(obj)..'"')
|
||||
return '"' .. utils.escape(obj) .. '"'
|
||||
elseif t == 'boolean' or t == 'number' then
|
||||
return tostring(obj)
|
||||
elseif obj == nil then
|
||||
return "null"
|
||||
else
|
||||
return '"' .. tostring(obj) .. '"'
|
||||
end
|
||||
end
|
||||
|
||||
function explode(str, div) -- credit: http://richard.warburton.it
|
||||
if (div=='') then return false end
|
||||
local pos,arr = 0,{}
|
||||
-- for each divider found
|
||||
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
|
||||
pos = sp + 1 -- Jump past current divider
|
||||
end
|
||||
table.insert(arr,string.sub(str,pos)) -- Attach chars right of last divider
|
||||
return arr
|
||||
end
|
||||
if (div == '') then
|
||||
return false
|
||||
end
|
||||
local pos, arr = 0, {}
|
||||
-- for each divider found
|
||||
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
|
||||
pos = sp + 1 -- Jump past current divider
|
||||
end
|
||||
table.insert(arr, string.sub(str, pos)) -- Attach chars right of last divider
|
||||
return arr
|
||||
end
|
||||
function implode(arr, div)
|
||||
return table.concat(arr,div)
|
||||
return table.concat(arr, div)
|
||||
end
|
||||
|
||||
function firstToUpper(str)
|
||||
return (str:gsub("^%l", string.upper))
|
||||
end
|
||||
|
||||
|
||||
local charset = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890"
|
||||
|
||||
function utils.generate_salt(length)
|
||||
local ret = {}
|
||||
local r
|
||||
for i = 1, length do
|
||||
r = math.random(1, #charset)
|
||||
table.insert(ret, charset:sub(r, r))
|
||||
end
|
||||
return table.concat(ret)
|
||||
local ret = {}
|
||||
local r
|
||||
for i = 1, length do
|
||||
r = math.random(1, #charset)
|
||||
table.insert(ret, charset:sub(r, r))
|
||||
end
|
||||
return table.concat(ret)
|
||||
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
|
||||
-- should be something like this
|
||||
-- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query>
|
||||
|
||||
-- require needed library
|
||||
BASE_FRW = ""
|
||||
require(BASE_FRW.."api")
|
||||
|
||||
-- some global variables
|
||||
DIR_SEP = "/"
|
||||
WWW_ROOT = "/opt/www/htdocs/apps"
|
||||
HTTP_ROOT = "https://apps.localhost:9195/"
|
||||
-- class path: path.to.class
|
||||
BASE_FRW = ""
|
||||
|
||||
-- class path: path.to.class
|
||||
CONTROLLER_ROOT = BASE_FRW.."apps.controllers"
|
||||
MODEL_ROOT = BASE_FRW.."apps.models"
|
||||
-- file path: path/to/file
|
||||
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
|
||||
local REGISTRY = {}
|
||||
-- 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.layout = 'default'
|
||||
|
||||
@ -31,7 +31,7 @@ REGISTRY.router = router
|
||||
router:setPath(CONTROLLER_ROOT)
|
||||
--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
|
||||
local default_routes_dependencies = {
|
||||
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