2 LuCI - Lua Development Framework
4 Copyright 2009 Steven Barth <steven@midlink.org>
6 Licensed under the Apache License, Version 2.0 (the "License");
7 you may not use this file except in compliance with the License.
8 You may obtain a copy of the License at
10 http://www.apache.org/licenses/LICENSE-2.0
15 local ipairs, pairs = ipairs, pairs
16 local tostring, tonumber = tostring, tonumber
17 local pcall, assert, type, unpack = pcall, assert, type, unpack
19 local nixio = require "nixio"
20 local json = require "luci.json"
21 local util = require "luci.util"
22 local table = require "table"
23 local ltn12 = require "luci.ltn12"
25 module "luci.lucid.rpc.server"
27 RQLIMIT = 32 * nixio.const.buffersize
31 ERRNO_INVALID = -32600
32 ERRNO_UNKNOWN = -32001
33 ERRNO_TIMEOUT = -32000
34 ERRNO_NOTFOUND = -32601
35 ERRNO_NOACCESS = -32002
36 ERRNO_INTERNAL = -32603
37 ERRNO_NOSUPPORT = -32003
40 [ERRNO_PARSE] = "Parse error.",
41 [ERRNO_INVALID] = "Invalid request.",
42 [ERRNO_TIMEOUT] = "Connection timeout.",
43 [ERRNO_UNKNOWN] = "Unknown error.",
44 [ERRNO_NOTFOUND] = "Method not found.",
45 [ERRNO_NOACCESS] = "Access denied.",
46 [ERRNO_INTERNAL] = "Internal error.",
47 [ERRNO_NOSUPPORT] = "Operation not supported."
54 function Method.extended(...)
60 function Method.__init__(self, method, description)
61 self.description = description
65 function Method.xcall(self, session, argv)
66 return self.method(session, unpack(argv))
69 function Method.call(self, session, argv)
70 return self.method(unpack(argv))
73 function Method.process(self, session, request, argv)
74 local stat, result = pcall(self.call, self, session, argv)
77 return { result=result }
81 message=ERRMSG[ERRNO_UNKNOWN],
88 local function remapipv6(adr)
90 if adr:sub(1, #map) == map then
91 return adr:sub(#map+1)
99 function Module.__init__(self, description)
100 self.description = description
104 function Module.add(self, k, v)
108 -- Access Restrictions
109 function Module.restrict(self, restriction)
110 if not self.restrictions then
111 self.restrictions = {restriction}
113 self.restrictions[#self.restrictions+1] = restriction
117 -- Check restrictions
118 function Module.checkrestricted(self, session, request, argv)
119 if not self.restrictions then
123 for _, r in ipairs(self.restrictions) do
125 if stat and r.interface then -- Interface restriction
126 if not session.localif then
127 for _, v in ipairs(session.env.interfaces) do
128 if v.addr == session.localaddr then
129 session.localif = v.name
135 if r.interface ~= session.localif then
140 if stat and r.user and session.user ~= r.user then -- User restriction
149 return {error={code=ERRNO_NOACCESS, message=ERRMSG[ERRNO_NOACCESS]}}
152 function Module.register(self, m, descr)
154 for k, v in pairs(m) do
155 if util.instanceof(v, Method) then
157 elseif type(v) == "table" then
158 self.handler[k] = Module()
159 self.handler[k]:register(v, descr[k])
160 elseif type(v) == "function" then
161 self.handler[k] = Method(v, descr[k])
167 function Module.process(self, session, request, argv)
168 local first, last = request:match("^([^.]+).?(.*)$")
170 local stat = self:checkrestricted(session, request, argv)
171 if stat then -- Access Denied
175 local hndl = first and self.handler[first]
177 return {error={code=ERRNO_NOTFOUND, message=ERRMSG[ERRNO_NOTFOUND]}}
180 session.chain[#session.chain+1] = self
181 return hndl:process(session, last, argv)
186 Server = util.class()
188 function Server.__init__(self, root)
192 function Server.get_root(self)
196 function Server.set_root(self, root)
200 function Server.reply(self, jsonrpc, id, res, err)
204 if jsonrpc ~= "2.0" then
206 res = res or json.null
207 err = err or json.null
211 {id=id, result=res, error=err, jsonrpc=jsonrpc}, BUFSIZE
215 function Server.process(self, client, env)
217 local sinkout = client:sink()
218 client:setopt("socket", "sndtimeo", 90)
219 client:setopt("socket", "rcvtimeo", 90)
222 local session = {server = self, chain = {}, client = client, env = env,
223 localaddr = remapipv6(client:getsockname())}
224 local req, stat, response, result, cb
227 local oldchunk = decoder and decoder.chunk
228 decoder = json.ActiveDecoder(client:blocksource(nil, RQLIMIT))
229 decoder.chunk = oldchunk
231 result, response, cb = nil, nil, nil
234 stat, req = pcall(decoder.get, decoder)
237 if type(req) == "table" and type(req.method) == "string"
238 and (not req.params or type(req.params) == "table") then
239 req.params = req.params or {}
240 result, cb = self.root:process(session, req.method, req.params)
241 if type(result) == "table" then
242 if req.id ~= nil then
243 response = self:reply(req.jsonrpc, req.id,
244 result.result, result.error)
248 if req.id ~= nil then
249 response = self:reply(req.jsonrpc, req.id, nil,
250 {code=ERRNO_INTERNAL, message=ERRMSG[ERRNO_INTERNAL]})
254 response = self:reply(req.jsonrpc, req.id,
255 nil, {code=ERRNO_INVALID, message=ERRMSG[ERRNO_INVALID]})
258 if nixio.errno() ~= nixio.const.EAGAIN then
259 response = self:reply("2.0", nil,
260 nil, {code=ERRNO_PARSE, message=ERRMSG[ERRNO_PARSE]})
262 response = self:reply("2.0", nil,
263 nil, {code=ERRNO_TIMEOUT, message=ERRMSG_TIMEOUT})]]
269 ltn12.pump.all(response, sinkout)
273 close = cb(client, session, self) or close