0
0
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:
Wouter R 2013-07-09 01:49:56 +02:00
parent 4847b59378
commit 6caa7d244e
5 changed files with 115 additions and 114 deletions

58
TODO.md
View File

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

View File

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

View File

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

View File

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

View File

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