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()
45 --- Build the URL relative to the server webroot from given virtual path.
46 -- @param ... Virtual path
47 -- @return Relative URL
48 function build_url(...)
49 return luci.http.getenv("SCRIPT_NAME") .. "/" .. table.concat(arg, "/")
52 --- Send a 404 error code and render the "error404" template if available.
53 -- @param message Custom error message (optional)
55 function error404(message)
56 luci.http.status(404, "Not Found")
57 message = message or "Not Found"
59 require("luci.template")
60 if not luci.util.copcall(luci.template.render, "error404") then
61 luci.http.prepare_content("text/plain")
62 luci.http.write(message)
67 --- Send a 500 error code and render the "error500" template if available.
68 -- @param message Custom error message (optional)#
70 function error500(message)
71 luci.http.status(500, "Internal Server Error")
73 require("luci.template")
74 if not luci.util.copcall(luci.template.render, "error500", {message=message}) then
75 luci.http.prepare_content("text/plain")
76 luci.http.write(message)
81 function authenticator.htmlauth(validator, default)
82 local user = luci.http.formvalue("username")
83 local pass = luci.http.formvalue("password")
85 if user and validator(user, pass) then
90 require("luci.template")
92 luci.template.render("sysauth", {duser=default, fuser=user})
97 --- Dispatch an HTTP request.
98 -- @param request LuCI HTTP Request object
99 function httpdispatch(request)
100 luci.http.context.request = request
102 local pathinfo = request:getenv("PATH_INFO") or ""
104 for node in pathinfo:gmatch("[^/]+") do
105 table.insert(context.request, node)
108 dispatch(context.request)
112 --- Dispatches a LuCI virtual path.
113 -- @param request Virtual path
114 function dispatch(request)
115 context.path = request
118 luci.i18n.setlanguage(require("luci.config").main.lang)
120 if not context.tree then
124 local c = context.tree
129 for i, s in ipairs(request) do
132 if not c or c.leaf then
136 for k, v in pairs(c) do
142 for j=n+1, #request do
143 table.insert(args, request[j])
148 require("luci.i18n").loadc(track.i18n)
151 -- Init template engine
152 local tpl = require("luci.template")
154 tpl.context.viewns = viewns
155 viewns.write = luci.http.write
156 viewns.translate = function(...) return require("luci.i18n").translate(...) end
157 viewns.controller = luci.http.getenv("SCRIPT_NAME")
158 viewns.media = luci.config.main.mediaurlbase
159 viewns.resource = luci.config.main.resourcebase
160 viewns.REQUEST_URI = luci.http.getenv("SCRIPT_NAME") .. (luci.http.getenv("PATH_INFO") or "")
162 if track.dependent then
163 local stat, err = pcall(assert, not track.auto)
170 if track.sysauth then
171 require("luci.sauth")
172 local authen = authenticator[track.sysauth_authenticator]
173 local def = (type(track.sysauth) == "string") and track.sysauth
174 local accs = def and {track.sysauth} or track.sysauth
175 local user = luci.sauth.read(luci.http.getcookie("sysauth"))
177 if not luci.util.contains(accs, user) then
179 local user = authen(luci.sys.user.checkpasswd, def)
180 if not user or not luci.util.contains(accs, user) then
183 local sid = luci.sys.uniqueid(16)
184 luci.http.header("Set-Cookie", "sysauth=" .. sid.."; path=/")
185 luci.sauth.write(sid, user)
188 luci.http.status(403, "Forbidden")
192 luci.http.status(403, "Forbidden")
197 if track.setgroup then
198 luci.sys.process.setgroup(track.setgroup)
201 if track.setuser then
202 luci.sys.process.setuser(track.setuser)
205 if c and type(c.target) == "function" then
206 context.dispatched = c
207 stat, mod = luci.util.copcall(require, c.module)
209 luci.util.updfenv(c.target, mod)
212 stat, err = luci.util.copcall(c.target, unpack(args))
221 --- Generate the dispatching index using the best possible strategy.
222 function createindex()
223 local path = luci.util.libpath() .. "/controller/"
226 if luci.util.copcall(require, "luci.fastindex") then
227 createindex_fastindex(path, suff)
229 createindex_plain(path, suff)
233 --- Generate the dispatching index using the fastindex C-indexer.
234 -- @param path Controller base directory
235 -- @param suffix Controller file suffix
236 function createindex_fastindex(path, suffix)
240 fi = luci.fastindex.new("index")
241 fi.add(path .. "*" .. suffix)
242 fi.add(path .. "*/*" .. suffix)
246 for k, v in pairs(fi.indexes) do
251 --- Generate the dispatching index using the native file-cache based strategy.
252 -- @param path Controller base directory
253 -- @param suffix Controller file suffix
254 function createindex_plain(path, suffix)
259 local controllers = luci.util.combine(
260 luci.fs.glob(path .. "*" .. suffix) or {},
261 luci.fs.glob(path .. "*/*" .. suffix) or {}
265 cache = luci.fs.mtime(indexcache)
268 luci.fs.mkdir(indexcache)
269 luci.fs.chmod(indexcache, "a=,u=rwx")
270 cache = luci.fs.mtime(indexcache)
274 for i,c in ipairs(controllers) do
275 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
281 cachefile = indexcache .. "/" .. module
282 stime = luci.fs.mtime(c) or 0
283 ctime = luci.fs.mtime(cachefile) or 0
286 if not cache or stime > ctime then
287 stat, mod = luci.util.copcall(require, module)
289 if stat and mod and type(mod.index) == "function" then
290 index[module] = mod.index
293 luci.fs.writefile(cachefile, luci.util.get_bytecode(mod.index))
297 index[module] = loadfile(cachefile)
302 --- Create the dispatching tree from the index.
303 -- Build the index before if it does not exist yet.
304 function createtree()
309 context.tree = {nodes={}}
312 -- Load default translation
313 luci.i18n.loadc("default")
315 local scope = luci.util.clone(_G)
316 for k,v in pairs(luci.dispatcher) do
317 if type(v) == "function" then
322 for k, v in pairs(index) do
326 local stat, err = luci.util.copcall(v)
328 error500("createtree failed: " .. k .. ": " .. err)
335 --- Clone a node of the dispatching tree to another position.
336 -- @param path Virtual path destination
337 -- @param clone Virtual path source
338 -- @param title Destination node title (optional)
339 -- @param order Destination node order value (optional)
340 -- @return Dispatching tree node
341 function assign(path, clone, title, order)
342 local obj = node(unpack(path))
349 local c = context.tree
350 for k, v in ipairs(clone) do
351 if not c.nodes[v] then
352 c.nodes[v] = {nodes={}}
358 setmetatable(obj, {__index = c})
363 --- Create a new dispatching node and define common parameters.
364 -- @param path Virtual path
365 -- @param target Target function to call when dispatched.
366 -- @param title Destination node title
367 -- @param order Destination node order value (optional)
368 -- @return Dispatching tree node
369 function entry(path, target, title, order)
370 local c = node(unpack(path))
375 c.module = getfenv(2)._NAME
380 --- Fetch or create a new dispatching node.
381 -- @param ... Virtual path
382 -- @return Dispatching tree node
384 local c = context.tree
387 for k,v in ipairs(arg) do
388 if not c.nodes[v] then
389 c.nodes[v] = {nodes={}, auto=true}
395 c.module = getfenv(2)._NAME
404 --- Create a redirect to another dispatching node.
405 -- @param ... Virtual path destination
413 --- Rewrite the first x path values of the request.
414 -- @param n Number of path values to replace
415 -- @param ... Virtual path to replace removed path values with
416 function rewrite(n, ...)
420 table.remove(context.path, 1)
423 for i,r in ipairs(req) do
424 table.insert(context.path, i, r)
431 --- Create a function-call dispatching target.
432 -- @param name Target function of local controller
433 -- @param ... Additional parameters passed to the function
434 function call(name, ...)
436 return function() return getfenv()[name](unpack(argv)) end
439 --- Create a template render dispatching target.
440 -- @param name Template to be rendered
441 function template(name)
442 require("luci.template")
443 return function() luci.template.render(name) end
446 --- Create a CBI model dispatching target.
447 -- @param model CBI model tpo be rendered
450 require("luci.template")
453 local stat, maps = luci.util.copcall(luci.cbi.load, model, ...)
459 for i, res in ipairs(maps) do
460 local stat, err = luci.util.copcall(res.parse, res)
467 luci.template.render("cbi/header")
468 for i, res in ipairs(maps) do
471 luci.template.render("cbi/footer")
475 --- Create a CBI form model dispatching target.
476 -- @param model CBI form model tpo be rendered
479 require("luci.template")
482 local stat, maps = luci.util.copcall(luci.cbi.load, model, ...)
488 for i, res in ipairs(maps) do
489 local stat, err = luci.util.copcall(res.parse, res)
496 luci.template.render("header")
497 for i, res in ipairs(maps) do
500 luci.template.render("footer")