* Performance optimizations
[project/luci.git] / libs / web / luasrc / dispatcher.lua
1 --[[
2 LuCI - Dispatcher
3
4 Description:
5 The request dispatcher and module dispatcher generators
6
7 FileId:
8 $Id$
9
10 License:
11 Copyright 2008 Steven Barth <steven@midlink.org>
12
13 Licensed under the Apache License, Version 2.0 (the "License");
14 you may not use this file except in compliance with the License.
15 You may obtain a copy of the License at
16
17 http://www.apache.org/licenses/LICENSE-2.0
18
19 Unless required by applicable law or agreed to in writing, software
20 distributed under the License is distributed on an "AS IS" BASIS,
21 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 See the License for the specific language governing permissions and
23 limitations under the License.
24
25 ]]--
26 module("luci.dispatcher", package.seeall)
27 require("luci.http")
28 require("luci.sys")
29 require("luci.fs")
30
31 -- Local dispatch database
32 local tree = {nodes={}}
33
34 -- Index table
35 local index = {}
36
37 -- Global request object
38 request = {}
39
40 -- Active dispatched node
41 dispatched = nil
42
43 -- Status fields
44 built_index = false
45 built_tree = false
46
47
48 -- Builds a URL
49 function build_url(...)
50 return luci.http.dispatcher() .. "/" .. table.concat(arg, "/")
51 end
52
53 -- Sends a 404 error code and renders the "error404" template if available
54 function error404(message)
55 luci.http.status(404, "Not Found")
56 message = message or "Not Found"
57
58 require("luci.template")
59 if not pcall(luci.template.render, "error404") then
60 luci.http.prepare_content("text/plain")
61 print(message)
62 end
63 return false
64 end
65
66 -- Sends a 500 error code and renders the "error500" template if available
67 function error500(message)
68 luci.http.status(500, "Internal Server Error")
69
70 require("luci.template")
71 if not pcall(luci.template.render, "error500", {message=message}) then
72 luci.http.prepare_content("text/plain")
73 print(message)
74 end
75 return false
76 end
77
78 -- Creates a request object for dispatching
79 function httpdispatch()
80 local pathinfo = luci.http.env.PATH_INFO or ""
81 local c = tree
82
83 for s in pathinfo:gmatch("([%w-]+)") do
84 table.insert(request, s)
85 end
86
87 dispatch()
88 end
89
90 -- Dispatches a request
91 function dispatch()
92 if not built_tree then
93 createtree()
94 end
95
96 local c = tree
97 local track = {}
98
99 for i, s in ipairs(request) do
100 c = c.nodes[s]
101 if not c or c.leaf then
102 break
103 end
104
105 for k, v in pairs(c) do
106 track[k] = v
107 end
108 end
109
110
111 if track.i18n then
112 require("luci.i18n").loadc(track.i18n)
113 end
114
115 if track.setgroup then
116 luci.sys.process.setgroup(track.setgroup)
117 end
118
119 if track.setuser then
120 luci.sys.process.setuser(track.setuser)
121 end
122
123 -- Init template engine
124 local tpl = require("luci.template")
125 tpl.viewns.translate = function(...) return require("luci.i18n").translate(...) end
126 tpl.viewns.controller = luci.http.dispatcher()
127 tpl.viewns.uploadctrl = luci.http.dispatcher_upload()
128 tpl.viewns.media = luci.config.main.mediaurlbase
129 tpl.viewns.resource = luci.config.main.resourcebase
130 tpl.viewns.REQUEST_URI = luci.http.env.SCRIPT_NAME .. luci.http.env.PATH_INFO
131
132
133 if c and type(c.target) == "function" then
134 dispatched = c
135 stat, mod = pcall(require, c.module)
136 if stat then
137 luci.util.updfenv(c.target, mod)
138 end
139
140 stat, err = pcall(c.target)
141 if not stat then
142 error500(err)
143 end
144 else
145 error404()
146 end
147 end
148
149 -- Generates the dispatching tree
150 function createindex()
151 index = {}
152 local path = luci.sys.libpath() .. "/controller/"
153 local suff = ".lua"
154
155 --[[if pcall(require, "fastindex") then
156 createindex_fastindex(path, suff)
157 else
158 createindex_plain(path, suff)
159 end]]--
160
161 createindex_plain(path, suff)
162
163 built_index = true
164 end
165
166 -- Uses fastindex to create the dispatching tree
167 function createindex_fastindex(path, suffix)
168 local fi = fastindex.new("index")
169 fi.add(path .. "*" .. suffix)
170 fi.add(path .. "*/*" .. suffix)
171 fi.scan()
172
173 for k, v in pairs(fi.indexes) do
174 index[v[2]] = v[1]
175 end
176 end
177
178 -- Calls the index function of all available controllers
179 function createindex_plain(path, suffix)
180 local cache = nil
181
182 local controllers = luci.util.combine(
183 luci.fs.glob(path .. "*" .. suffix) or {},
184 luci.fs.glob(path .. "*/*" .. suffix) or {}
185 )
186
187 if indexcache then
188 cache = luci.fs.mtime(indexcache)
189
190 if not cache then
191 luci.fs.mkdir(indexcache)
192 luci.fs.chmod(indexcache, "a=,u=rwx")
193 cache = luci.fs.mtime(indexcache)
194 end
195 end
196
197 for i,c in ipairs(controllers) do
198 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
199 local cachefile = indexcache .. "/" .. module
200 local stime
201 local ctime
202
203 if cache then
204 stime = luci.fs.mtime(c) or 0
205 ctime = luci.fs.mtime(cachefile) or 0
206 end
207
208 if not cache or stime > ctime then
209 stat, mod = pcall(require, module)
210
211 if stat and mod and type(mod.index) == "function" then
212 index[module] = mod.index
213
214 if cache then
215 luci.fs.writefile(cachefile, luci.util.dump(mod.index))
216 end
217 end
218 else
219 index[module] = loadfile(cachefile)
220 end
221 end
222 end
223
224 -- Creates the dispatching tree from the index
225 function createtree()
226 if not built_index then
227 createindex()
228 end
229
230 require("luci.i18n")
231
232 -- Load default translation
233 luci.i18n.loadc("default")
234
235 local scope = luci.util.clone(_G)
236 for k,v in pairs(_M) do
237 if type(v) == "function" then
238 scope[k] = v
239 end
240 end
241
242 for k, v in pairs(index) do
243 scope._NAME = k
244 setfenv(v, scope)
245
246 local stat, err = pcall(v)
247 if not stat then
248 error500(err)
249 os.exit(1)
250 end
251 end
252
253 built_tree = true
254 end
255
256 -- Shortcut for creating a dispatching node
257 function entry(path, target, title, order, add)
258 add = add or {}
259
260 local c = node(path)
261 c.target = target
262 c.title = title
263 c.order = order
264 c.module = getfenv(2)._NAME
265
266 for k,v in pairs(add) do
267 c[k] = v
268 end
269
270 return c
271 end
272
273 -- Fetch a dispatching node
274 function node(...)
275 local c = tree
276
277 if arg[1] and type(arg[1]) == "table" then
278 arg = arg[1]
279 end
280
281 for k,v in ipairs(arg) do
282 if not c.nodes[v] then
283 c.nodes[v] = {nodes={}, module=getfenv(2)._NAME}
284 end
285
286 c = c.nodes[v]
287 end
288
289 return c
290 end
291
292 -- Subdispatchers --
293 function alias(...)
294 local req = arg
295 return function()
296 request = req
297 dispatch()
298 end
299 end
300
301 function rewrite(n, ...)
302 local req = arg
303 return function()
304 for i=1,n do
305 table.remove(request, 1)
306 end
307
308 for i,r in ipairs(req) do
309 table.insert(request, i, r)
310 end
311
312 dispatch()
313 end
314 end
315
316 function call(name)
317 return function() getfenv()[name]() end
318 end
319
320 function template(name)
321 require("luci.template")
322 return function() luci.template.render(name) end
323 end
324
325 function cbi(model)
326 require("luci.cbi")
327 require("luci.template")
328
329 return function()
330 local stat, res = pcall(luci.cbi.load, model)
331 if not stat then
332 error500(res)
333 return true
334 end
335
336 local stat, err = pcall(res.parse, res)
337 if not stat then
338 error500(err)
339 return true
340 end
341
342 luci.template.render("cbi/header")
343 res:render()
344 luci.template.render("cbi/footer")
345 end
346 end