0
0
mirror of https://github.com/Doodle3D/doodle3d-firmware.git synced 2024-12-22 11:03:48 +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)
- The endpoint function info in response objects is incorrect when the global function is called with a blank argument,
to cleanly solve this, module/function resolution should be moved from main() to the request object
FIX: handle mod/func resolution and calling in request:handle() which returns a response object
then send it using response:send("json")
- fix up init script handling as described here: http://wiki.openwrt.org/doc/devel/packages#packaging.a.service
- versioning/updating
# TODO (new functionality)
- 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
- fix init script handling as described here: http://wiki.openwrt.org/doc/devel/packages#packaging.a.service
- write a simple client script to autotest as much of the api as possible
extend the client script to run arbitrary post/get requests
- 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)
- steps to take regarding versioning/updating
* versioning scheme
* 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)
@ -13,42 +15,38 @@
* 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?)
* 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
- 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
- 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)
- 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)
- 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)
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)
- 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
- in captive portal mode, https is not redirected
- see if updating works when a new version is 'released' (probably needs a real feed)
- perhaps opkg+safeboot could be useful in the update mechanism?
- 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
- 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)
# Bugs
- in captive portal mode, https is not redirected
- 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?
- protect dump function against reference loops (see <http://lua-users.org/wiki/TableSerialization>, json also handles this well)
- relocatabilty of package (take root prefix into consideration everywhere)
# 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 RequestClass = require("rest.request")
local ResponseClass = require("rest.response")
@ -61,38 +40,6 @@ local function init()
return true
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 rq = RequestClass.new(postData, DEBUG_PCALLS) -- initializes itself using various environment variables and the arg array
@ -111,35 +58,10 @@ end
else
io.write ("Content-type: text/plain\r\n\r\n")
local response, err = rq:handle()
local mod = rq:getApiModule()
local func = rq:getApiFunction()
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
if err ~= nil then l:error(err) end
response:send()
end
end
@ -147,7 +69,7 @@ local s, msg = init()
if s == false then
local resp = ResponseClass.new()
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
os.exit(1)
else

View File

@ -17,19 +17,22 @@ end
function M.success(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
end
function M.fail(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
end
function M.error(d)
local r = ResponseClass.new(d)
r:setError("this error has been generated on purpose")
r:addData("url", "http://xkcd.com/1024/")
return r
end

View File

@ -1,8 +1,11 @@
local urlcode = require("util.urlcode")
local ResponseClass = require("rest.response")
local M = {}
M.__index = M
local GLOBAL_API_FUNCTION_NAME = "_global"
local function kvTableFromUrlEncodedString(encodedText)
local args = {}
if (encodedText ~= nil) then
@ -113,4 +116,75 @@ function M:getAll()
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

View File

@ -61,4 +61,8 @@ function M:serializeAsJson()
return JSON:encode(self.body)
end
function M:send()
print(self:serializeAsJson())
end
return M