GSoC: Add LuCId RPC-Slave
[project/luci.git] / libs / lucid-rpc / luasrc / lucid / rpc / server.lua
1 --[[
2 LuCI - Lua Development Framework
3
4 Copyright 2009 Steven Barth <steven@midlink.org>
5
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
9
10 http://www.apache.org/licenses/LICENSE-2.0
11
12 $Id$
13 ]]
14
15 local ipairs, pairs = ipairs, pairs
16 local tostring, tonumber = tostring, tonumber
17 local pcall, assert, type, unpack = pcall, assert, type, unpack
18
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"
24
25 module "luci.lucid.rpc.server"
26
27 RQLIMIT = 32 * nixio.const.buffersize
28 VERSION = "1.0"
29
30 ERRNO_PARSE = -32700
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
38
39 ERRMSG = {
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."
48 }
49
50
51
52 Method = util.class()
53
54 function Method.extended(...)
55 local m = Method(...)
56 m.call = m.xcall
57 return m
58 end
59
60 function Method.__init__(self, method, description)
61 self.description = description
62 self.method = method
63 end
64
65 function Method.xcall(self, session, argv)
66 return self.method(session, unpack(argv))
67 end
68
69 function Method.call(self, session, argv)
70 return self.method(unpack(argv))
71 end
72
73 function Method.process(self, session, request, argv)
74 local stat, result = pcall(self.call, self, session, argv)
75
76 if stat then
77 return { result=result }
78 else
79 return { error={
80 code=ERRNO_UNKNOWN,
81 message=ERRMSG[ERRNO_UNKNOWN],
82 data=result
83 } }
84 end
85 end
86
87
88 local function remapipv6(adr)
89 local map = "::ffff:"
90 if adr:sub(1, #map) == map then
91 return adr:sub(#map+1)
92 else
93 return adr
94 end
95 end
96
97 Module = util.class()
98
99 function Module.__init__(self, description)
100 self.description = description
101 self.handler = {}
102 end
103
104 function Module.add(self, k, v)
105 self.handler[k] = v
106 end
107
108 -- Access Restrictions
109 function Module.restrict(self, restriction)
110 if not self.restrictions then
111 self.restrictions = {restriction}
112 else
113 self.restrictions[#self.restrictions+1] = restriction
114 end
115 end
116
117 -- Check restrictions
118 function Module.checkrestricted(self, session, request, argv)
119 if not self.restrictions then
120 return
121 end
122
123 for _, r in ipairs(self.restrictions) do
124 local stat = true
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
130 break
131 end
132 end
133 end
134
135 if r.interface ~= session.localif then
136 stat = false
137 end
138 end
139
140 if stat and r.user and session.user ~= r.user then -- User restriction
141 stat = false
142 end
143
144 if stat then
145 return
146 end
147 end
148
149 return {error={code=ERRNO_NOACCESS, message=ERRMSG[ERRNO_NOACCESS]}}
150 end
151
152 function Module.register(self, m, descr)
153 descr = descr or {}
154 for k, v in pairs(m) do
155 if util.instanceof(v, Method) then
156 self.handler[k] = v
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])
162 end
163 end
164 return self
165 end
166
167 function Module.process(self, session, request, argv)
168 local first, last = request:match("^([^.]+).?(.*)$")
169
170 local stat = self:checkrestricted(session, request, argv)
171 if stat then -- Access Denied
172 return stat
173 end
174
175 local hndl = first and self.handler[first]
176 if not hndl then
177 return {error={code=ERRNO_NOTFOUND, message=ERRMSG[ERRNO_NOTFOUND]}}
178 end
179
180 session.chain[#session.chain+1] = self
181 return hndl:process(session, last, argv)
182 end
183
184
185
186 Server = util.class()
187
188 function Server.__init__(self, root)
189 self.root = root
190 end
191
192 function Server.get_root(self)
193 return self.root
194 end
195
196 function Server.set_root(self, root)
197 self.root = root
198 end
199
200 function Server.reply(self, jsonrpc, id, res, err)
201 id = id or json.null
202
203 -- 1.0 compatibility
204 if jsonrpc ~= "2.0" then
205 jsonrpc = nil
206 res = res or json.null
207 err = err or json.null
208 end
209
210 return json.Encoder(
211 {id=id, result=res, error=err, jsonrpc=jsonrpc}, BUFSIZE
212 ):source()
213 end
214
215 function Server.process(self, client, env)
216 local decoder
217 local sinkout = client:sink()
218 client:setopt("socket", "sndtimeo", 90)
219 client:setopt("socket", "rcvtimeo", 90)
220
221 local close = false
222 local session = {server = self, chain = {}, client = client, env = env,
223 localaddr = remapipv6(client:getsockname())}
224 local req, stat, response, result, cb
225
226 repeat
227 local oldchunk = decoder and decoder.chunk
228 decoder = json.ActiveDecoder(client:blocksource(nil, RQLIMIT))
229 decoder.chunk = oldchunk
230
231 result, response, cb = nil, nil, nil
232
233 -- Read one request
234 stat, req = pcall(decoder.get, decoder)
235
236 if stat then
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)
245 end
246 close = result.close
247 else
248 if req.id ~= nil then
249 response = self:reply(req.jsonrpc, req.id, nil,
250 {code=ERRNO_INTERNAL, message=ERRMSG[ERRNO_INTERNAL]})
251 end
252 end
253 else
254 response = self:reply(req.jsonrpc, req.id,
255 nil, {code=ERRNO_INVALID, message=ERRMSG[ERRNO_INVALID]})
256 end
257 else
258 if nixio.errno() ~= nixio.const.EAGAIN then
259 response = self:reply("2.0", nil,
260 nil, {code=ERRNO_PARSE, message=ERRMSG[ERRNO_PARSE]})
261 --[[else
262 response = self:reply("2.0", nil,
263 nil, {code=ERRNO_TIMEOUT, message=ERRMSG_TIMEOUT})]]
264 end
265 close = true
266 end
267
268 if response then
269 ltn12.pump.all(response, sinkout)
270 end
271
272 if cb then
273 close = cb(client, session, self) or close
274 end
275 until close
276
277 client:shutdown()
278 client:close()
279 end