mirror of
https://github.com/lxsang/silk.git
synced 2025-07-25 02:10:06 +02:00
Add C based module + lua based API code (WIP)
This commit is contained in:
108
silkmvc/BaseController.lua
Normal file
108
silkmvc/BaseController.lua
Normal file
@ -0,0 +1,108 @@
|
||||
-- create class
|
||||
BaseObject:subclass("BaseController",
|
||||
{
|
||||
registry = {},
|
||||
models = {},
|
||||
main = false
|
||||
})
|
||||
|
||||
-- set the name here in each subclasses
|
||||
function BaseController:initialize()
|
||||
for k, v in pairs(self.models) do
|
||||
--- infer the class here
|
||||
local modelname = firstToUpper(v).."Model"
|
||||
local path = MODEL_ROOT.."."..modelname
|
||||
-- require it
|
||||
pcall(require, path)
|
||||
--require(controller_path)
|
||||
if not _G[modelname] then
|
||||
self:modelnotfound(v)
|
||||
else
|
||||
-- create new model object
|
||||
self[v] = _G[modelname]:new{registry = self.registry}
|
||||
end
|
||||
end
|
||||
-- create template
|
||||
self.template = Template:new{registry = self.registry}
|
||||
end
|
||||
|
||||
function BaseController:print(...)
|
||||
return self:actionnotfound("print")
|
||||
end
|
||||
|
||||
function BaseController:redirect(url)
|
||||
std.status(301, "Moved Permanently")
|
||||
std.custom_header("Content-Type","text/html")
|
||||
std.custom_header("Location", url)
|
||||
std.header_flush()
|
||||
end
|
||||
|
||||
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")
|
||||
end
|
||||
end
|
||||
|
||||
function BaseController:setSession(key, value)
|
||||
SESSION[key] = value
|
||||
end
|
||||
function BaseController:getSession(key)
|
||||
return SESSION[key]
|
||||
end
|
||||
function BaseController:removeSession(key)
|
||||
self:setSession(key, nil)
|
||||
end
|
||||
function BaseController:index(...)
|
||||
self:error("#index: subclasses responsibility")
|
||||
end
|
||||
|
||||
-- not found action
|
||||
function BaseController:actionnotfound(...)
|
||||
local args = {...}
|
||||
self:error("#action "..args[1].." is not found in controller "..self.class)
|
||||
end
|
||||
-- not found model
|
||||
function BaseController:modelnotfound(...)
|
||||
local args = {...}
|
||||
self:error("Model "..firstToUpper(args[1]).."Model is not found in controller "..self.class)
|
||||
end
|
||||
-- The not found controller
|
||||
|
||||
BaseController:subclass("NotfoundController",{ registry = {}, models = {} })
|
||||
|
||||
function NotfoundController:index(...)
|
||||
local args = {...}
|
||||
local error = args[2] or ""
|
||||
if self.template:path() then
|
||||
self.template:set("error", error)
|
||||
self.template:set("title", "404 not found")
|
||||
return true
|
||||
end
|
||||
self:error("404: Controller "..args[1].." not found : "..error)
|
||||
return false
|
||||
end
|
||||
|
||||
-- The asset controller for the static file
|
||||
BaseController:subclass("AssetController", {registry={}, models={}})
|
||||
function AssetController:index(...)
|
||||
local args = {...}
|
||||
return self:get(table.unpack(args))
|
||||
end
|
||||
|
||||
function AssetController:get(...)
|
||||
local path = WWW_ROOT..DIR_SEP..implode({...}, DIR_SEP)
|
||||
|
||||
if self.registry.fileaccess and ulib.exists(path) then
|
||||
local mime = std.mimeOf(path)
|
||||
if POLICY.mimes[mime] then
|
||||
std.sendFile(path)
|
||||
else
|
||||
self:error("Access forbidden: "..path)
|
||||
end
|
||||
else
|
||||
self:error("Asset file not found or access forbidden: "..path)
|
||||
end
|
||||
return false
|
||||
end
|
51
silkmvc/BaseModel.lua
Normal file
51
silkmvc/BaseModel.lua
Normal file
@ -0,0 +1,51 @@
|
||||
-- create class
|
||||
BaseObject:subclass("BaseModel", {registry = {}})
|
||||
|
||||
function BaseModel:initialize()
|
||||
self.db = self.registry.db
|
||||
if self.db and self.name and self.name ~= "" and self.fields and
|
||||
not self.db:available(self.name) then
|
||||
self.db:createTable(self.name, self.fields)
|
||||
end
|
||||
end
|
||||
|
||||
function BaseModel:create(m)
|
||||
if self.db and m then return self.db:insert(self.name, m) end
|
||||
return false
|
||||
end
|
||||
|
||||
function BaseModel:update(m)
|
||||
if self.db and m then return self.db:update(self.name, m) end
|
||||
return false
|
||||
end
|
||||
|
||||
function BaseModel:delete(cond)
|
||||
if self.db and cond then return self.db:delete(self.name, cond) end
|
||||
return false
|
||||
end
|
||||
|
||||
function BaseModel:find(cond)
|
||||
if self.db and cond then return self.db:find(self.name, cond) end
|
||||
return false
|
||||
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]
|
||||
end
|
||||
|
||||
function BaseModel:findAll()
|
||||
if self.db then return self.db:getAll(self.name) end
|
||||
return false
|
||||
end
|
||||
|
||||
function BaseModel:query(sql)
|
||||
if self.db then return self.db:query(sql) end
|
||||
return false
|
||||
end
|
||||
|
||||
function BaseModel:select(sel, sql_cnd)
|
||||
if self.db then return self.db:select(self.name, sel, sql_cnd) end
|
||||
return nil
|
||||
end
|
34
silkmvc/BaseObject.lua
Normal file
34
silkmvc/BaseObject.lua
Normal file
@ -0,0 +1,34 @@
|
||||
BaseObject = Object:extends{registry = {}, class="BaseObject"}
|
||||
function BaseObject:subclass(name, args)
|
||||
_G[name] = self:extends(args)
|
||||
_G[name].class = name
|
||||
end
|
||||
|
||||
function BaseObject:log(msg, level)
|
||||
level = level or "INFO"
|
||||
if self.registry.logger then
|
||||
self.registry.logger:log(msg,level)
|
||||
end
|
||||
end
|
||||
|
||||
function BaseObject:debug(msg)
|
||||
self:log(msg, "DEBUG")
|
||||
end
|
||||
|
||||
function BaseObject:print()
|
||||
print(self.class)
|
||||
end
|
||||
|
||||
function BaseObject:error(msg, trace)
|
||||
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
|
||||
return false
|
||||
end
|
144
silkmvc/DBHelper.lua
Normal file
144
silkmvc/DBHelper.lua
Normal file
@ -0,0 +1,144 @@
|
||||
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
|
30
silkmvc/Logger.lua
Normal file
30
silkmvc/Logger.lua
Normal file
@ -0,0 +1,30 @@
|
||||
Logger = Object:extends{levels = {}}
|
||||
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
||||
function Logger:info(msg)
|
||||
self:log(msg, "INFO")
|
||||
end
|
||||
|
||||
function Logger:debug(msg)
|
||||
self:log(msg, "DEBUG")
|
||||
end
|
||||
|
||||
|
||||
function Logger:error(msg)
|
||||
self:log(msg, "ERROR")
|
||||
end
|
164
silkmvc/Router.lua
Normal file
164
silkmvc/Router.lua
Normal file
@ -0,0 +1,164 @@
|
||||
--define the class
|
||||
BaseObject:subclass("Router", {registry = {}})
|
||||
function Router:setPath(path)
|
||||
self.path = path
|
||||
end
|
||||
|
||||
function Router:initialize()
|
||||
self.routes = {}
|
||||
self.remaps = {}
|
||||
end
|
||||
|
||||
--function Router:setArgs(args)
|
||||
-- self.args = args
|
||||
--end
|
||||
|
||||
--function Router:arg(name)
|
||||
-- return self.args[name]
|
||||
--end
|
||||
|
||||
function Router:infer(url)
|
||||
-- a controller is like this /a/b/c/d/e
|
||||
-- a is controller name
|
||||
-- b is action
|
||||
-- c,d,e is parameters
|
||||
-- if user dont provide the url, try to infer it
|
||||
-- from the REQUEST
|
||||
url = url or REQUEST.r or ""
|
||||
url = std.trim(url, "/")
|
||||
local args = explode(url, "/")
|
||||
local data = {
|
||||
name = "index",
|
||||
action = "index",
|
||||
args = {}
|
||||
}
|
||||
if args and #args > 0 and args[1] ~= "" then
|
||||
data.name = args[1]:gsub("%.", "")
|
||||
if args[2] then
|
||||
data.action = args[2]:gsub("%.", "")
|
||||
end
|
||||
for i = 3, #args do
|
||||
table.insert(data.args, args[i])
|
||||
end
|
||||
end
|
||||
|
||||
-- remap if needed
|
||||
if self.remaps[data.name] ~= nil then
|
||||
data.name = self.remaps[data.name]
|
||||
end
|
||||
-- find the controller class and init it
|
||||
local controller_name = firstToUpper(data.name) .. "Controller"
|
||||
local controller_path = self.path .. "." .. controller_name
|
||||
-- require the controller module
|
||||
-- ignore the error
|
||||
local r, e = pcall(require, controller_path)
|
||||
--require(controller_path)
|
||||
if not _G[controller_name] then
|
||||
-- verify if it is an asset
|
||||
url = url:gsub("/", DIR_SEP)
|
||||
local filepath = WWW_ROOT..DIR_SEP..url
|
||||
if ulib.exists(filepath) then -- and not std.is_dir(filepath)
|
||||
data.controller = AssetController:new {registry = self.registry}
|
||||
data.action = "get"
|
||||
data.name = "asset"
|
||||
data.args ={url}
|
||||
else
|
||||
-- let the notfound controller handle the error
|
||||
data.controller = NotfoundController:new {registry = self.registry}
|
||||
data.args = {controller_name, e}
|
||||
data.action = "index"
|
||||
data.name = "notfound"
|
||||
end
|
||||
else
|
||||
-- create the coresponding controller
|
||||
data.controller = _G[controller_name]:new {registry = self.registry}
|
||||
if not data.controller[data.action] then
|
||||
--data.args = {data.action}
|
||||
table.insert(data.args, 1, data.action)
|
||||
data.action = "actionnotfound"
|
||||
end
|
||||
end
|
||||
|
||||
self:log("Controller: " .. data.controller.class .. ", action: "..data.action..", args: ".. JSON.encode(data.args))
|
||||
return data
|
||||
end
|
||||
|
||||
function Router:delegate()
|
||||
local views = {}
|
||||
local data = self:infer()
|
||||
-- set the controller to the main controller
|
||||
data.controller.main = true
|
||||
views.__main__ = self:call(data)
|
||||
if not views.__main__ then
|
||||
--self:error("No view available for this action")
|
||||
return
|
||||
end
|
||||
-- get all visible routes
|
||||
local routes = self:dependencies(data.name .. "/" .. data.action)
|
||||
for k, v in pairs(routes) do
|
||||
data = self:infer(v)
|
||||
views[k] = self:call(data)
|
||||
end
|
||||
-- now require the main page to put the view
|
||||
local view_args = {}
|
||||
local view_argv = {}
|
||||
for k,v in pairs(views) do
|
||||
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)
|
||||
html()
|
||||
if fn then
|
||||
local r, o = pcall(fn, table.unpack(view_argv))
|
||||
if not r then
|
||||
self:error(o)
|
||||
end
|
||||
else
|
||||
e = e or ""
|
||||
self:error("The index page is not found for layout: " .. self.registry.layout..": "..e)
|
||||
end
|
||||
end
|
||||
|
||||
function Router:dependencies(url)
|
||||
if not self.routes[self.registry.layout] then
|
||||
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, "/")
|
||||
if v.visibility == "ALL" then
|
||||
list[k] = v.url
|
||||
elseif v.visibility.routes then
|
||||
if v.visibility.shown == true or v.visibility.shown == nil then
|
||||
if v.visibility.routes[url] then
|
||||
list[k] = v.url
|
||||
end
|
||||
else
|
||||
if not v.visibility.routes[url] then
|
||||
list[k] = v.url
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return list
|
||||
end
|
||||
|
||||
function Router:call(data)
|
||||
data.controller.template:setView(data.action, data.name)
|
||||
local obj = data.controller[data.action](data.controller, table.unpack(data.args))
|
||||
if obj then
|
||||
return data.controller.template
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function Router:remap(from, to)
|
||||
self.remaps[from] = to
|
||||
end
|
||||
|
||||
function Router:route(layout, dependencies)
|
||||
self.routes[layout] = dependencies
|
||||
end
|
58
silkmvc/Template.lua
Normal file
58
silkmvc/Template.lua
Normal file
@ -0,0 +1,58 @@
|
||||
-- create class
|
||||
BaseObject:subclass("Template",{registry = {}})
|
||||
|
||||
function Template:initialize()
|
||||
self.vars = {}
|
||||
end
|
||||
|
||||
function Template:set(k, v, ow)
|
||||
if not self.vars[k] or (self.vars[k] and ow) then
|
||||
self.vars[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
function Template:get(k)
|
||||
return self.vars[k]
|
||||
end
|
||||
|
||||
function Template:remove(k)
|
||||
self.vars[k] = nil
|
||||
end
|
||||
|
||||
-- infer view path
|
||||
function Template:setView(name, controller)
|
||||
self.name = name
|
||||
if controller then
|
||||
self.controller = controller
|
||||
end
|
||||
end
|
||||
function Template:path()
|
||||
local path = VIEW_ROOT..DIR_SEP..self.registry.layout..DIR_SEP..self.controller..DIR_SEP..self.name..".ls"
|
||||
if ulib.exists(path) then
|
||||
return path
|
||||
else
|
||||
return false, path
|
||||
end
|
||||
end
|
||||
-- render the page
|
||||
function Template:render()
|
||||
local path, err = self:path()
|
||||
if not path then
|
||||
return self:error("View not found: "..err)
|
||||
end
|
||||
local args = {}
|
||||
local argv = {}
|
||||
for k, v in pairs(self.vars) do
|
||||
table.insert( args, k )
|
||||
table.insert( argv,v )
|
||||
end
|
||||
local fn, e = loadscript(self:path(), args)
|
||||
if fn then
|
||||
local r,o = pcall(fn, table.unpack(argv))
|
||||
if not r then
|
||||
self:error(o)
|
||||
end
|
||||
else
|
||||
self:error(e)
|
||||
end
|
||||
end
|
57
silkmvc/api.lua
Normal file
57
silkmvc/api.lua
Normal file
@ -0,0 +1,57 @@
|
||||
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")
|
||||
|
||||
-- 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,
|
||||
|
||||
}
|
||||
|
||||
|
||||
HEADER_FLAG = false
|
||||
|
||||
function html()
|
||||
if not HEADER_FLAG then
|
||||
std.chtml(SESSION)
|
||||
HEADER_FLAG = true
|
||||
end
|
||||
end
|
||||
|
||||
function import(module)
|
||||
return require(BASE_FRW.."silk.api."..module)
|
||||
end
|
40
silkmvc/core/OOP.lua
Normal file
40
silkmvc/core/OOP.lua
Normal file
@ -0,0 +1,40 @@
|
||||
Object = {}
|
||||
|
||||
function Object:prototype(o)
|
||||
o = o or {} -- create table if user does not provide one
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end
|
||||
|
||||
function Object:new(o)
|
||||
local obj = self:prototype(o)
|
||||
obj:initialize()
|
||||
return obj
|
||||
end
|
||||
|
||||
function Object:print()
|
||||
print('an Object')
|
||||
end
|
||||
|
||||
function Object:initialize()
|
||||
end
|
||||
|
||||
function Object:asJSON()
|
||||
return '{}'
|
||||
end
|
||||
|
||||
function Object:inherit(o)
|
||||
return self:prototype(o)
|
||||
end
|
||||
|
||||
|
||||
function Object:extends(o)
|
||||
return self:inherit(o)
|
||||
end
|
||||
|
||||
Test = Object:inherit{dummy = 0}
|
||||
|
||||
function Test:toWeb()
|
||||
wio.t(self.dummy)
|
||||
end
|
260
silkmvc/core/api.lua
Normal file
260
silkmvc/core/api.lua
Normal file
@ -0,0 +1,260 @@
|
||||
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')
|
63
silkmvc/core/cif.lua
Normal file
63
silkmvc/core/cif.lua
Normal file
@ -0,0 +1,63 @@
|
||||
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
|
79
silkmvc/core/extra_mime.lua
Normal file
79
silkmvc/core/extra_mime.lua
Normal file
@ -0,0 +1,79 @@
|
||||
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
|
157
silkmvc/core/sqlite.lua
Normal file
157
silkmvc/core/sqlite.lua
Normal file
@ -0,0 +1,157 @@
|
||||
sqlite = modules.sqlite()
|
||||
|
||||
if sqlite == nil then return 0 end
|
||||
require("OOP")
|
||||
-- create class
|
||||
DBModel = Object:inherit{db=nil, name=''}
|
||||
|
||||
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
|
||||
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
|
||||
end
|
||||
|
||||
function DBModel:get(id)
|
||||
return sqlite.select(self.db, self.name, "*","id="..id)[1]
|
||||
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
|
||||
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
|
||||
end
|
||||
|
||||
function DBModel:query(sql)
|
||||
return sqlite.query(self.db, sql) == 1
|
||||
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
|
||||
end
|
||||
|
||||
function DBModel:available()
|
||||
return sqlite.hasTable(self.db, self.name) == 1
|
||||
end
|
||||
function DBModel:deleteByID(id)
|
||||
local sql = "DELETE FROM "..self.name.." WHERE id="..id..";"
|
||||
return sqlite.query(self.db, sql) == 1
|
||||
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
|
||||
end
|
||||
function DBModel:delete(cond)
|
||||
local sql = "DELETE FROM "..self.name.." WHERE "..self:gencond(cond)..";"
|
||||
return sqlite.query(self.db, sql) == 1
|
||||
end
|
||||
|
||||
function DBModel:lastInsertID()
|
||||
return sqlite.lastInsertID(self.db)
|
||||
end
|
||||
|
||||
function DBModel:close()
|
||||
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
|
171
silkmvc/core/std.lua
Normal file
171
silkmvc/core/std.lua
Normal file
@ -0,0 +1,171 @@
|
||||
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
|
||||
|
||||
RESPONSE_HEADER = {
|
||||
status = 200,
|
||||
header = {},
|
||||
cookie = {},
|
||||
sent = false
|
||||
}
|
||||
|
||||
function std.status(code)
|
||||
RESPONSE_HEADER.status=code
|
||||
end
|
||||
function std.custom_header(k,v)
|
||||
std.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
|
||||
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
|
||||
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")
|
||||
end
|
||||
|
||||
--_redirect
|
||||
--[[ function std.redirect(s)
|
||||
std._redirect(HTTP_REQUEST.id,s)
|
||||
end ]]
|
||||
|
||||
function std.html()
|
||||
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()
|
||||
end
|
||||
|
||||
function std.json()
|
||||
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()
|
||||
end
|
||||
function std.octstream(s)
|
||||
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.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
|
||||
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
|
||||
end
|
||||
|
||||
function std.ws.read(h)
|
||||
return std.ws_read(HTTP_REQUEST.id,h)
|
||||
end
|
||||
function std.ws.swrite(s)
|
||||
std.ws_t(HTTP_REQUEST.id,s)
|
||||
end
|
||||
function std.ws.fwrite(s)
|
||||
std.ws_f(HTTP_REQUEST.id,s)
|
||||
end
|
||||
function std.ws.write_bytes(arr)
|
||||
std.ws_b(HTTP_REQUEST.id,arr)
|
||||
end
|
||||
function std.ws.enable()
|
||||
return HTTP_REQUEST ~= nil and HTTP_REQUEST.request["__web_socket__"] == "1"
|
||||
end
|
||||
function std.ws.close(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
|
161
silkmvc/core/utils.lua
Normal file
161
silkmvc/core/utils.lua
Normal file
@ -0,0 +1,161 @@
|
||||
utils = {}
|
||||
|
||||
function utils.is_array(table)
|
||||
local max = 0
|
||||
local count = 0
|
||||
for k, v in pairs(table) do
|
||||
if type(k) == "number" then
|
||||
if k > max then max = k end
|
||||
count = count + 1
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
if max > count * 2 then
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
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 ))
|
||||
end
|
||||
|
||||
function utils.escape_pattern(s)
|
||||
return s:gsub("[%(%)%.%+%-%*%?%[%]%^%$%%]", "%%%1")
|
||||
end
|
||||
|
||||
function utils.unescape_pattern(s)
|
||||
return s:gsub( "[%%]", "%%%%")
|
||||
end
|
||||
|
||||
function utils.hex_to_char(x)
|
||||
return string.char(tonumber(x, 16))
|
||||
end
|
||||
|
||||
function utils.decodeURI(url)
|
||||
return url:gsub("%%(%x%x)", utils.hex_to_char)
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
function implode(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)
|
||||
end
|
54
silkmvc/router.lua.tpl
Normal file
54
silkmvc/router.lua.tpl
Normal file
@ -0,0 +1,54 @@
|
||||
|
||||
-- the rewrite rule for the framework
|
||||
-- should be something like this
|
||||
-- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query>
|
||||
-- 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.db = DBHelper:new{db="iosapps"}
|
||||
REGISTRY.layout = 'default'
|
||||
|
||||
REGISTRY.db:open()
|
||||
local router = Router:new{registry = REGISTRY}
|
||||
REGISTRY.router = router
|
||||
router:setPath(CONTROLLER_ROOT)
|
||||
--router:route('edit', 'post/edit', "ALL" )
|
||||
|
||||
-- example of depedencies to the current main route
|
||||
-- each layout may have different dependencies
|
||||
local default_routes_dependencies = {
|
||||
edit = {
|
||||
url = "post/edit",
|
||||
visibility = {
|
||||
shown = true,
|
||||
routes = {
|
||||
["post/index"] = true
|
||||
}
|
||||
}
|
||||
},
|
||||
--category = {
|
||||
-- url = "cat/index",
|
||||
-- visibility = "ALL"
|
||||
--}
|
||||
}
|
||||
router:route('default', default_routes_dependencies )
|
||||
router:delegate()
|
||||
if REGISTRY.db then REGISTRY.db:close() end
|
||||
|
Reference in New Issue
Block a user