5 The request dispatcher and module dispatcher generators
11 Copyright 2008 Steven Barth <steven@midlink.org>
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
17 http://www.apache.org/licenses/LICENSE-2.0
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.
27 --- LuCI web dispatcher.
28 module("luci.dispatcher", package.seeall)
34 context = luci.util.threadlocal()
43 --- Build the URL relative to the server webroot from given virtual path.
44 -- @param ... Virtual path
45 -- @return Relative URL
46 function build_url(...)
47 return luci.http.getenv("SCRIPT_NAME") .. "/" .. table.concat(arg, "/")
50 --- Send a 404 error code and render the "error404" template if available.
51 -- @param message Custom error message (optional)
53 function error404(message)
54 luci.http.status(404, "Not Found")
55 message = message or "Not Found"
57 require("luci.template")
58 if not luci.util.copcall(luci.template.render, "error404") then
59 luci.http.prepare_content("text/plain")
60 luci.http.write(message)
65 --- Send a 500 error code and render the "error500" template if available.
66 -- @param message Custom error message (optional)#
68 function error500(message)
69 luci.http.status(500, "Internal Server Error")
71 require("luci.template")
72 if not luci.util.copcall(luci.template.render, "error500", {message=message}) then
73 luci.http.prepare_content("text/plain")
74 luci.http.write(message)
79 --- Render and evaluate the system authentication login form.
80 -- @param default Default username
81 -- @return Authentication status
82 function sysauth(default)
83 local user = luci.http.formvalue("username")
84 local pass = luci.http.formvalue("password")
86 if user and luci.sys.user.checkpasswd(user, pass) then
87 local sid = luci.sys.uniqueid(16)
88 luci.http.header("Set-Cookie", "sysauth=" .. sid.."; path=/")
89 luci.sauth.write(sid, user)
93 require("luci.template")
95 luci.template.render("sysauth", {duser=default, fuser=user})
100 --- Dispatch an HTTP request.
101 -- @param request LuCI HTTP Request object
102 function httpdispatch(request)
103 luci.http.context.request = request
105 local pathinfo = request:getenv("PATH_INFO") or ""
107 for node in pathinfo:gmatch("[^/]+") do
108 table.insert(context.request, node)
111 dispatch(context.request)
115 --- Dispatches a LuCI virtual path.
116 -- @param request Virtual path
117 function dispatch(request)
118 context.path = request
121 luci.i18n.setlanguage(require("luci.config").main.lang)
123 if not context.tree then
127 local c = context.tree
132 for i, s in ipairs(request) do
135 if not c or c.leaf then
139 for k, v in pairs(c) do
145 for j=n+1, #request do
146 table.insert(args, request[j])
151 require("luci.i18n").loadc(track.i18n)
154 -- Init template engine
155 local tpl = require("luci.template")
157 tpl.context.viewns = viewns
158 viewns.write = luci.http.write
159 viewns.translate = function(...) return require("luci.i18n").translate(...) end
160 viewns.controller = luci.http.getenv("SCRIPT_NAME")
161 viewns.media = luci.config.main.mediaurlbase
162 viewns.resource = luci.config.main.resourcebase
163 viewns.REQUEST_URI = luci.http.getenv("SCRIPT_NAME") .. (luci.http.getenv("PATH_INFO") or "")
165 if track.dependent then
166 local stat, err = pcall(assert, not track.auto)
173 if track.sysauth then
174 require("luci.sauth")
175 local def = (type(track.sysauth) == "string") and track.sysauth
176 local accs = def and {track.sysauth} or track.sysauth
177 local user = luci.sauth.read(luci.http.getcookie("sysauth"))
180 if not luci.util.contains(accs, user) then
181 if not sysauth(def) then
187 if track.setgroup then
188 luci.sys.process.setgroup(track.setgroup)
191 if track.setuser then
192 luci.sys.process.setuser(track.setuser)
195 if c and type(c.target) == "function" then
196 context.dispatched = c
197 stat, mod = luci.util.copcall(require, c.module)
199 luci.util.updfenv(c.target, mod)
202 stat, err = luci.util.copcall(c.target, unpack(args))
211 --- Generate the dispatching index using the best possible strategy.
212 function createindex()
213 local path = luci.util.libpath() .. "/controller/"
216 if luci.util.copcall(require, "luci.fastindex") then
217 createindex_fastindex(path, suff)
219 createindex_plain(path, suff)
223 --- Generate the dispatching index using the fastindex C-indexer.
224 -- @param path Controller base directory
225 -- @param suffix Controller file suffix
226 function createindex_fastindex(path, suffix)
230 fi = luci.fastindex.new("index")
231 fi.add(path .. "*" .. suffix)
232 fi.add(path .. "*/*" .. suffix)
236 for k, v in pairs(fi.indexes) do
241 --- Generate the dispatching index using the native file-cache based strategy.
242 -- @param path Controller base directory
243 -- @param suffix Controller file suffix
244 function createindex_plain(path, suffix)
249 local controllers = luci.util.combine(
250 luci.fs.glob(path .. "*" .. suffix) or {},
251 luci.fs.glob(path .. "*/*" .. suffix) or {}
255 cache = luci.fs.mtime(indexcache)
258 luci.fs.mkdir(indexcache)
259 luci.fs.chmod(indexcache, "a=,u=rwx")
260 cache = luci.fs.mtime(indexcache)
264 for i,c in ipairs(controllers) do
265 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
271 cachefile = indexcache .. "/" .. module
272 stime = luci.fs.mtime(c) or 0
273 ctime = luci.fs.mtime(cachefile) or 0
276 if not cache or stime > ctime then
277 stat, mod = luci.util.copcall(require, module)
279 if stat and mod and type(mod.index) == "function" then
280 index[module] = mod.index
283 luci.fs.writefile(cachefile, luci.util.get_bytecode(mod.index))
287 index[module] = loadfile(cachefile)
292 --- Create the dispatching tree from the index.
293 -- Build the index before if it does not exist yet.
294 function createtree()
299 context.tree = {nodes={}}
302 -- Load default translation
303 luci.i18n.loadc("default")
305 local scope = luci.util.clone(_G)
306 for k,v in pairs(luci.dispatcher) do
307 if type(v) == "function" then
312 for k, v in pairs(index) do
316 local stat, err = luci.util.copcall(v)
318 error500("createtree failed: " .. k .. ": " .. err)
325 --- Clone a node of the dispatching tree to another position.
326 -- @param path Virtual path destination
327 -- @param clone Virtual path source
328 -- @param title Destination node title (optional)
329 -- @param order Destination node order value (optional)
330 -- @return Dispatching tree node
331 function assign(path, clone, title, order)
332 local obj = node(unpack(path))
339 local c = context.tree
340 for k, v in ipairs(clone) do
341 if not c.nodes[v] then
342 c.nodes[v] = {nodes={}}
348 setmetatable(obj, {__index = c})
353 --- Create a new dispatching node and define common parameters.
354 -- @param path Virtual path
355 -- @param target Target function to call when dispatched.
356 -- @param title Destination node title
357 -- @param order Destination node order value (optional)
358 -- @return Dispatching tree node
359 function entry(path, target, title, order)
360 local c = node(unpack(path))
365 c.module = getfenv(2)._NAME
370 --- Fetch or create a new dispatching node.
371 -- @param ... Virtual path
372 -- @return Dispatching tree node
374 local c = context.tree
377 for k,v in ipairs(arg) do
378 if not c.nodes[v] then
379 c.nodes[v] = {nodes={}, auto=true}
385 c.module = getfenv(2)._NAME
394 --- Create a redirect to another dispatching node.
395 -- @param ... Virtual path destination
403 --- Rewrite the first x path values of the request.
404 -- @param n Number of path values to replace
405 -- @param ... Virtual path to replace removed path values with
406 function rewrite(n, ...)
410 table.remove(context.path, 1)
413 for i,r in ipairs(req) do
414 table.insert(context.path, i, r)
421 --- Create a function-call dispatching target.
422 -- @param name Target function of local controller
423 -- @param ... Additional parameters passed to the function
424 function call(name, ...)
426 return function() return getfenv()[name](unpack(argv)) end
429 --- Create a template render dispatching target.
430 -- @param name Template to be rendered
431 function template(name)
432 require("luci.template")
433 return function() luci.template.render(name) end
436 --- Create a CBI model dispatching target.
437 -- @param model CBI model tpo be rendered
440 require("luci.template")
443 local stat, maps = luci.util.copcall(luci.cbi.load, model, ...)
449 for i, res in ipairs(maps) do
450 local stat, err = luci.util.copcall(res.parse, res)
457 luci.template.render("cbi/header")
458 for i, res in ipairs(maps) do
461 luci.template.render("cbi/footer")
465 --- Create a CBI form model dispatching target.
466 -- @param model CBI form model tpo be rendered
469 require("luci.template")
472 local stat, maps = luci.util.copcall(luci.cbi.load, model, ...)
478 for i, res in ipairs(maps) do
479 local stat, err = luci.util.copcall(res.parse, res)
486 luci.template.render("header")
487 for i, res in ipairs(maps) do
490 luci.template.render("footer")