summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorSteven Barth <steven@midlink.org>2008-08-26 17:50:32 +0000
committerSteven Barth <steven@midlink.org>2008-08-26 17:50:32 +0000
commitdf40e4df5e3485d1d70548b420e02b81aa61e60b (patch)
tree9cb095e2733430076ac1611787e6058c45901b29
parent05b30bba2adf9974378fc484273b709c55ebc011 (diff)
libs/json: Completed JSON library
modules/rpc: Added experimental JSON-RPC API
-rw-r--r--contrib/package/luci/Makefile29
-rw-r--r--libs/json/luasrc/json.lua274
-rw-r--r--libs/web/luasrc/http.lua6
-rw-r--r--modules/rpc/luasrc/controller/rpc.lua35
-rw-r--r--modules/rpc/luasrc/jsonrpc.lua18
5 files changed, 297 insertions, 65 deletions
diff --git a/contrib/package/luci/Makefile b/contrib/package/luci/Makefile
index 1cb3a79a6f..e8a1d3698d 100644
--- a/contrib/package/luci/Makefile
+++ b/contrib/package/luci/Makefile
@@ -187,6 +187,16 @@ define Package/luci-ipkg/install
endef
+define Package/luci-json
+ $(call Package/luci/libtemplate)
+ TITLE:=LuCI JSON Library
+endef
+
+define Package/luci-json/install
+ $(call Package/luci/install/template,$(1),libs/json)
+endef
+
+
define Package/luci-sys
$(call Package/luci/libtemplate)
TITLE:=LuCI Linux/POSIX system library
@@ -354,6 +364,17 @@ define Package/luci-admin-full/install
endef
+define Package/luci-admin-rpc
+ $(call Package/luci/webtemplate)
+ DEPENDS+=+luci-json
+ TITLE:=LuCI RPC - JSON-RPC API
+endef
+
+define Package/luci-admin-rpc/install
+ $(call Package/luci/install/template,$(1),modules/rpc)
+endef
+
+
define Package/luci-mod-freifunk
$(call Package/luci/fftemplate)
DEPENDS:=+luci-admin-full
@@ -607,6 +628,9 @@ endif
ifneq ($(CONFIG_PACKAGE_luci-ipkg),)
PKG_SELECTED_MODULES+=libs/ipkg
endif
+ifneq ($(CONFIG_PACKAGE_luci-json),)
+ PKG_SELECTED_MODULES+=libs/json
+endif
ifneq ($(CONFIG_PACKAGE_luci-uci),)
PKG_SELECTED_MODULES+=libs/uci
endif
@@ -649,6 +673,9 @@ endif
ifneq ($(CONFIG_PACKAGE_luci-admin-full),)
PKG_SELECTED_MODULES+=modules/admin-full
endif
+ifneq ($(CONFIG_PACKAGE_luci-admin-rpc),)
+ PKG_SELECTED_MODULES+=modules/rpc
+endif
ifneq ($(CONFIG_PACKAGE_luci-mod-freifunk),)
PKG_SELECTED_MODULES+=modules/freifunk
endif
@@ -733,6 +760,7 @@ $(eval $(call BuildPackage,luci-cbi))
$(eval $(call BuildPackage,luci-fastindex))
$(eval $(call BuildPackage,luci-http))
$(eval $(call BuildPackage,luci-ipkg))
+$(eval $(call BuildPackage,luci-json))
$(eval $(call BuildPackage,luci-uci))
$(eval $(call BuildPackage,luci-sys))
$(eval $(call BuildPackage,luci-web))
@@ -749,6 +777,7 @@ $(eval $(call BuildPackage,luci-ff-augsburg))
$(eval $(call BuildPackage,luci-admin-core))
$(eval $(call BuildPackage,luci-admin-mini))
$(eval $(call BuildPackage,luci-admin-full))
+$(eval $(call BuildPackage,luci-admin-rpc))
$(eval $(call BuildPackage,luci-mod-freifunk))
$(eval $(call BuildPackage,luci-app-ffwizard-leipzig))
diff --git a/libs/json/luasrc/json.lua b/libs/json/luasrc/json.lua
index 0d38ed479c..3d6a576a8c 100644
--- a/libs/json/luasrc/json.lua
+++ b/libs/json/luasrc/json.lua
@@ -11,16 +11,49 @@ You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
$Id$
+
+Decoder:
+ Info:
+ null will be decoded to luci.json.null if first parameter of Decoder() is true
+
+ Example:
+ decoder = luci.json.Decoder()
+ luci.ltn12.pump.all(luci.ltn12.source.string("decodableJSON"), decoder:sink())
+ luci.util.dumptable(decoder:get())
+
+ Known issues:
+ does not support unicode conversion \uXXYY with XX != 00 will be ignored
+
+
+Encoder:
+ Info:
+ Accepts numbers, strings, nil, booleans as they are
+ Accepts luci.json.null as replacement for nil
+ Accepts full associative and full numerically indexed tables
+ Mixed tables will loose their associative values during conversion
+ Iterator functions will be encoded as an array of their return values
+ Non-iterator functions will probably corrupt the encoder
+
+ Example:
+ encoder = luci.json.Encoder(encodableData)
+ luci.ltn12.pump.all(encoder:source(), luci.ltn12.sink.file(io.open("someFile", w)))
]]--
local util = require "luci.util"
-local ltn12 = require "luci.ltn12"
local table = require "table"
+local string = require "string"
local coroutine = require "coroutine"
local assert = assert
local tonumber = tonumber
+local tostring = tostring
local error = error
+local type = type
+local pairs = pairs
+local ipairs = ipairs
+local next = next
+
+local getmetatable = getmetatable
module "luci.json"
@@ -30,9 +63,161 @@ function null()
return null
end
+
+Encoder = util.class()
+
+--- Creates a new Encoder.
+-- @param data Data to be encoded.
+-- @param buffersize Buffersize of returned data.
+-- @param fastescape Use non-standard escaping (don't escape control chars)
+function Encoder.__init__(self, data, buffersize, fastescape)
+ self.data = data
+ self.buffersize = buffersize or 512
+ self.buffer = ""
+ self.fastescape = fastescape
+
+ getmetatable(self).__call = Encoder.source
+end
+
+--- Create an LTN12 source from the encoder object
+-- @return LTN12 source
+function Encoder.source(self)
+ local source = coroutine.create(self.dispatch)
+ return function()
+ local res, data = coroutine.resume(source, self, self.data, true)
+ if res then
+ return data
+ else
+ return nil, data
+ end
+ end
+end
+
+function Encoder.dispatch(self, data, start)
+ local parser = self.parsers[type(data)]
+
+ parser(self, data)
+
+ if start then
+ if #self.buffer > 0 then
+ coroutine.yield(self.buffer)
+ end
+
+ coroutine.yield()
+ end
+end
+
+function Encoder.put(self, chunk)
+ if self.buffersize < 2 then
+ corountine.yield(chunk)
+ else
+ if #self.buffer + #chunk > self.buffersize then
+ local written = 0
+ local fbuffer = self.buffersize - #self.buffer
+
+ coroutine.yield(self.buffer .. chunk:sub(written + 1, fbuffer))
+ written = fbuffer
+
+ while #chunk - written > self.buffersize do
+ fbuffer = written + self.buffersize
+ coroutine.yield(chunk:sub(written + 1, fbuffer))
+ written = fbuffer
+ end
+
+ self.buffer = chunk:sub(written + 1)
+ else
+ self.buffer = self.buffer .. chunk
+ end
+ end
+end
+
+function Encoder.parse_nil(self)
+ self:put("null")
+end
+
+function Encoder.parse_bool(self, obj)
+ self:put(obj and "true" or "false")
+end
+
+function Encoder.parse_number(self, obj)
+ self:put(tostring(obj))
+end
+
+function Encoder.parse_string(self, obj)
+ if self.fastescape then
+ self:put('"' .. obj:gsub('\\', '\\\\'):gsub('"', '\\"') .. '"')
+ else
+ self:put('"' ..
+ obj:gsub('[%c\\"]',
+ function(char)
+ return '\\u00%02x' % char:byte()
+ end
+ )
+ .. '"')
+ end
+end
+
+function Encoder.parse_iter(self, obj)
+ if obj == null then
+ return self:put("null")
+ end
+
+ if type(obj) == "table" and (#obj == 0 and next(obj)) then
+ self:put("{")
+ local first = true
+
+ for key, entry in pairs(obj) do
+ first = first or self:put(",")
+ first = first and false
+ self:parse_string(tostring(key))
+ self:put(":")
+ self:dispatch(entry)
+ end
+
+ self:put("}")
+ else
+ self:put("[")
+ local first = true
+
+ if type(obj) == "table" then
+ for i, entry in pairs(obj) do
+ first = first or self:put(",")
+ first = first and nil
+ self:dispatch(entry)
+ end
+ else
+ for entry in obj do
+ first = first or self:put(",")
+ first = first and nil
+ self:dispatch(entry)
+ end
+ end
+
+ self:put("]")
+ end
+end
+
+Encoder.parsers = {
+ ['nil'] = Encoder.parse_nil,
+ ['table'] = Encoder.parse_iter,
+ ['number'] = Encoder.parse_number,
+ ['string'] = Encoder.parse_string,
+ ['boolean'] = Encoder.parse_bool,
+ ['function'] = Encoder.parse_iter
+}
+
+
+
Decoder = util.class()
---- Create an LTN12 sink from the decoder object
+--- Create a new Decoder object.
+-- @param customnull User luci.json.null instead of nil
+function Decoder.__init__(self, customnull)
+ self.cnull = customnull
+ getmetatable(self).__call = Decoder.sink
+end
+
+--- Create an LTN12 sink from the decoder object.
-- @return LTN12 sink
function Decoder.sink(self)
local sink = coroutine.create(self.dispatch)
@@ -48,62 +233,41 @@ function Decoder.get(self)
return self.data
end
-
function Decoder.dispatch(self, chunk, src_err, strict)
local robject, object
+ local oset = false
while chunk do
- if #chunk < 1 then
+ while chunk and #chunk < 1 do
chunk = self:fetch()
end
assert(not strict or chunk, "Unexpected EOS")
- if not chunk then
- break
- end
+ if not chunk then break end
- local parser = nil
local char = chunk:sub(1, 1)
+ local parser = self.parsers[char]
+ or (char:match("%s") and self.parse_space)
+ or (char:match("[0-9-]") and self.parse_number)
+ or error("Unexpected char '%s'" % char)
- if char == '"' then
- parser = self.parse_string
- elseif char == 't' then
- parser = self.parse_true
- elseif char == 'f' then
- parser = self.parse_false
- elseif char == 'n' then
- parser = self.parse_null
- elseif char == '[' then
- parser = self.parse_array
- elseif char == '{' then
- parser = self.parse_object
- elseif char:match("%s") then
- parser = self.parse_space
- elseif char:match("[0-9-]") then
- parser = self.parse_number
- end
+ chunk, robject = parser(self, chunk)
- if parser then
- chunk, robject = parser(self, chunk)
-
- if robject ~= nil then
- assert(object == nil, "Scope violation: Too many objects")
- object = robject
- end
-
- if strict and object ~= nil then
+ if parser ~= self.parse_space then
+ assert(not oset, "Scope violation: Too many objects")
+ object = robject
+ oset = true
+
+ if strict then
return chunk, object
end
- else
- error("Unexpected char '%s'" % char)
end
end
assert(not src_err, src_err)
- assert(object ~= nil, "Unexpected EOS")
+ assert(oset, "Unexpected EOS")
self.data = object
- return chunk, object
end
@@ -162,7 +326,7 @@ end
function Decoder.parse_null(self, chunk)
- return self:parse_literal(chunk, "null", null)
+ return self:parse_literal(chunk, "null", self.cnull and null)
end
@@ -224,6 +388,14 @@ function Decoder.parse_escape(self, chunk)
return chunk, '"'
elseif char == "\\" then
return chunk, "\\"
+ elseif char == "u" then
+ chunk = self:fetch_atleast(chunk, 4)
+ local s1, s2 = chunk:sub(1, 2), chunk:sub(3, 4)
+ s1, s2 = tonumber(s1, 16), tonumber(s2, 16)
+ assert(s1 and s2, "Invalid Unicode character")
+
+ -- ToDo: Unicode support
+ return chunk:sub(5), s1 == 0 and string.char(s2) or ""
elseif char == "/" then
return chunk, "/"
elseif char == "b" then
@@ -236,14 +408,6 @@ function Decoder.parse_escape(self, chunk)
return chunk, "\r"
elseif char == "t" then
return chunk, "\t"
- elseif char == "u" then
- chunk = self:fetch_atleast(chunk, 4)
- local s1, s2 = chunk:sub(1, 4):match("^([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])$")
- assert(s1 and s2, "Invalid Unicode character 'U+%s%s'" % {s1, s2})
- s1, s2 = tonumber(s1, 16), tonumber(s2, 16)
-
- -- ToDo: Unicode support
- return chunk:sub(5), s1 == 0 and s2 or ""
else
error("Unexpected escaping sequence '\\%s'" % char)
end
@@ -253,6 +417,7 @@ end
function Decoder.parse_array(self, chunk)
chunk = chunk:sub(2)
local array = {}
+ local nextp = 1
local chunk, object = self:parse_delimiter(chunk, "%]")
@@ -262,7 +427,8 @@ function Decoder.parse_array(self, chunk)
repeat
chunk, object = self:dispatch(chunk, nil, true)
- table.insert(array, object)
+ table.insert(array, nextp, object)
+ nextp = nextp + 1
chunk, object = self:parse_delimiter(chunk, ",%]")
assert(object, "Delimiter expected")
@@ -316,4 +482,14 @@ function Decoder.parse_delimiter(self, chunk, delimiter)
return chunk, nil
end
end
-end \ No newline at end of file
+end
+
+
+Decoder.parsers = {
+ ['"'] = Decoder.parse_string,
+ ['t'] = Decoder.parse_true,
+ ['f'] = Decoder.parse_false,
+ ['n'] = Decoder.parse_null,
+ ['['] = Decoder.parse_array,
+ ['{'] = Decoder.parse_object
+} \ No newline at end of file
diff --git a/libs/web/luasrc/http.lua b/libs/web/luasrc/http.lua
index 17b27482e4..b1ffac2bef 100644
--- a/libs/web/luasrc/http.lua
+++ b/libs/web/luasrc/http.lua
@@ -197,6 +197,12 @@ function prepare_content(mime)
header("Content-Type", mime)
end
+--- Get the RAW HTTP input source
+-- @return HTTP LTN12 source
+function source()
+ return context.request.input
+end
+
--- Set the HTTP status code and status message.
-- @param code Status code
-- @param message Status message
diff --git a/modules/rpc/luasrc/controller/rpc.lua b/modules/rpc/luasrc/controller/rpc.lua
index aa77a8f248..98fafe3f1b 100644
--- a/modules/rpc/luasrc/controller/rpc.lua
+++ b/modules/rpc/luasrc/controller/rpc.lua
@@ -51,6 +51,7 @@ function rpc_auth()
local sauth = require "luci.sauth"
local http = require "luci.http"
local sys = require "luci.sys"
+ local ltn12 = require "luci.ltn12"
http.setfilehandler()
@@ -70,35 +71,53 @@ function rpc_auth()
end
http.prepare_content("application/json")
- http.write(jsonrpc.handle(server, http.content()))
+ ltn12.pump.all(jsonrpc.handle(server, http.source()), http.write)
end
function rpc_uci()
local uci = require "luci.controller.rpc.uci"
local jsonrpc = require "luci.jsonrpc"
local http = require "luci.http"
+ local ltn12 = require "luci.ltn12"
- http.setfilehandler()
http.prepare_content("application/json")
- http.write(jsonrpc.handle(uci, http.content()))
+ ltn12.pump.all(jsonrpc.handle(uci, http.source()), http.write)
end
function rpc_fs()
- local fs = require "luci.fs"
+ local util = require "luci.util"
+ local fs = util.clone(require "luci.fs")
local jsonrpc = require "luci.jsonrpc"
local http = require "luci.http"
+ local ltn12 = require "luci.ltn12"
+
+ function fs.readfile(filename)
+ if not pcall(require, "mime") then
+ error("Base64 support not available. Please install LuaSocket.")
+ end
+
+ return ltn12.source.chain(ltn12.source.file(filename), mime.encode("base64"))
+ end
+
+ function fs.writefile(filename, data)
+ if not pcall(require, "mime") then
+ error("Base64 support not available. Please install LuaSocket.")
+ end
+
+ local sink = ltn12.sink.chain(mime.decode("base64"), ltn12.sink.file(filename))
+ return ltn12.pump.all(ltn12.source.string(data), sink)
+ end
- http.setfilehandler()
http.prepare_content("application/json")
- http.write(jsonrpc.handle(fs, http.content()))
+ ltn12.pump.all(jsonrpc.handle(fs, http.source()), http.write)
end
function rpc_sys()
local sys = require "luci.sys"
local jsonrpc = require "luci.jsonrpc"
local http = require "luci.http"
+ local ltn12 = require "luci.ltn12"
- http.setfilehandler()
http.prepare_content("application/json")
- http.write(jsonrpc.handle(sys, http.content()))
+ ltn12.pump.all(jsonrpc.handle(sys, http.source()), http.write)
end \ No newline at end of file
diff --git a/modules/rpc/luasrc/jsonrpc.lua b/modules/rpc/luasrc/jsonrpc.lua
index c4fed2accb..1c0db8bcef 100644
--- a/modules/rpc/luasrc/jsonrpc.lua
+++ b/modules/rpc/luasrc/jsonrpc.lua
@@ -34,8 +34,10 @@ function resolve(mod, method)
end
end
-function handle(tbl, rawdata)
- local stat, json = luci.util.copcall(luci.json.Decode, rawdata)
+function handle(tbl, rawsource, ...)
+ local decoder = luci.json.Decoder()
+ local stat = luci.ltn12.pump.all(rawsource, decoder:sink())
+ local json = decoder:get()
local response
local success = false
@@ -54,22 +56,22 @@ function handle(tbl, rawdata)
nil, {code=-32600, message="Invalid request."})
end
else
- response = reply(json.jsonrpc, nil,
+ response = reply("2.0", nil,
nil, {code=-32700, message="Parse error."})
end
- return luci.json.Encode(response)
+ return luci.json.Encoder(response, ...):source()
end
function reply(jsonrpc, id, res, err)
require "luci.json"
- id = id or luci.json.Null
+ id = id or luci.json.null
-- 1.0 compatibility
if jsonrpc ~= "2.0" then
jsonrpc = nil
- res = res or luci.json.Null
- err = err or luci.json.Null
+ res = res or luci.json.null
+ err = err or luci.json.null
end
return {id=id, result=res, error=err, jsonrpc=jsonrpc}
@@ -83,7 +85,7 @@ function proxy(method, ...)
return nil, {code=-32602, message="Invalid params.", data=table.remove(res, 1)}
else
if #res <= 1 then
- return res[1] or luci.json.Null
+ return res[1] or luci.json.null
else
return res
end