mirror of
https://github.com/Doodle3D/doodle3d-firmware.git
synced 2024-12-22 19:13:49 +01:00
Move request handling to request object; update and combine todo items to TODO.md; improve test api.
This commit is contained in:
parent
4847b59378
commit
6caa7d244e
58
TODO.md
58
TODO.md
@ -1,10 +1,12 @@
|
|||||||
# TODO (from api_info.lua)
|
# TODO (new functionality)
|
||||||
- The endpoint function info in response objects is incorrect when the global function is called with a blank argument,
|
- save requested mod+func in request, as well as resolved function/pretty print version/function pointer (or subset…); then fix endpoint function name (when called with blank argument) in response objects to show pretty print name
|
||||||
to cleanly solve this, module/function resolution should be moved from main() to the request object
|
- fix init script handling as described here: http://wiki.openwrt.org/doc/devel/packages#packaging.a.service
|
||||||
FIX: handle mod/func resolution and calling in request:handle() which returns a response object
|
- write a simple client script to autotest as much of the api as possible
|
||||||
then send it using response:send("json")
|
extend the client script to run arbitrary post/get requests
|
||||||
- fix up init script handling as described here: http://wiki.openwrt.org/doc/devel/packages#packaging.a.service
|
- document REST API (mention rq IDs and endpoint information, list endpoints+args+CRUD type, unknown values are empty fields)
|
||||||
- versioning/updating
|
(describe fail/error difference: fail is valid rq..could not comply, while error is invalid rq _or_ system error)
|
||||||
|
- use a slightly more descriptive success/error definition (e.g. errortype=system/missing-arg/generic)
|
||||||
|
- steps to take regarding versioning/updating
|
||||||
* versioning scheme
|
* versioning scheme
|
||||||
* create feed location (e.g. www.doodle3d.com/firmware/packages) (see here: http://wiki.openwrt.org/doc/packages#third.party.packages)
|
* create feed location (e.g. www.doodle3d.com/firmware/packages) (see here: http://wiki.openwrt.org/doc/packages#third.party.packages)
|
||||||
* create opkg (already present in bin/ar71xx/packages as .ipk file)
|
* create opkg (already present in bin/ar71xx/packages as .ipk file)
|
||||||
@ -13,42 +15,38 @@
|
|||||||
* determine how opkg decides what is 'upgradeable'
|
* determine how opkg decides what is 'upgradeable'
|
||||||
* at this point manual updating should be possible, now find out how to implement in lua (execve? or write a minimalistic binding to libopkg?)
|
* at this point manual updating should be possible, now find out how to implement in lua (execve? or write a minimalistic binding to libopkg?)
|
||||||
* expose through info API and/or system API; also provide a way (future) to flash a new image
|
* expose through info API and/or system API; also provide a way (future) to flash a new image
|
||||||
- dynamic AP name based on partial MAC
|
- dynamic AP name based on partial MAC (set once on installation and then only upon explicit request? (e.g. api/config/wifiname/default))
|
||||||
- require api functions which change state to be invoked as post request
|
- require api functions which change state to be invoked as post request
|
||||||
- add functions to test network connectivity in steps (ifup? hasip? resolve? ping?) to network or test
|
- add API functions to test network connectivity in steps (ifup? hasip? resolve? ping?) to network or test
|
||||||
|
- add more config options to package, which should act as defaults for a config file on the system; candidates:
|
||||||
|
reconf.WWW_RENAME_NAME, wifihelper.{AP_ADDRESS, AP_NETMASK, (NET)}
|
||||||
|
<https://github.com/2ion/ini.lua>
|
||||||
|
|
||||||
|
|
||||||
|
# Ideas / issues to work out
|
||||||
|
- generally, for configuration keys, it could be a good idea to use the concept of default values so it's always possible to return to a 'sane default config'
|
||||||
- add system api module? with check-updates/do-update/etc
|
- add system api module? with check-updates/do-update/etc
|
||||||
- write a simple client script to autotest as much of the api as possible
|
|
||||||
- extend the client script to run arbitrary post/get requests
|
|
||||||
|
|
||||||
|
|
||||||
# TODO (from main.lua)
|
|
||||||
- document REST API (mention rq IDs and endpoint information, list endpoints+args+CRUD type, unknown values are empty fields)
|
|
||||||
(describe fail/error difference: fail is valid rq..could not comply, while error is invalid rq _or_ system error)
|
|
||||||
- use a slightly more descriptive success/error definition (e.g. errortype=system/missing-arg/generic)
|
|
||||||
- how to handle requests which need a restart of uhttpd? (e.g. network/openap)
|
- how to handle requests which need a restart of uhttpd? (e.g. network/openap)
|
||||||
- a plain GET request (no ajax/script) runs the risk of timing out on lengthy operations: implement polling in API to get progress updates?
|
- a plain GET request (no ajax/script) runs the risk of timing out on lengthy operations: implement polling in API to get progress updates?
|
||||||
(this would require those operations to run in a separate daemon process which can be monitored by the CGI handler)
|
(this would require those operations to run in a separate daemon process which can be monitored by the CGI handler)
|
||||||
(!!!is this true? it could very well be caused by a uhttpd restart)
|
(!!!is this true? it could very well be caused by a uhttpd restart)
|
||||||
- protect dump function against reference loops (see: http://lua-users.org/wiki/TableSerialization, json also handles this well)
|
- licensing (also for hardware and firmware) + credits for external code and used ideas
|
||||||
|
<http://www.codinghorror.com/blog/2007/04/pick-a-license-any-license.html>
|
||||||
- (this is an old todo item from network:available(), might still be relevant at some point)
|
- (this is an old todo item from network:available(), might still be relevant at some point)
|
||||||
extend netconf interface to support function arguments (as tables) so wifihelper functionality can be integrated
|
extend netconf interface to support function arguments (as tables) so wifihelper functionality can be integrated
|
||||||
but how? idea: pass x_args={arg1="a",arg2="2342"} for component 'x'
|
but how? idea: pass x_args={arg1="a",arg2="2342"} for component 'x'
|
||||||
or: allow alternative for x="y" --> x={action="y", arg1="a", arg2="2342"}
|
or: allow alternative for x="y" --> x={action="y", arg1="a", arg2="2342"}
|
||||||
in any case, arguments should be put in a new table to pass to the function (since order is undefined it must be an assoc array)
|
in any case, arguments should be put in a new table to pass to the function (since order is undefined it must be an assoc array)
|
||||||
|
- perhaps opkg+safeboot could be useful in the update mechanism?
|
||||||
|
- add config option to compile sources using luac _or_ add an option to minify the lua code
|
||||||
|
|
||||||
|
|
||||||
# TODO
|
# Bugs
|
||||||
- in captive portal mode, https is not redirected
|
- in captive portal mode, https is not redirected
|
||||||
- see if updating works when a new version is 'released' (probably needs a real feed)
|
- using iwinfo with interface name 'radio0' yields very little 'info' output while wlan0 works fine.
|
||||||
- perhaps opkg+safeboot could be useful in the update mechanism?
|
However, sometimes wlan0 disappears (happened after trying to associate with non-existing network)...why?
|
||||||
- licensing (also for hardware and firmware) + credits for external code and used ideas
|
- protect dump function against reference loops (see <http://lua-users.org/wiki/TableSerialization>, json also handles this well)
|
||||||
http://www.codinghorror.com/blog/2007/04/pick-a-license-any-license.html
|
- relocatabilty of package (take root prefix into consideration everywhere)
|
||||||
- add more config options to package, which should act as defaults for a config file on the system; candidates:
|
|
||||||
reconf.WWW_RENAME_NAME, wifihelper.{AP_ADDRESS, AP_NETMASK, (NET)}
|
|
||||||
https://github.com/2ion/ini.lua
|
|
||||||
- add config option to compile sources using luac _or_ add an option to minify the lua code
|
|
||||||
- add dependency on either wr703n or mr3020 (and probably comment it out again to avoid being to conservative...)
|
|
||||||
- relocatabilty of package (take root prefix into consideration everywhere)
|
|
||||||
|
|
||||||
|
|
||||||
# Logos
|
# Logos
|
||||||
|
86
src/main.lua
86
src/main.lua
@ -1,24 +1,3 @@
|
|||||||
--[[
|
|
||||||
TODO:
|
|
||||||
- document REST API (mention rq IDs and endpoint information, list endpoints+args+CRUD type, unknown values are empty fields)
|
|
||||||
(describe fail/error difference: fail is valid rq..could not comply, while error is invalid rq _or_ system error)
|
|
||||||
- use a slightly more descriptive success/error definition (e.g. errortype=system/missing-arg/generic)
|
|
||||||
- how to handle requests which need a restart of uhttpd? (e.g. network/openap)
|
|
||||||
- a plain GET request (no ajax/script) runs the risk of timing out on lengthy operations: implement polling in API to get progress updates?
|
|
||||||
(this would require those operations to run in a separate daemon process which can be monitored by the CGI handler)
|
|
||||||
(!!!is this true? it could very well be caused by a uhttpd restart)
|
|
||||||
- protect dump function against reference loops (see: http://lua-users.org/wiki/TableSerialization, json also handles this well)
|
|
||||||
- (this is an old todo item from network:available(), might still be relevant at some point)
|
|
||||||
extend netconf interface to support function arguments (as tables) so wifihelper functionality can be integrated
|
|
||||||
but how? idea: pass x_args={arg1="a",arg2="2342"} for component 'x'
|
|
||||||
or: allow alternative for x="y" --> x={action="y", arg1="a", arg2="2342"}
|
|
||||||
in any case, arguments should be put in a new table to pass to the function (since order is undefined it must be an assoc array)
|
|
||||||
|
|
||||||
NOTES:
|
|
||||||
- using iwinfo with interface name 'radio0' yields very little 'info' output while wlan0 works fine.
|
|
||||||
However, sometimes wlan0 disappears (happened after trying to associate with non-existing network)...why?
|
|
||||||
]]--
|
|
||||||
|
|
||||||
local l = require("logger")
|
local l = require("logger")
|
||||||
local RequestClass = require("rest.request")
|
local RequestClass = require("rest.request")
|
||||||
local ResponseClass = require("rest.response")
|
local ResponseClass = require("rest.response")
|
||||||
@ -61,38 +40,6 @@ local function init()
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
--usually returns function+nil, function+number in case of number in place of function name; or
|
|
||||||
--nil+string if given arguments could not be resolved
|
|
||||||
local function resolveApiFunction(mod, func)
|
|
||||||
if mod == nil then return nil, ("missing module name in CGI call") end
|
|
||||||
|
|
||||||
local ok, mObj
|
|
||||||
local reqModPath = "rest.api.api_" .. mod
|
|
||||||
|
|
||||||
if DEBUG_PCALLS then ok, mObj = true, require(reqModPath)
|
|
||||||
else ok, mObj = pcall(require, reqModPath)
|
|
||||||
end
|
|
||||||
|
|
||||||
if ok == false then return nil, ("API module '" .. mod .. "' does not exist") end
|
|
||||||
|
|
||||||
if mObj == nil then return nil, ("API module '" .. mod .. "' could not be found") end
|
|
||||||
|
|
||||||
if mObj.isApi ~= true then return nil, ("module '" .. mod .. "' is not part of the CGI API") end
|
|
||||||
|
|
||||||
if (func == nil or func == '') then func = "_global" end --treat empty function name as nil
|
|
||||||
local f = mObj[func]
|
|
||||||
|
|
||||||
if (type(f) ~= "function") then
|
|
||||||
if tonumber(func) ~= nil then
|
|
||||||
return mObj["_global"], tonumber(func)
|
|
||||||
else
|
|
||||||
return nil, ("function '" .. func .. "' does not exist in API module '" .. mod .. "'")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return f
|
|
||||||
end
|
|
||||||
|
|
||||||
local function main()
|
local function main()
|
||||||
local rq = RequestClass.new(postData, DEBUG_PCALLS) -- initializes itself using various environment variables and the arg array
|
local rq = RequestClass.new(postData, DEBUG_PCALLS) -- initializes itself using various environment variables and the arg array
|
||||||
|
|
||||||
@ -111,35 +58,10 @@ end
|
|||||||
|
|
||||||
else
|
else
|
||||||
io.write ("Content-type: text/plain\r\n\r\n")
|
io.write ("Content-type: text/plain\r\n\r\n")
|
||||||
|
local response, err = rq:handle()
|
||||||
|
|
||||||
local mod = rq:getApiModule()
|
if err ~= nil then l:error(err) end
|
||||||
local func = rq:getApiFunction()
|
response:send()
|
||||||
|
|
||||||
local sf,sr = resolveApiFunction(mod, func)
|
|
||||||
if (sf ~= nil) then
|
|
||||||
if (sr ~= nil) then
|
|
||||||
rq:setBlankArgument(sr)
|
|
||||||
end
|
|
||||||
|
|
||||||
local ok, r
|
|
||||||
if DEBUG_PCALLS then ok, r = true, sf(rq)
|
|
||||||
else ok, r = pcall(sf, rq)
|
|
||||||
end
|
|
||||||
|
|
||||||
if ok == true then
|
|
||||||
print(r:serializeAsJson())
|
|
||||||
else
|
|
||||||
local resp = ResponseClass.new(rq)
|
|
||||||
resp:setError("call to function '" .. mod .. "/" .. sr .. "' failed")
|
|
||||||
print(resp:serializeAsJson())
|
|
||||||
l:error("calling function '" .. func .. "' in API module '" .. mod .. "' somehow failed ('" .. r .. "')")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local resp = ResponseClass.new(rq)
|
|
||||||
resp:setError("function unknown '" .. (mod or "<empty>") .. "/" .. (func or "<global>") .. "'")
|
|
||||||
print(resp:serializeAsJson())
|
|
||||||
l:error("could not resolve requested API function ('" .. sr .. "')")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -147,7 +69,7 @@ local s, msg = init()
|
|||||||
if s == false then
|
if s == false then
|
||||||
local resp = ResponseClass.new()
|
local resp = ResponseClass.new()
|
||||||
resp:setError("initialization failed (" .. msg .. ")")
|
resp:setError("initialization failed (" .. msg .. ")")
|
||||||
print(resp:serializeAsJson()) --FIXME: this message does not seem to be sent
|
resp:send() --FIXME: this message does not seem to be sent
|
||||||
l:error("initialization failed (" .. msg .. ")") --NOTE: this assumes the logger has been inited properly, despite init() having failed
|
l:error("initialization failed (" .. msg .. ")") --NOTE: this assumes the logger has been inited properly, despite init() having failed
|
||||||
os.exit(1)
|
os.exit(1)
|
||||||
else
|
else
|
||||||
|
@ -17,19 +17,22 @@ end
|
|||||||
|
|
||||||
function M.success(d)
|
function M.success(d)
|
||||||
local r = ResponseClass.new(d)
|
local r = ResponseClass.new(d)
|
||||||
r:setSuccess()
|
r:setSuccess("this successful response has been generated on purpose")
|
||||||
|
r:addData("url", "http://xkcd.com/349/")
|
||||||
return r
|
return r
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.fail(d)
|
function M.fail(d)
|
||||||
local r = ResponseClass.new(d)
|
local r = ResponseClass.new(d)
|
||||||
r:setFail()
|
r:setFail("this failure has been generated on purpose")
|
||||||
|
r:addData("url", "http://xkcd.com/336/")
|
||||||
return r
|
return r
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.error(d)
|
function M.error(d)
|
||||||
local r = ResponseClass.new(d)
|
local r = ResponseClass.new(d)
|
||||||
r:setError("this error has been generated on purpose")
|
r:setError("this error has been generated on purpose")
|
||||||
|
r:addData("url", "http://xkcd.com/1024/")
|
||||||
return r
|
return r
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
local urlcode = require("util.urlcode")
|
local urlcode = require("util.urlcode")
|
||||||
|
local ResponseClass = require("rest.response")
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
M.__index = M
|
M.__index = M
|
||||||
|
|
||||||
|
local GLOBAL_API_FUNCTION_NAME = "_global"
|
||||||
|
|
||||||
local function kvTableFromUrlEncodedString(encodedText)
|
local function kvTableFromUrlEncodedString(encodedText)
|
||||||
local args = {}
|
local args = {}
|
||||||
if (encodedText ~= nil) then
|
if (encodedText ~= nil) then
|
||||||
@ -113,4 +116,75 @@ function M:getAll()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--returns either a module object, or nil+errmsg
|
||||||
|
local function resolveApiModule(modname)
|
||||||
|
if modname == nil then return nil, "missing module name" end
|
||||||
|
if string.find(modname, "_") == 1 then return nil, "module names starting with '_' are preserved for internal use" end
|
||||||
|
|
||||||
|
local reqModName = "rest.api.api_" .. modname
|
||||||
|
local ok, modObj
|
||||||
|
|
||||||
|
--TODO: create config.lua which contains DEBUG_PCALLS (nothing else for the moment)
|
||||||
|
if DEBUG_PCALLS then ok, modObj = true, require(reqModName)
|
||||||
|
else ok, modObj = pcall(require, reqModName)
|
||||||
|
end
|
||||||
|
|
||||||
|
if ok == false then return nil, "API module does not exist" end
|
||||||
|
if modObj == nil then return nil, "API module could not be found" end
|
||||||
|
if modObj.isApi ~= true then return nil, "module is not part of the CGI API" end
|
||||||
|
|
||||||
|
return modObj
|
||||||
|
end
|
||||||
|
|
||||||
|
--returns funcobj+nil (usual), funcobj+number (global func with blank arg), or nil+errmsg (unresolvable or inaccessible)
|
||||||
|
local function resolveApiFunction(modname, funcname)
|
||||||
|
if funcname and string.find(funcname, "_") == 1 then return nil, "function names starting with '_' are preserved for internal use" end
|
||||||
|
|
||||||
|
local mod, msg = resolveApiModule(modname)
|
||||||
|
|
||||||
|
if (funcname == nil or funcname == '') then funcname = GLOBAL_API_FUNCTION_NAME end --treat empty function name as nil
|
||||||
|
local f = mod[funcname]
|
||||||
|
local funcNumber = tonumber(funcname)
|
||||||
|
|
||||||
|
if (type(f) == "function") then
|
||||||
|
return f
|
||||||
|
elseif funcNumber ~= nil then
|
||||||
|
return mod[GLOBAL_API_FUNCTION_NAME], funcNumber
|
||||||
|
else
|
||||||
|
return nil, ("function '" .. funcname .. "' does not exist in API module '" .. modname .. "'")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--returns either a response object+nil, or response object+errmsg
|
||||||
|
function M:handle()
|
||||||
|
|
||||||
|
--TEMP: should be moved to init
|
||||||
|
local mod = self:getApiModule()
|
||||||
|
local func = self:getApiFunction()
|
||||||
|
local sf, sr = resolveApiFunction(mod, func)
|
||||||
|
|
||||||
|
local resp = ResponseClass.new(rq) --TEMP: do not do this before resolving. after resolving has been moved to init that will be automatically true
|
||||||
|
|
||||||
|
if (sf ~= nil) then
|
||||||
|
if (sr ~= nil) then self:setBlankArgument(sr) end
|
||||||
|
|
||||||
|
local ok, r
|
||||||
|
if DEBUG_PCALLS then ok, r = true, sf(self)
|
||||||
|
else ok, r = pcall(sf, self)
|
||||||
|
end
|
||||||
|
|
||||||
|
if ok == true then
|
||||||
|
return r, nil
|
||||||
|
else
|
||||||
|
resp:setError("call to function '" .. mod .. "/" .. sr .. "' failed")
|
||||||
|
return resp, ("calling function '" .. func .. "' in API module '" .. mod .. "' somehow failed ('" .. r .. "')")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
resp:setError("function unknown '" .. (mod or "<empty>") .. "/" .. (func or "<global>") .. "'")
|
||||||
|
return resp, ("could not resolve requested API function ('" .. sr .. "')")
|
||||||
|
end
|
||||||
|
|
||||||
|
return resp
|
||||||
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
@ -61,4 +61,8 @@ function M:serializeAsJson()
|
|||||||
return JSON:encode(self.body)
|
return JSON:encode(self.body)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function M:send()
|
||||||
|
print(self:serializeAsJson())
|
||||||
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
Loading…
Reference in New Issue
Block a user