Add C based module + lua based API code (WIP)

This commit is contained in:
DanyLE
2023-01-18 15:12:48 +01:00
parent 8417bccefb
commit d4bb12c6b5
56 changed files with 23110 additions and 41 deletions

40
silkmvc/core/OOP.lua Normal file
View 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
View 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
View 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

View 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
View 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
View 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
View 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