mirror of
https://github.com/Doodle3D/doodle3d-firmware.git
synced 2024-12-22 02:53:49 +01:00
Change REST API to obtain module/function arguments from URL path; move several network settings to config.h.
This commit is contained in:
parent
1ac3f130ff
commit
9dd2928755
20
TODO.md
20
TODO.md
@ -4,6 +4,7 @@
|
||||
* in 'test' dir next to 'src', with API tests under 'test/www/'
|
||||
* www tests check functionality of the test module
|
||||
* www tests also provide an interface to run arbitrary get/post requests
|
||||
* test path splitting as well
|
||||
- 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
|
||||
@ -11,7 +12,7 @@
|
||||
* 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)
|
||||
- define a list of REST error codes to be more descriptive for clients (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)
|
||||
@ -21,25 +22,24 @@
|
||||
* 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 (set once on installation and then only upon explicit request? (e.g. api/config/wifiname/default))
|
||||
- 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'
|
||||
* use a uci wifibox config to store configuration and a uci wifibox-defaults config as fallback-lookup (which contains a complete default configuration)
|
||||
* specify min/max/type/regex for each config key in separate lua file
|
||||
* perhaps defaults should be specified together with min/max/type/regex
|
||||
- dynamic AP name based on partial MAC (present in default config so it can be overridden and reverted again)
|
||||
- require api functions which change state to be invoked as post request
|
||||
* 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
|
||||
- handling requests which need a restart of uhttpd (e.g. network/openap) will probably respond with some kind of 'please check back in a few seconds' response
|
||||
- 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
|
||||
- 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)
|
||||
- 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 system api module? for check-updates/do-update/etc
|
||||
- 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'
|
||||
|
@ -7,4 +7,8 @@ M.DEBUG_PCALLS = true
|
||||
--REST responses will contain 'module' and 'function' keys describing what was requested
|
||||
M.API_INCLUDE_ENDPOINT_INFO = true
|
||||
|
||||
M.DEFAULT_AP_SSID = "d3d-ap-%MAC_ADDR_TAIL%"
|
||||
M.DEFAULT_AP_ADDRESS = "192.168.10.1"
|
||||
M.DEFAULT_AP_NETMASK = "255.255.255.0"
|
||||
|
||||
return M
|
||||
|
@ -1,3 +1,4 @@
|
||||
local config = require("config")
|
||||
local u = require("util.utils")
|
||||
local l = require("logger")
|
||||
local uci = require("uci").cursor()
|
||||
@ -96,13 +97,13 @@ function reconf.apnet_add_noreload(dirtyList) reconf.apnet_add(dirtyList, true)
|
||||
function reconf.apnet_add(dirtyList, noReload)
|
||||
local sname = nil
|
||||
uci:foreach("wireless", "wifi-iface", function(s)
|
||||
if s.ssid == wifi.AP_SSID then sname = s[".name"]; return false end
|
||||
if s.ssid == config.DEFAULT_AP_SSID then sname = s[".name"]; return false end
|
||||
end)
|
||||
if sname == nil then sname = uci:add("wireless", "wifi-iface") end
|
||||
|
||||
M.uciTableSet("wireless", sname, {
|
||||
network = wifi.NET,
|
||||
ssid = wifi.AP_SSID,
|
||||
ssid = config.DEFAULT_AP_SSID,
|
||||
encryption = "none",
|
||||
device = "radio0",
|
||||
mode = "ap",
|
||||
@ -114,7 +115,7 @@ end
|
||||
function reconf.apnet_rm(dirtyList)
|
||||
local sname = nil
|
||||
uci:foreach("wireless", "wifi-iface", function(s)
|
||||
if s.ssid == wifi.AP_SSID then sname = s[".name"]; return false end
|
||||
if s.ssid == config.DEFAULT_AP_SSID then sname = s[".name"]; return false end
|
||||
end)
|
||||
if sname == nil then return l:info("AP network configuration does not exist, nothing to remove") end
|
||||
uci:delete("wireless", sname)
|
||||
@ -129,8 +130,8 @@ function reconf.staticaddr_add(dirtyList)
|
||||
--NOTE: 'type = "bridge"' should -not- be added as this prevents defining a separate dhcp pool (http://wiki.openwrt.org/doc/recipes/routedap)
|
||||
M.uciTableSet("network", wifi.NET, {
|
||||
proto = "static",
|
||||
ipaddr = wifi.AP_ADDRESS,
|
||||
netmask = wifi.AP_NETMASK
|
||||
ipaddr = config.DEFAULT_AP_ADDRESS,
|
||||
netmask = config.DEFAULT_AP_NETMASK
|
||||
})
|
||||
bothBits(dirtyList, "network")
|
||||
end
|
||||
@ -177,7 +178,7 @@ end
|
||||
|
||||
--[[ Add/remove redirecton of all DNS requests to self ]]
|
||||
function reconf.dnsredir_add(dirtyList)
|
||||
local redirText = "/#/" .. wifi.AP_ADDRESS
|
||||
local redirText = "/#/" .. config.DEFAULT_AP_ADDRESS
|
||||
local sname = u.getUciSectionName("dhcp", "dnsmasq")
|
||||
if sname == nil then return l:error("dhcp config does not contain a dnsmasq section") end
|
||||
if uci:get("dhcp", sname, "address") ~= nil then return l:debug("DNS address redirection already in place, not re-adding", false) end
|
||||
@ -225,7 +226,7 @@ function reconf.natreflect_add(dirtyList)
|
||||
proto = "tcp",
|
||||
src_dport = "80",
|
||||
dest_port = "80",
|
||||
dest_ip = wifi.AP_ADDRESS,
|
||||
dest_ip = config.DEFAULT_AP_ADDRESS,
|
||||
target = "DNAT"
|
||||
})
|
||||
bothBits(dirtyList, "firewall")
|
||||
|
@ -8,9 +8,6 @@ local M = {}
|
||||
--NOTE: fallback device 'radio0' is required because sometimes the wlan0 device disappears
|
||||
M.DFL_DEVICE = "wlan0"
|
||||
M.DFL_DEVICE_FALLBACK = "radio0"
|
||||
M.AP_SSID = "d3d-ap"
|
||||
M.AP_ADDRESS = "192.168.10.1"
|
||||
M.AP_NETMASK = "255.255.255.0"
|
||||
M.NET = "wlan"
|
||||
|
||||
local dev, dev_api
|
||||
@ -82,6 +79,21 @@ function M.getDeviceState()
|
||||
return result
|
||||
end
|
||||
|
||||
--returns the wireless device's MAC address (as string, without colons)
|
||||
--(lua numbers on openWrt seem to be 32bit so they cannot represent a MAC address as one number)
|
||||
function M.getMacAddress()
|
||||
local iw = iwinfo[dev_api]
|
||||
local macText = iw.bssid(dev)
|
||||
local out = ""
|
||||
|
||||
for i = 0, 5 do
|
||||
local bt = string.sub(macText, i*3+1, i*3+2)
|
||||
out = out .. bt
|
||||
end
|
||||
|
||||
return out
|
||||
end
|
||||
|
||||
--- Return one or all available wifi networks resulting from an iwinfo scan
|
||||
-- @param ssid return data for given SSID or for all networks if SSID not given
|
||||
-- @return data for all or requested network; false+error on failure or nil when requested network not found
|
||||
|
@ -1,3 +1,4 @@
|
||||
local config = require("config")
|
||||
local l = require("logger")
|
||||
local u = require("util.utils")
|
||||
local netconf = require("network.netconfig")
|
||||
@ -24,7 +25,7 @@ function M.available(request, response)
|
||||
response:setSuccess("")
|
||||
local netInfoList = {}
|
||||
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 ~= config.DEFAULT_AP_SSID then
|
||||
local netInfo = {}
|
||||
|
||||
netInfo["ssid"] = se.ssid
|
||||
@ -139,10 +140,10 @@ end
|
||||
function M.openap(request, response)
|
||||
--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"}
|
||||
wifi.activateConfig(wifi.AP_SSID)
|
||||
wifi.activateConfig(config.DEFAULT_AP_SSID)
|
||||
netconf.switchConfiguration{ wifiiface="add", network="reload", staticaddr="add", dhcppool="add", wwwredir="add", dnsredir="add", wwwcaptive="add" }
|
||||
response:setSuccess("switched to Access Point mode")
|
||||
response:addData("ssid", wifi.AP_SSID)
|
||||
response:addData("ssid", config.DEFAULT_AP_SSID)
|
||||
end
|
||||
|
||||
--UNTESTED
|
||||
|
@ -29,7 +29,9 @@ end
|
||||
|
||||
function M.echo(request, response)
|
||||
response:setSuccess("request echo")
|
||||
response:addData("request_data", request)
|
||||
response:addData("request_data", request:getAll())
|
||||
response:addData("blank_argument", request:getBlankArgument())
|
||||
response:addData("path_data", request:getPathData())
|
||||
end
|
||||
|
||||
return M
|
||||
|
@ -1,3 +1,4 @@
|
||||
local util = require("util.utils") --required for string:split()
|
||||
local urlcode = require("util.urlcode")
|
||||
local config = require("config")
|
||||
local ResponseClass = require("rest.response")
|
||||
@ -42,6 +43,12 @@ local function kvTableFromArray(argArray)
|
||||
return args
|
||||
end
|
||||
|
||||
--NOTE: this function ignores empty tokens (e.g. '/a//b/' yields { [1] = a, [2] = b })
|
||||
local function arrayFromPath(pathText)
|
||||
return pathText and pathText:split("/") or {} --FIXME: nothing returned? regardless of which sep is used
|
||||
--return pathText:split("/")
|
||||
end
|
||||
|
||||
|
||||
--returns either a module object, or nil+errmsg
|
||||
local function resolveApiModule(modname)
|
||||
@ -68,6 +75,10 @@ local function resolveApiFunction(modname, funcname)
|
||||
|
||||
local mod, msg = resolveApiModule(modname)
|
||||
|
||||
if mod == nil then
|
||||
return nil, msg
|
||||
end
|
||||
|
||||
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)
|
||||
@ -106,22 +117,27 @@ function M.new(postData, debug)
|
||||
self.cmdLineArgs = kvTableFromArray(arg)
|
||||
self.getArgs = kvTableFromUrlEncodedString(os.getenv("QUERY_STRING"))
|
||||
self.postArgs = kvTableFromUrlEncodedString(postData)
|
||||
self.pathArgs = arrayFromPath(os.getenv("PATH_INFO"))
|
||||
|
||||
--TEMP: until these can be extracted from the url path itself
|
||||
self.requestedApiModule = self.getArgs["m"]
|
||||
self.requestedApiFunction = self.getArgs["f"]
|
||||
|
||||
if debug then
|
||||
self.requestedApiModule = self.cmdLineArgs["m"] or self.requestedApiModule
|
||||
self.requestedApiFunction = self.cmdLineArgs["f"] or self.requestedApiFunction
|
||||
--override path arguments with command line parameter if debugging is enabled
|
||||
if debug and self.requestMethod == "CMDLINE" then
|
||||
self.pathArgs = arrayFromPath(self.cmdLineArgs["p"])
|
||||
end
|
||||
|
||||
|
||||
if #self.pathArgs >= 1 then self.requestedApiModule = self.pathArgs[1] end
|
||||
if #self.pathArgs >= 2 then self.requestedApiFunction = self.pathArgs[2] end
|
||||
|
||||
-- if debug then
|
||||
-- self.requestedApiModule = self.cmdLineArgs["m"] or self.requestedApiModule
|
||||
-- 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
|
||||
@ -130,7 +146,12 @@ function M.new(postData, debug)
|
||||
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
|
||||
if self:getRequestedApiFunction() ~= nil then
|
||||
self.realApiFunctionName = self:getRequestedApiFunction()
|
||||
if #self.pathArgs >= 3 then self:setBlankArgument(self.pathArgs[3]) end --aha, we have both a function and a blank argument
|
||||
else
|
||||
self.realApiFunctionName = GLOBAL_API_FUNCTION_NAME
|
||||
end
|
||||
end
|
||||
else
|
||||
--instead of throwing an error, save the message for handle() which is expected to return a response anyway
|
||||
@ -141,7 +162,7 @@ function M.new(postData, debug)
|
||||
return self
|
||||
end
|
||||
|
||||
--GET/POST/CMDLINE
|
||||
--returns either GET or POST or CMDLINE
|
||||
function M:getRequestMethod()
|
||||
return self.requestMethod
|
||||
end
|
||||
@ -194,6 +215,10 @@ function M:getAll()
|
||||
end
|
||||
end
|
||||
|
||||
function M:getPathData()
|
||||
return self.pathArgs
|
||||
end
|
||||
|
||||
|
||||
--returns either a response object+nil, or response object+errmsg
|
||||
function M:handle()
|
||||
@ -215,7 +240,7 @@ function M:handle()
|
||||
return resp, ("calling function '" .. self.realApiFunctionName .. "' in API module '" .. modname .. "' somehow failed ('" .. r .. "')")
|
||||
end
|
||||
else
|
||||
resp:setError("function unknown '" .. (modname or "<empty>") .. "/" .. (self:getRequestedApiFunction() or "<empty>") .. "'")
|
||||
resp:setError("function or module unknown '" .. (modname or "<empty>") .. "/" .. (self:getRequestedApiFunction() or "<empty>") .. "'")
|
||||
return resp, ("could not resolve requested API function ('" .. self.resolutionError .. "')")
|
||||
end
|
||||
|
||||
|
@ -2,6 +2,13 @@ local uci = require("uci").cursor()
|
||||
|
||||
local M = {}
|
||||
|
||||
function string:split(sep)
|
||||
local sep, fields = sep or ":", {}
|
||||
local pattern = string.format("([^%s]+)", sep)
|
||||
self:gsub(pattern, function(c) fields[#fields+1] = c end)
|
||||
return fields
|
||||
end
|
||||
|
||||
function M.toboolean(s)
|
||||
if not s then return false end
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user