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"
27 module "luci.lucid.rpc.server"
29 RQLIMIT = 32 * nixio.const.buffersize
33 ERRNO_INVALID = -32600
34 ERRNO_UNKNOWN = -32001
35 ERRNO_TIMEOUT = -32000
36 ERRNO_NOTFOUND = -32601
37 ERRNO_NOACCESS = -32002
38 ERRNO_INTERNAL = -32603
39 ERRNO_NOSUPPORT = -32003
42 [ERRNO_PARSE] = "Parse error.",
43 [ERRNO_INVALID] = "Invalid request.",
44 [ERRNO_TIMEOUT] = "Connection timeout.",
45 [ERRNO_UNKNOWN] = "Unknown error.",
46 [ERRNO_NOTFOUND] = "Method not found.",
47 [ERRNO_NOACCESS] = "Access denied.",
48 [ERRNO_INTERNAL] = "Internal error.",
49 [ERRNO_NOSUPPORT] = "Operation not supported."
53 --- Create an RPC method wrapper.
55 -- @param method Lua function
56 -- @param description Method description
57 -- @return Wrapped RPC method
60 --- Create an extended wrapped RPC method.
62 -- @param method Lua function
63 -- @param description Method description
64 -- @return Wrapped RPC method
65 function Method.extended(...)
71 function Method.__init__(self, method, description)
72 self.description = description
76 --- Extended call the associated function.
77 -- @param session Session storage
78 -- @param argv Request parameters
79 -- @return function call response
80 function Method.xcall(self, session, argv)
81 return self.method(session, unpack(argv))
84 --- Standard call the associated function.
85 -- @param session Session storage
86 -- @param argv Request parameters
87 -- @return function call response
88 function Method.call(self, session, argv)
89 return self.method(unpack(argv))
92 --- Process a given request and create a JSON response.
93 -- @param session Session storage
94 -- @param request Requested method
95 -- @param argv Request parameters
96 function Method.process(self, session, request, argv)
97 local stat, result = pcall(self.call, self, session, argv)
100 return { result=result }
104 message=ERRMSG[ERRNO_UNKNOWN],
110 -- Remap IPv6-IPv4-compatibility addresses to IPv4 addresses
111 local function remapipv6(adr)
112 local map = "::ffff:"
113 if adr:sub(1, #map) == map then
114 return adr:sub(#map+1)
121 --- Create an RPC module.
123 -- @param description Method description
124 -- @return RPC module
125 Module = util.class()
127 function Module.__init__(self, description)
128 self.description = description
135 function Module.add(self, k, v)
139 --- Add an access restriction.
140 -- @param restriction Restriction specification
141 function Module.restrict(self, restriction)
142 if not self.restrictions then
143 self.restrictions = {restriction}
145 self.restrictions[#self.restrictions+1] = restriction
149 --- Enforce access restrictions.
150 -- @param request Request object
151 -- @return nil or HTTP statuscode, table of headers, response source
152 function Module.checkrestricted(self, session, request, argv)
153 if not self.restrictions then
157 for _, r in ipairs(self.restrictions) do
159 if stat and r.interface then -- Interface restriction
160 if not session.localif then
161 for _, v in ipairs(session.env.interfaces) do
162 if v.addr == session.localaddr then
163 session.localif = v.name
169 if r.interface ~= session.localif then
174 if stat and r.user and session.user ~= r.user then -- User restriction
183 return {error={code=ERRNO_NOACCESS, message=ERRMSG[ERRNO_NOACCESS]}}
186 --- Register a handler, submodule or function.
188 -- @param descr description
189 -- @return Module (self)
190 function Module.register(self, m, descr)
192 for k, v in pairs(m) do
193 if util.instanceof(v, Method) then
195 elseif type(v) == "table" then
196 self.handler[k] = Module()
197 self.handler[k]:register(v, descr[k])
198 elseif type(v) == "function" then
199 self.handler[k] = Method(v, descr[k])
205 --- Process a request.
206 -- @param session Session storage
207 -- @param request Request object
208 -- @param argv Request parameters
209 -- @return JSON response object
210 function Module.process(self, session, request, argv)
211 local first, last = request:match("^([^.]+).?(.*)$")
213 local stat = self:checkrestricted(session, request, argv)
214 if stat then -- Access Denied
218 local hndl = first and self.handler[first]
220 return {error={code=ERRNO_NOTFOUND, message=ERRMSG[ERRNO_NOTFOUND]}}
223 session.chain[#session.chain+1] = self
224 return hndl:process(session, last, argv)
228 --- Create a server object.
230 -- @param root Root module
231 -- @return Server object
232 Server = util.class()
234 function Server.__init__(self, root)
238 --- Get the associated root module.
239 -- @return Root module
240 function Server.get_root(self)
244 --- Set a new root module.
245 -- @param root Root module
246 function Server.set_root(self, root)
250 --- Create a JSON reply.
251 -- @param jsonrpc JSON-RPC version
252 -- @param id Message id
255 -- @reutrn JSON response source
256 function Server.reply(self, jsonrpc, id, res, err)
260 if jsonrpc ~= "2.0" then
262 res = res or json.null
263 err = err or json.null
267 {id=id, result=res, error=err, jsonrpc=jsonrpc}, BUFSIZE
271 --- Handle a new client connection.
272 -- @param client client socket
273 -- @param env superserver environment
274 function Server.process(self, client, env)
276 local sinkout = client:sink()
277 client:setopt("socket", "sndtimeo", 90)
278 client:setopt("socket", "rcvtimeo", 90)
281 local session = {server = self, chain = {}, client = client, env = env,
282 localaddr = remapipv6(client:getsockname())}
283 local req, stat, response, result, cb
286 local oldchunk = decoder and decoder.chunk
287 decoder = json.ActiveDecoder(client:blocksource(nil, RQLIMIT))
288 decoder.chunk = oldchunk
290 result, response, cb = nil, nil, nil
293 stat, req = pcall(decoder.get, decoder)
296 if type(req) == "table" and type(req.method) == "string"
297 and (not req.params or type(req.params) == "table") then
298 req.params = req.params or {}
299 result, cb = self.root:process(session, req.method, req.params)
300 if type(result) == "table" then
301 if req.id ~= nil then
302 response = self:reply(req.jsonrpc, req.id,
303 result.result, result.error)
307 if req.id ~= nil then
308 response = self:reply(req.jsonrpc, req.id, nil,
309 {code=ERRNO_INTERNAL, message=ERRMSG[ERRNO_INTERNAL]})
313 response = self:reply(req.jsonrpc, req.id,
314 nil, {code=ERRNO_INVALID, message=ERRMSG[ERRNO_INVALID]})
317 if nixio.errno() ~= nixio.const.EAGAIN then
318 response = self:reply("2.0", nil,
319 nil, {code=ERRNO_PARSE, message=ERRMSG[ERRNO_PARSE]})
321 response = self:reply("2.0", nil,
322 nil, {code=ERRNO_TIMEOUT, message=ERRMSG_TIMEOUT})]]
328 ltn12.pump.all(response, sinkout)
332 close = cb(client, session, self) or close