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