mirror of
https://github.com/Doodle3D/doodle3d-firmware.git
synced 2024-12-22 11:03:48 +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/'
|
* in 'test' dir next to 'src', with API tests under 'test/www/'
|
||||||
* www tests check functionality of the test module
|
* www tests check functionality of the test module
|
||||||
* www tests also provide an interface to run arbitrary get/post requests
|
* www tests also provide an interface to run arbitrary get/post requests
|
||||||
|
* test path splitting as well
|
||||||
- document REST API
|
- document REST API
|
||||||
* fail/error difference: fail is a valid rq aka 'could not comply', while error is invalid rq _or_ system error
|
* 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
|
* modules/functions prefixed with '_' are for internal use
|
||||||
@ -11,7 +12,7 @@
|
|||||||
* list endpoints+args+CRUD type
|
* list endpoints+args+CRUD type
|
||||||
* success/fail/error statuses are justified by drupal api
|
* success/fail/error statuses are justified by drupal api
|
||||||
* unknown values (e.g. in network info) are either empty or unmentioned fields
|
* 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
|
- 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)
|
||||||
@ -21,25 +22,24 @@
|
|||||||
* 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 (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
|
- require api functions which change state to be invoked as post request
|
||||||
* can this be modelled like java annotations or c function attributes?
|
* can this be modelled like java annotations or c function attributes?
|
||||||
* otherwise maybe pair each function with <func>_attribs = {…}?
|
* 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 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:
|
- 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>
|
||||||
|
|
||||||
|
|
||||||
# Ideas / issues to work out
|
# 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? for check-updates/do-update/etc
|
||||||
- add system api module? with 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>)
|
||||||
- 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>
|
|
||||||
- (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'
|
||||||
|
@ -7,4 +7,8 @@ M.DEBUG_PCALLS = true
|
|||||||
--REST responses will contain 'module' and 'function' keys describing what was requested
|
--REST responses will contain 'module' and 'function' keys describing what was requested
|
||||||
M.API_INCLUDE_ENDPOINT_INFO = true
|
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
|
return M
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
local config = require("config")
|
||||||
local u = require("util.utils")
|
local u = require("util.utils")
|
||||||
local l = require("logger")
|
local l = require("logger")
|
||||||
local uci = require("uci").cursor()
|
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)
|
function reconf.apnet_add(dirtyList, noReload)
|
||||||
local sname = nil
|
local sname = nil
|
||||||
uci:foreach("wireless", "wifi-iface", function(s)
|
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)
|
end)
|
||||||
if sname == nil then sname = uci:add("wireless", "wifi-iface") end
|
if sname == nil then sname = uci:add("wireless", "wifi-iface") end
|
||||||
|
|
||||||
M.uciTableSet("wireless", sname, {
|
M.uciTableSet("wireless", sname, {
|
||||||
network = wifi.NET,
|
network = wifi.NET,
|
||||||
ssid = wifi.AP_SSID,
|
ssid = config.DEFAULT_AP_SSID,
|
||||||
encryption = "none",
|
encryption = "none",
|
||||||
device = "radio0",
|
device = "radio0",
|
||||||
mode = "ap",
|
mode = "ap",
|
||||||
@ -114,7 +115,7 @@ end
|
|||||||
function reconf.apnet_rm(dirtyList)
|
function reconf.apnet_rm(dirtyList)
|
||||||
local sname = nil
|
local sname = nil
|
||||||
uci:foreach("wireless", "wifi-iface", function(s)
|
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)
|
end)
|
||||||
if sname == nil then return l:info("AP network configuration does not exist, nothing to remove") end
|
if sname == nil then return l:info("AP network configuration does not exist, nothing to remove") end
|
||||||
uci:delete("wireless", sname)
|
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)
|
--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, {
|
M.uciTableSet("network", wifi.NET, {
|
||||||
proto = "static",
|
proto = "static",
|
||||||
ipaddr = wifi.AP_ADDRESS,
|
ipaddr = config.DEFAULT_AP_ADDRESS,
|
||||||
netmask = wifi.AP_NETMASK
|
netmask = config.DEFAULT_AP_NETMASK
|
||||||
})
|
})
|
||||||
bothBits(dirtyList, "network")
|
bothBits(dirtyList, "network")
|
||||||
end
|
end
|
||||||
@ -177,7 +178,7 @@ end
|
|||||||
|
|
||||||
--[[ Add/remove redirecton of all DNS requests to self ]]
|
--[[ Add/remove redirecton of all DNS requests to self ]]
|
||||||
function reconf.dnsredir_add(dirtyList)
|
function reconf.dnsredir_add(dirtyList)
|
||||||
local redirText = "/#/" .. wifi.AP_ADDRESS
|
local redirText = "/#/" .. config.DEFAULT_AP_ADDRESS
|
||||||
local sname = u.getUciSectionName("dhcp", "dnsmasq")
|
local sname = u.getUciSectionName("dhcp", "dnsmasq")
|
||||||
if sname == nil then return l:error("dhcp config does not contain a dnsmasq section") end
|
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
|
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",
|
proto = "tcp",
|
||||||
src_dport = "80",
|
src_dport = "80",
|
||||||
dest_port = "80",
|
dest_port = "80",
|
||||||
dest_ip = wifi.AP_ADDRESS,
|
dest_ip = config.DEFAULT_AP_ADDRESS,
|
||||||
target = "DNAT"
|
target = "DNAT"
|
||||||
})
|
})
|
||||||
bothBits(dirtyList, "firewall")
|
bothBits(dirtyList, "firewall")
|
||||||
|
@ -8,9 +8,6 @@ local M = {}
|
|||||||
--NOTE: fallback device 'radio0' is required because sometimes the wlan0 device disappears
|
--NOTE: fallback device 'radio0' is required because sometimes the wlan0 device disappears
|
||||||
M.DFL_DEVICE = "wlan0"
|
M.DFL_DEVICE = "wlan0"
|
||||||
M.DFL_DEVICE_FALLBACK = "radio0"
|
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"
|
M.NET = "wlan"
|
||||||
|
|
||||||
local dev, dev_api
|
local dev, dev_api
|
||||||
@ -82,6 +79,21 @@ function M.getDeviceState()
|
|||||||
return result
|
return result
|
||||||
end
|
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
|
--- 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
|
-- @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
|
-- @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 l = require("logger")
|
||||||
local u = require("util.utils")
|
local u = require("util.utils")
|
||||||
local netconf = require("network.netconfig")
|
local netconf = require("network.netconfig")
|
||||||
@ -24,7 +25,7 @@ function M.available(request, response)
|
|||||||
response: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 ~= config.DEFAULT_AP_SSID then
|
||||||
local netInfo = {}
|
local netInfo = {}
|
||||||
|
|
||||||
netInfo["ssid"] = se.ssid
|
netInfo["ssid"] = se.ssid
|
||||||
@ -139,10 +140,10 @@ end
|
|||||||
function M.openap(request, response)
|
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
|
--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(config.DEFAULT_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" }
|
||||||
response:setSuccess("switched to Access Point mode")
|
response:setSuccess("switched to Access Point mode")
|
||||||
response:addData("ssid", wifi.AP_SSID)
|
response:addData("ssid", config.DEFAULT_AP_SSID)
|
||||||
end
|
end
|
||||||
|
|
||||||
--UNTESTED
|
--UNTESTED
|
||||||
|
@ -29,7 +29,9 @@ end
|
|||||||
|
|
||||||
function M.echo(request, response)
|
function M.echo(request, response)
|
||||||
response:setSuccess("request echo")
|
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
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
local util = require("util.utils") --required for string:split()
|
||||||
local urlcode = require("util.urlcode")
|
local urlcode = require("util.urlcode")
|
||||||
local config = require("config")
|
local config = require("config")
|
||||||
local ResponseClass = require("rest.response")
|
local ResponseClass = require("rest.response")
|
||||||
@ -42,6 +43,12 @@ local function kvTableFromArray(argArray)
|
|||||||
return args
|
return args
|
||||||
end
|
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
|
--returns either a module object, or nil+errmsg
|
||||||
local function resolveApiModule(modname)
|
local function resolveApiModule(modname)
|
||||||
@ -68,6 +75,10 @@ local function resolveApiFunction(modname, funcname)
|
|||||||
|
|
||||||
local mod, msg = resolveApiModule(modname)
|
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
|
if (funcname == nil or funcname == '') then funcname = GLOBAL_API_FUNCTION_NAME end --treat empty function name as nil
|
||||||
local f = mod[funcname]
|
local f = mod[funcname]
|
||||||
local funcNumber = tonumber(funcname)
|
local funcNumber = tonumber(funcname)
|
||||||
@ -106,22 +117,27 @@ function M.new(postData, debug)
|
|||||||
self.cmdLineArgs = kvTableFromArray(arg)
|
self.cmdLineArgs = kvTableFromArray(arg)
|
||||||
self.getArgs = kvTableFromUrlEncodedString(os.getenv("QUERY_STRING"))
|
self.getArgs = kvTableFromUrlEncodedString(os.getenv("QUERY_STRING"))
|
||||||
self.postArgs = kvTableFromUrlEncodedString(postData)
|
self.postArgs = kvTableFromUrlEncodedString(postData)
|
||||||
|
self.pathArgs = arrayFromPath(os.getenv("PATH_INFO"))
|
||||||
|
|
||||||
--TEMP: until these can be extracted from the url path itself
|
--override path arguments with command line parameter if debugging is enabled
|
||||||
self.requestedApiModule = self.getArgs["m"]
|
if debug and self.requestMethod == "CMDLINE" then
|
||||||
self.requestedApiFunction = self.getArgs["f"]
|
self.pathArgs = arrayFromPath(self.cmdLineArgs["p"])
|
||||||
|
|
||||||
if debug then
|
|
||||||
self.requestedApiModule = self.cmdLineArgs["m"] or self.requestedApiModule
|
|
||||||
self.requestedApiFunction = self.cmdLineArgs["f"] or self.requestedApiFunction
|
|
||||||
end
|
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.requestedApiModule == "" then self.requestedApiModule = nil end
|
||||||
if self.requestedApiFunction == "" then self.requestedApiFunction = nil end
|
if self.requestedApiFunction == "" then self.requestedApiFunction = nil end
|
||||||
|
|
||||||
|
|
||||||
-- Perform module/function resolution
|
-- Perform module/function resolution
|
||||||
--TODO: improve naming and perhaps argument passing
|
|
||||||
local sfunc, sres = resolveApiFunction(self:getRequestedApiModule(), self:getRequestedApiFunction())
|
local sfunc, sres = resolveApiFunction(self:getRequestedApiModule(), self:getRequestedApiFunction())
|
||||||
|
|
||||||
if sfunc ~= nil then --function (possibly the global one) could be resolved
|
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:setBlankArgument(sres)
|
||||||
self.realApiFunctionName = GLOBAL_API_FUNCTION_NAME
|
self.realApiFunctionName = GLOBAL_API_FUNCTION_NAME
|
||||||
else --resolved without blank argument but still potentially the global function, hence the _or_ construction
|
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
|
end
|
||||||
else
|
else
|
||||||
--instead of throwing an error, save the message for handle() which is expected to return a response anyway
|
--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
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
--GET/POST/CMDLINE
|
--returns either GET or POST or CMDLINE
|
||||||
function M:getRequestMethod()
|
function M:getRequestMethod()
|
||||||
return self.requestMethod
|
return self.requestMethod
|
||||||
end
|
end
|
||||||
@ -194,6 +215,10 @@ function M:getAll()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function M:getPathData()
|
||||||
|
return self.pathArgs
|
||||||
|
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()
|
||||||
@ -215,7 +240,7 @@ function M:handle()
|
|||||||
return resp, ("calling function '" .. self.realApiFunctionName .. "' in API module '" .. modname .. "' somehow failed ('" .. r .. "')")
|
return resp, ("calling function '" .. self.realApiFunctionName .. "' in API module '" .. modname .. "' somehow failed ('" .. r .. "')")
|
||||||
end
|
end
|
||||||
else
|
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 .. "')")
|
return resp, ("could not resolve requested API function ('" .. self.resolutionError .. "')")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -2,6 +2,13 @@ local uci = require("uci").cursor()
|
|||||||
|
|
||||||
local M = {}
|
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)
|
function M.toboolean(s)
|
||||||
if not s then return false end
|
if not s then return false end
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user