mirror of
https://github.com/Doodle3D/doodle3d-firmware.git
synced 2024-12-22 11:03:48 +01:00
Fix response sending when init() fails; create config.lua to contain global configuration; improve API function resolution; improve API code structure.
This commit is contained in:
parent
6caa7d244e
commit
1ac3f130ff
20
TODO.md
20
TODO.md
@ -1,10 +1,16 @@
|
|||||||
# TODO (new functionality)
|
# 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
|
- 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
|
- implement (automated) test code where possible
|
||||||
extend the client script to run arbitrary post/get requests
|
* in 'test' dir next to 'src', with API tests under 'test/www/'
|
||||||
- document REST API (mention rq IDs and endpoint information, list endpoints+args+CRUD type, unknown values are empty fields)
|
* www tests check functionality of the test module
|
||||||
(describe fail/error difference: fail is valid rq..could not comply, while error is invalid rq _or_ system error)
|
* www tests also provide an interface to run arbitrary get/post requests
|
||||||
|
- document REST API
|
||||||
|
* fail/error difference: fail is a valid rq aka 'could not comply', while error is invalid rq _or_ system error
|
||||||
|
* modules/functions prefixed with '_' are for internal use
|
||||||
|
* rq IDs and endpoint information can be supplied (but it's probably not useful after all)
|
||||||
|
* list endpoints+args+CRUD type
|
||||||
|
* success/fail/error statuses are justified by drupal api
|
||||||
|
* unknown values (e.g. in network info) are either empty or unmentioned fields
|
||||||
- use a slightly more descriptive success/error definition (e.g. errortype=system/missing-arg/generic)
|
- use a slightly more descriptive success/error definition (e.g. errortype=system/missing-arg/generic)
|
||||||
- steps to take regarding versioning/updating
|
- steps to take regarding versioning/updating
|
||||||
* versioning scheme
|
* versioning scheme
|
||||||
@ -17,7 +23,9 @@
|
|||||||
* 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 (set once on installation and then only upon explicit request? (e.g. api/config/wifiname/default))
|
- 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 API functions to test network connectivity in steps (ifup? hasip? resolve? ping?) to network or test
|
* can this be modelled like java annotations or c function attributes?
|
||||||
|
* otherwise maybe pair each function with <func>_attribs = {…}?
|
||||||
|
- add API functions to test network connectivity in steps (any chance(e.g. ~ap)? 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:
|
- 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)}
|
reconf.WWW_RENAME_NAME, wifihelper.{AP_ADDRESS, AP_NETMASK, (NET)}
|
||||||
<https://github.com/2ion/ini.lua>
|
<https://github.com/2ion/ini.lua>
|
||||||
|
10
src/config.lua
Normal file
10
src/config.lua
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
--NOTE: pcall protects from invocation exceptions, which is what we need except
|
||||||
|
--during debugging. This flag replaces them with a normal call so we can inspect stack traces.
|
||||||
|
M.DEBUG_PCALLS = true
|
||||||
|
|
||||||
|
--REST responses will contain 'module' and 'function' keys describing what was requested
|
||||||
|
M.API_INCLUDE_ENDPOINT_INFO = true
|
||||||
|
|
||||||
|
return M
|
24
src/main.lua
24
src/main.lua
@ -3,12 +3,7 @@ local RequestClass = require("rest.request")
|
|||||||
local ResponseClass = require("rest.response")
|
local ResponseClass = require("rest.response")
|
||||||
local wifi = require("network.wlanconfig")
|
local wifi = require("network.wlanconfig")
|
||||||
local netconf = require("network.netconfig")
|
local netconf = require("network.netconfig")
|
||||||
|
local config = require("config")
|
||||||
|
|
||||||
--NOTE: pcall protects from invocation exceptions, which is what we need except
|
|
||||||
--during debugging. This flag replaces them with a normal call so we can inspect stack traces.
|
|
||||||
local DEBUG_PCALLS = true
|
|
||||||
|
|
||||||
|
|
||||||
local postData = nil
|
local postData = nil
|
||||||
|
|
||||||
@ -21,7 +16,7 @@ local function init()
|
|||||||
l:init(l.LEVEL.debug)
|
l:init(l.LEVEL.debug)
|
||||||
l:setStream(io.stderr)
|
l:setStream(io.stderr)
|
||||||
|
|
||||||
if DEBUG_PCALLS then l:info("Wifibox CGI handler started (pcall debugging enabled)")
|
if config.DEBUG_PCALLS then l:info("Wifibox CGI handler started (pcall debugging enabled)")
|
||||||
else l:info("Wifibox CGI handler started")
|
else l:info("Wifibox CGI handler started")
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -41,7 +36,7 @@ local function init()
|
|||||||
end
|
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, config.DEBUG_PCALLS)
|
||||||
|
|
||||||
l:info("received request of type " .. rq:getRequestMethod() .. " with arguments: " .. l:dump(rq:getAll()))
|
l:info("received request of type " .. rq:getRequestMethod() .. " with arguments: " .. l:dump(rq:getAll()))
|
||||||
if rq:getRequestMethod() ~= "CMDLINE" then
|
if rq:getRequestMethod() ~= "CMDLINE" then
|
||||||
@ -49,7 +44,7 @@ end
|
|||||||
l:debug("user agent: " .. rq:getUserAgent())
|
l:debug("user agent: " .. rq:getUserAgent())
|
||||||
end
|
end
|
||||||
|
|
||||||
if (not DEBUG_PCALLS and rq:getRequestMethod() == "CMDLINE") then
|
if (not config.DEBUG_PCALLS and rq:getRequestMethod() == "CMDLINE") then
|
||||||
if rq:get("autowifi") ~= nil then
|
if rq:get("autowifi") ~= nil then
|
||||||
setupAutoWifiMode()
|
setupAutoWifiMode()
|
||||||
else
|
else
|
||||||
@ -65,12 +60,17 @@ end
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---'entry point'---
|
||||||
local s, msg = init()
|
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 .. ")")
|
local errSuffix = msg and " (" .. msg .. ")" or ""
|
||||||
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
|
resp:setError("initialization failed" .. errSuffix)
|
||||||
|
io.write ("Content-type: text/plain\r\n\r\n")
|
||||||
|
resp:send()
|
||||||
|
l:error("initialization failed" .. errSuffix) --NOTE: this assumes the logger has been inited properly, despite init() having failed
|
||||||
|
|
||||||
os.exit(1)
|
os.exit(1)
|
||||||
else
|
else
|
||||||
main()
|
main()
|
||||||
|
@ -8,21 +8,20 @@ local M = {}
|
|||||||
|
|
||||||
M.isApi = true
|
M.isApi = true
|
||||||
|
|
||||||
function M._global(d)
|
function M._global(request, response)
|
||||||
local r = ResponseClass.new(d)
|
response:setError("not implemented")
|
||||||
r:setError("not implemented")
|
|
||||||
return r
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--accepts API argument 'nofilter'(bool) to disable filtering of APs and 'self'
|
--accepts API argument 'nofilter'(bool) to disable filtering of APs and 'self'
|
||||||
function M.available(d)
|
--accepts with_raw(bool) to include raw table dump
|
||||||
local r = ResponseClass.new(d)
|
function M.available(request, response)
|
||||||
local noFilter = u.toboolean(d:get("nofilter"))
|
local noFilter = u.toboolean(request:get("nofilter"))
|
||||||
|
local withRaw = u.toboolean(request:get("with_raw"))
|
||||||
local sr = wifi.getScanInfo()
|
local sr = wifi.getScanInfo()
|
||||||
local si, se
|
local si, se
|
||||||
|
|
||||||
if sr and #sr > 0 then
|
if sr and #sr > 0 then
|
||||||
r:setSuccess("")
|
response:setSuccess("")
|
||||||
local netInfoList = {}
|
local netInfoList = {}
|
||||||
for _, se in ipairs(sr) do
|
for _, se in ipairs(sr) do
|
||||||
if noFilter or se.mode ~= "ap" and se.ssid ~= wifi.AP_SSID then
|
if noFilter or se.mode ~= "ap" and se.ssid ~= wifi.AP_SSID then
|
||||||
@ -36,26 +35,25 @@ function M.available(d)
|
|||||||
netInfo["signal"] = se.signal
|
netInfo["signal"] = se.signal
|
||||||
netInfo["quality"] = se.quality
|
netInfo["quality"] = se.quality
|
||||||
netInfo["quality_max"] = se.quality_max
|
netInfo["quality_max"] = se.quality_max
|
||||||
--netInfo["raw"] = l:dump(se) --TEMP for debugging only
|
if withRaw then netInfo["_raw"] = l:dump(se) end
|
||||||
|
|
||||||
table.insert(netInfoList, netInfo)
|
table.insert(netInfoList, netInfo)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
r:addData("count", #netInfoList)
|
response:addData("count", #netInfoList)
|
||||||
r:addData("networks", netInfoList)
|
response:addData("networks", netInfoList)
|
||||||
else
|
else
|
||||||
r:setFail("No scan results or scanning not possible")
|
response:setFail("No scan results or scanning not possible")
|
||||||
end
|
end
|
||||||
|
|
||||||
return r
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--accepts API argument 'nofilter'(bool) to disable filtering of APs and 'self'
|
--accepts API argument 'nofilter'(bool) to disable filtering of APs and 'self'
|
||||||
function M.known(d)
|
--accepts with_raw(bool) to include raw table dump
|
||||||
local r = ResponseClass.new(d)
|
function M.known(request, response)
|
||||||
local noFilter = u.toboolean(d:get("nofilter"))
|
local noFilter = u.toboolean(request:get("nofilter"))
|
||||||
|
local withRaw = u.toboolean(request:get("with_raw"))
|
||||||
|
|
||||||
r:setSuccess()
|
response:setSuccess()
|
||||||
local netInfoList = {}
|
local netInfoList = {}
|
||||||
for _, net in ipairs(wifi.getConfigs()) do
|
for _, net in ipairs(wifi.getConfigs()) do
|
||||||
if noFilter or net.mode == "sta" then
|
if noFilter or net.mode == "sta" then
|
||||||
@ -64,47 +62,43 @@ function M.known(d)
|
|||||||
netInfo["bssid"] = net.bssid or ""
|
netInfo["bssid"] = net.bssid or ""
|
||||||
netInfo["channel"] = net.channel or ""
|
netInfo["channel"] = net.channel or ""
|
||||||
netInfo["encryption"] = net.encryption
|
netInfo["encryption"] = net.encryption
|
||||||
--netInfo["raw"] = l:dump(net) --TEMP for debugging only
|
if withRaw then netInfo["_raw"] = l:dump(net) end
|
||||||
table.insert(netInfoList, netInfo)
|
table.insert(netInfoList, netInfo)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
r:addData("count", #netInfoList)
|
response:addData("count", #netInfoList)
|
||||||
r:addData("networks", netInfoList)
|
response:addData("networks", netInfoList)
|
||||||
|
|
||||||
return r
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.state(d)
|
--accepts with_raw(bool) to include raw table dump
|
||||||
local r = ResponseClass.new(d)
|
function M.state(request, response)
|
||||||
|
local withRaw = u.toboolean(request:get("with_raw"))
|
||||||
local ds = wifi.getDeviceState()
|
local ds = wifi.getDeviceState()
|
||||||
|
|
||||||
r:setSuccess()
|
response:setSuccess()
|
||||||
r:addData("ssid", ds.ssid or "")
|
response:addData("ssid", ds.ssid or "")
|
||||||
r:addData("bssid", ds.bssid or "")
|
response:addData("bssid", ds.bssid or "")
|
||||||
r:addData("channel", ds.channel or "")
|
response:addData("channel", ds.channel or "")
|
||||||
r:addData("mode", ds.mode)
|
response:addData("mode", ds.mode)
|
||||||
r:addData("encryption", ds.encryption)
|
response:addData("encryption", ds.encryption)
|
||||||
r:addData("quality", ds.quality)
|
response:addData("quality", ds.quality)
|
||||||
r:addData("quality_max", ds.quality_max)
|
response:addData("quality_max", ds.quality_max)
|
||||||
r:addData("txpower", ds.txpower)
|
response:addData("txpower", ds.txpower)
|
||||||
r:addData("signal", ds.signal)
|
response:addData("signal", ds.signal)
|
||||||
r:addData("noise", ds.noise)
|
response:addData("noise", ds.noise)
|
||||||
--r:addData("raw", l:dump(ds)) --TEMP for debugging only
|
if withRaw then response:addData("_raw", l:dump(ds)) end
|
||||||
|
|
||||||
return r
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--UNTESTED
|
--UNTESTED
|
||||||
--requires ssid(string), accepts phrase(string), recreate(bool)
|
--requires ssid(string), accepts phrase(string), recreate(bool)
|
||||||
function M.assoc(d)
|
function M.assoc(request, response)
|
||||||
local r = ResponseClass.new(d)
|
local argSsid = request:get("ssid")
|
||||||
local argSsid = d:get("ssid")
|
local argPhrase = request:get("phrase")
|
||||||
local argPhrase = d:get("phrase")
|
local argRecreate = request:get("recreate")
|
||||||
local argRecreate = d:get("recreate")
|
|
||||||
|
|
||||||
if argSsid == nil or argSsid == "" then
|
if argSsid == nil or argSsid == "" then
|
||||||
r:setError("missing ssid argument")
|
response:setError("missing ssid argument")
|
||||||
return r
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local cfg = nil
|
local cfg = nil
|
||||||
@ -121,65 +115,53 @@ function M.assoc(d)
|
|||||||
wifi.createConfigFromScanInfo(scanResult, argPhrase)
|
wifi.createConfigFromScanInfo(scanResult, argPhrase)
|
||||||
else
|
else
|
||||||
--check for error
|
--check for error
|
||||||
r:setFail("no wireless network with requested SSID is available")
|
response:setFail("no wireless network with requested SSID is available")
|
||||||
r:addData("ssid", argSsid)
|
response:addData("ssid", argSsid)
|
||||||
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
wifi.activateConfig(argSsid)
|
wifi.activateConfig(argSsid)
|
||||||
netconf.switchConfiguration{ wifiiface="add", apnet="rm", staticaddr="rm", dhcppool="rm", wwwredir="rm", dnsredir="rm", wwwcaptive="rm", wireless="reload" }
|
netconf.switchConfiguration{ wifiiface="add", apnet="rm", staticaddr="rm", dhcppool="rm", wwwredir="rm", dnsredir="rm", wwwcaptive="rm", wireless="reload" }
|
||||||
r:setSuccess("wlan associated")
|
response:setSuccess("wlan associated")
|
||||||
r:addData("ssid", argSsid)
|
response:addData("ssid", argSsid)
|
||||||
|
|
||||||
return r
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--UNTESTED
|
--UNTESTED
|
||||||
function M.disassoc(d)
|
function M.disassoc(request, response)
|
||||||
local r = ResponseClass.new(d)
|
|
||||||
|
|
||||||
wifi.activateConfig()
|
wifi.activateConfig()
|
||||||
local rv = wifi.restart()
|
local rv = wifi.restart()
|
||||||
r:setSuccess("all wireless networks deactivated")
|
response:setSuccess("all wireless networks deactivated")
|
||||||
r:addData("wifi_restart_result", rv)
|
response:addData("wifi_restart_result", rv)
|
||||||
|
|
||||||
return r
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--UNTESTED
|
--UNTESTED
|
||||||
function M.openap(d)
|
function M.openap(request, response)
|
||||||
local r = ResponseClass.new(d)
|
|
||||||
|
|
||||||
--add AP net, activate it, deactivate all others, reload network/wireless config, add all dhcp and captive settings and reload as needed
|
--add AP net, activate it, deactivate all others, reload network/wireless config, add all dhcp and captive settings and reload as needed
|
||||||
netconf.switchConfiguration{apnet="add_noreload"}
|
netconf.switchConfiguration{apnet="add_noreload"}
|
||||||
wifi.activateConfig(wifi.AP_SSID)
|
wifi.activateConfig(wifi.AP_SSID)
|
||||||
netconf.switchConfiguration{ wifiiface="add", network="reload", staticaddr="add", dhcppool="add", wwwredir="add", dnsredir="add", wwwcaptive="add" }
|
netconf.switchConfiguration{ wifiiface="add", network="reload", staticaddr="add", dhcppool="add", wwwredir="add", dnsredir="add", wwwcaptive="add" }
|
||||||
r:setSuccess("switched to Access Point mode")
|
response:setSuccess("switched to Access Point mode")
|
||||||
r:addData("ssid", wifi.AP_SSID)
|
response:addData("ssid", wifi.AP_SSID)
|
||||||
|
|
||||||
return r
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--UNTESTED
|
--UNTESTED
|
||||||
--requires ssid(string)
|
--requires ssid(string)
|
||||||
function M.rm(d)
|
function M.rm(request, response)
|
||||||
local r = ResponseClass.new(d)
|
local argSsid = request:get("ssid")
|
||||||
local argSsid = d:get("ssid")
|
|
||||||
|
|
||||||
if argSsid == nil or argSsid == "" then
|
if argSsid == nil or argSsid == "" then
|
||||||
r:setError("missing ssid argument")
|
response:setError("missing ssid argument")
|
||||||
return r
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if wifi.removeConfig(argSsid) then
|
if wifi.removeConfig(argSsid) then
|
||||||
r:setSuccess("removed wireless network with requested SSID")
|
response:setSuccess("removed wireless network with requested SSID")
|
||||||
r:addData("ssid", argSsid)
|
response:addData("ssid", argSsid)
|
||||||
else
|
else
|
||||||
r:setFail("no wireless network with requested SSID") --this used to be a warning instead of an error...
|
response:setFail("no wireless network with requested SSID") --this used to be a warning instead of an error...
|
||||||
r:addData("ssid", argSsid)
|
response:addData("ssid", argSsid)
|
||||||
end
|
end
|
||||||
|
|
||||||
return r
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
@ -5,42 +5,31 @@ local M = {}
|
|||||||
|
|
||||||
M.isApi = true
|
M.isApi = true
|
||||||
|
|
||||||
function M._global(d)
|
function M._global(request, response)
|
||||||
local r = ResponseClass.new(d)
|
local ba = request:getBlankArgument()
|
||||||
local ba = d:getBlankArgument()
|
|
||||||
|
|
||||||
r:setSuccess("REST test API - default function called with blank argument: '" .. (ba or "<nil>") .. "'")
|
response:setSuccess("REST test API - default function called with blank argument: '" .. (ba or "<nil>") .. "'")
|
||||||
if ba ~= nil then r:addData("blank_argument", ba) end
|
if ba ~= nil then response:addData("blank_argument", ba) end
|
||||||
|
|
||||||
return r
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.success(d)
|
function M.success(request, response)
|
||||||
local r = ResponseClass.new(d)
|
response:setSuccess("this successful response has been generated on purpose")
|
||||||
r:setSuccess("this successful response has been generated on purpose")
|
response:addData("url", "http://xkcd.com/349/")
|
||||||
r:addData("url", "http://xkcd.com/349/")
|
|
||||||
return r
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.fail(d)
|
function M.fail(request, response)
|
||||||
local r = ResponseClass.new(d)
|
response:setFail("this failure has been generated on purpose")
|
||||||
r:setFail("this failure has been generated on purpose")
|
response:addData("url", "http://xkcd.com/336/")
|
||||||
r:addData("url", "http://xkcd.com/336/")
|
|
||||||
return r
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.error(d)
|
function M.error(request, response)
|
||||||
local r = ResponseClass.new(d)
|
response:setError("this error has been generated on purpose")
|
||||||
r:setError("this error has been generated on purpose")
|
response:addData("url", "http://xkcd.com/1024/")
|
||||||
r:addData("url", "http://xkcd.com/1024/")
|
|
||||||
return r
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.echo(d)
|
function M.echo(request, response)
|
||||||
local r = ResponseClass.new(d)
|
response:setSuccess("request echo")
|
||||||
r:setSuccess("request echo")
|
response:addData("request_data", request)
|
||||||
r:addData("request_data", d)
|
|
||||||
return r
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
local urlcode = require("util.urlcode")
|
local urlcode = require("util.urlcode")
|
||||||
|
local config = require("config")
|
||||||
local ResponseClass = require("rest.response")
|
local ResponseClass = require("rest.response")
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
@ -6,12 +7,24 @@ M.__index = M
|
|||||||
|
|
||||||
local GLOBAL_API_FUNCTION_NAME = "_global"
|
local GLOBAL_API_FUNCTION_NAME = "_global"
|
||||||
|
|
||||||
|
|
||||||
|
--NOTE: requestedApi* contain what was extracted from the request data
|
||||||
|
-- regarding the other variables: either both resolvedApiFunction and realApiFunctionName
|
||||||
|
-- are nil and resolutionError is not, or exactly the other way around
|
||||||
|
M.requestedApiModule = nil
|
||||||
|
M.requestedApiFunction = nil
|
||||||
|
M.resolvedApiFunction = nil --will contain function address, or nil
|
||||||
|
M.realApiFunctionName = nil --will contain requested name, or global name, or nil
|
||||||
|
M.resolutionError = nil --non-nil means function could not be resolved
|
||||||
|
|
||||||
|
|
||||||
local function kvTableFromUrlEncodedString(encodedText)
|
local function kvTableFromUrlEncodedString(encodedText)
|
||||||
local args = {}
|
local args = {}
|
||||||
if (encodedText ~= nil) then
|
if (encodedText ~= nil) then
|
||||||
urlcode.parsequery(encodedText, args)
|
urlcode.parsequery(encodedText, args)
|
||||||
end
|
end
|
||||||
return args
|
return args
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function kvTableFromArray(argArray)
|
local function kvTableFromArray(argArray)
|
||||||
@ -30,12 +43,53 @@ local function kvTableFromArray(argArray)
|
|||||||
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
|
||||||
|
|
||||||
|
if config.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
|
||||||
|
|
||||||
|
|
||||||
setmetatable(M, {
|
setmetatable(M, {
|
||||||
__call = function(cls, ...)
|
__call = function(cls, ...)
|
||||||
return cls.new(...)
|
return cls.new(...)
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|
||||||
|
--This function initializes itself using various environment variables, the arg array and the given postData
|
||||||
|
--NOTE: if debugging is enabled, commandline arguments 'm' and 'f' override requested module and function
|
||||||
function M.new(postData, debug)
|
function M.new(postData, debug)
|
||||||
local self = setmetatable({}, M)
|
local self = setmetatable({}, M)
|
||||||
|
|
||||||
@ -54,30 +108,54 @@ function M.new(postData, debug)
|
|||||||
self.postArgs = kvTableFromUrlEncodedString(postData)
|
self.postArgs = kvTableFromUrlEncodedString(postData)
|
||||||
|
|
||||||
--TEMP: until these can be extracted from the url path itself
|
--TEMP: until these can be extracted from the url path itself
|
||||||
self.apiModule = self.getArgs["m"]
|
self.requestedApiModule = self.getArgs["m"]
|
||||||
self.apiFunction = self.getArgs["f"]
|
self.requestedApiFunction = self.getArgs["f"]
|
||||||
|
|
||||||
if debug then
|
if debug then
|
||||||
self.apiModule = self.cmdLineArgs["m"] or self.apiModule
|
self.requestedApiModule = self.cmdLineArgs["m"] or self.requestedApiModule
|
||||||
self.apiFunction = self.cmdLineArgs["f"] or self.apiFunction
|
self.requestedApiFunction = self.cmdLineArgs["f"] or self.requestedApiFunction
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.requestedApiModule == "" then self.requestedApiModule = nil end
|
||||||
|
if self.requestedApiFunction == "" then self.requestedApiFunction = nil end
|
||||||
|
|
||||||
|
|
||||||
|
-- Perform module/function resolution
|
||||||
|
--TODO: improve naming and perhaps argument passing
|
||||||
|
local sfunc, sres = resolveApiFunction(self:getRequestedApiModule(), self:getRequestedApiFunction())
|
||||||
|
|
||||||
|
if sfunc ~= nil then --function (possibly the global one) could be resolved
|
||||||
|
self.resolvedApiFunction = sfunc
|
||||||
|
if sres ~= nil then --apparently it was the global one, and we received a 'blank argument'
|
||||||
|
self:setBlankArgument(sres)
|
||||||
|
self.realApiFunctionName = GLOBAL_API_FUNCTION_NAME
|
||||||
|
else --resolved without blank argument but still potentially the global function, hence the _or_ construction
|
||||||
|
self.realApiFunctionName = self:getRequestedApiFunction() or GLOBAL_API_FUNCTION_NAME
|
||||||
|
end
|
||||||
|
else
|
||||||
|
--instead of throwing an error, save the message for handle() which is expected to return a response anyway
|
||||||
|
self.resolutionError = sres
|
||||||
end
|
end
|
||||||
|
|
||||||
if self.apiModule == "" then self.apiModule = nil end
|
|
||||||
if self.apiFunction == "" then self.apiFunction = nil end
|
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--GET/POST/CMDLINE
|
||||||
function M:getRequestMethod()
|
function M:getRequestMethod()
|
||||||
return self.requestMethod
|
return self.requestMethod
|
||||||
end
|
end
|
||||||
|
|
||||||
function M:getApiModule()
|
function M:getRequestedApiModule()
|
||||||
return self.apiModule
|
return self.requestedApiModule
|
||||||
end
|
end
|
||||||
|
|
||||||
function M:getApiFunction()
|
function M:getRequestedApiFunction()
|
||||||
return self.apiFunction
|
return self.requestedApiFunction
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:getRealApiFunctionName()
|
||||||
|
return self.realApiFunctionName
|
||||||
end
|
end
|
||||||
|
|
||||||
function M:getBlankArgument()
|
function M:getBlankArgument()
|
||||||
@ -116,72 +194,29 @@ 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
|
--returns either a response object+nil, or response object+errmsg
|
||||||
function M:handle()
|
function M:handle()
|
||||||
|
local modname = self:getRequestedApiModule()
|
||||||
--TEMP: should be moved to init
|
local resp = ResponseClass.new(self)
|
||||||
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 (self.resolvedApiFunction ~= nil) then --we found a function (possible the global function)
|
||||||
|
--invoke the function
|
||||||
if (sf ~= nil) then
|
|
||||||
if (sr ~= nil) then self:setBlankArgument(sr) end
|
|
||||||
|
|
||||||
local ok, r
|
local ok, r
|
||||||
if DEBUG_PCALLS then ok, r = true, sf(self)
|
if config.DEBUG_PCALLS then ok, r = true, self.resolvedApiFunction(self, resp)
|
||||||
else ok, r = pcall(sf, self)
|
else ok, r = pcall(self.resolvedApiFunction, self, resp)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--handle the result
|
||||||
if ok == true then
|
if ok == true then
|
||||||
return r, nil
|
return resp, nil
|
||||||
else
|
else
|
||||||
resp:setError("call to function '" .. mod .. "/" .. sr .. "' failed")
|
resp:setError("call to function '" .. modname .. "/" .. self.realApiFunctionName .. "' failed")
|
||||||
return resp, ("calling function '" .. func .. "' in API module '" .. mod .. "' somehow failed ('" .. r .. "')")
|
return resp, ("calling function '" .. self.realApiFunctionName .. "' in API module '" .. modname .. "' somehow failed ('" .. r .. "')")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
resp:setError("function unknown '" .. (mod or "<empty>") .. "/" .. (func or "<global>") .. "'")
|
resp:setError("function unknown '" .. (modname or "<empty>") .. "/" .. (self:getRequestedApiFunction() or "<empty>") .. "'")
|
||||||
return resp, ("could not resolve requested API function ('" .. sr .. "')")
|
return resp, ("could not resolve requested API function ('" .. self.resolutionError .. "')")
|
||||||
end
|
end
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
local JSON = (loadfile "util/JSON.lua")()
|
local JSON = (loadfile "util/JSON.lua")()
|
||||||
|
local config = require("config")
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
M.__index = M
|
M.__index = M
|
||||||
|
|
||||||
local REQUEST_ID_ARGUMENT = "rq_id"
|
local REQUEST_ID_ARGUMENT = "rq_id"
|
||||||
local INCLUDE_ENDPOINT_INFO = false
|
|
||||||
|
|
||||||
setmetatable(M, {
|
setmetatable(M, {
|
||||||
__call = function(cls, ...)
|
__call = function(cls, ...)
|
||||||
@ -22,20 +22,15 @@ function M.new(requestObject)
|
|||||||
local rqId = requestObject:get(REQUEST_ID_ARGUMENT)
|
local rqId = requestObject:get(REQUEST_ID_ARGUMENT)
|
||||||
if rqId ~= nil then self.body[REQUEST_ID_ARGUMENT] = rqId end
|
if rqId ~= nil then self.body[REQUEST_ID_ARGUMENT] = rqId end
|
||||||
|
|
||||||
if INCLUDE_ENDPOINT_INFO == true then
|
if config.API_INCLUDE_ENDPOINT_INFO == true then
|
||||||
self.body["module"] = requestObject:getApiModule()
|
self.body["module"] = requestObject:getRequestedApiModule()
|
||||||
self.body["function"] = requestObject:getApiFunction() or ""
|
self.body["function"] = requestObject:getRealApiFunctionName() or ""
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
--use set{Success|Fail|Error}()
|
|
||||||
--function M:setStatus(s)
|
|
||||||
-- self.body.status = s
|
|
||||||
--end
|
|
||||||
|
|
||||||
function M:setSuccess(msg)
|
function M:setSuccess(msg)
|
||||||
self.body.status = "success"
|
self.body.status = "success"
|
||||||
if msg ~= "" then self.body.msg = msg end
|
if msg ~= "" then self.body.msg = msg end
|
||||||
|
Loading…
Reference in New Issue
Block a user