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 sess = luci.http.getcookie("sysauth")
176 sess = sess and sess:match("^[A-F0-9]+$")
177 local user = luci.sauth.read(sess)
179 if not luci.util.contains(accs, user) then
181 local user = authen(luci.sys.user.checkpasswd, def)
182 if not user or not luci.util.contains(accs, user) then
185 local sid = luci.sys.uniqueid(16)
186 luci.http.header("Set-Cookie", "sysauth=" .. sid.."; path=/")
187 luci.sauth.write(sid, user)
190 luci.http.status(403, "Forbidden")
196 if track.setgroup then
197 luci.sys.process.setgroup(track.setgroup)
200 if track.setuser then
201 luci.sys.process.setuser(track.setuser)
204 if c and type(c.target) == "function" then
205 context.dispatched = c
206 stat, mod = luci.util.copcall(require, c.module)
208 luci.util.updfenv(c.target, mod)
211 stat, err = luci.util.copcall(c.target, unpack(args))
220 --- Generate the dispatching index using the best possible strategy.
221 function createindex()
222 local path = luci.util.libpath() .. "/controller/"
225 if luci.util.copcall(require, "luci.fastindex") then
226 createindex_fastindex(path, suff)
228 createindex_plain(path, suff)
232 --- Generate the dispatching index using the fastindex C-indexer.
233 -- @param path Controller base directory
234 -- @param suffix Controller file suffix
235 function createindex_fastindex(path, suffix)
239 fi = luci.fastindex.new("index")
240 fi.add(path .. "*" .. suffix)
241 fi.add(path .. "*/*" .. suffix)
245 for k, v in pairs(fi.indexes) do
250 --- Generate the dispatching index using the native file-cache based strategy.
251 -- @param path Controller base directory
252 -- @param suffix Controller file suffix
253 function createindex_plain(path, suffix)
258 local controllers = luci.util.combine(
259 luci.fs.glob(path .. "*" .. suffix) or {},
260 luci.fs.glob(path .. "*/*" .. suffix) or {}
264 cache = luci.fs.mtime(indexcache)
267 luci.fs.mkdir(indexcache)
268 luci.fs.chmod(indexcache, "a=,u=rwx")
269 cache = luci.fs.mtime(indexcache)
273 for i,c in ipairs(controllers) do
274 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
280 cachefile = indexcache .. "/" .. module
281 stime = luci.fs.mtime(c) or 0
282 ctime = luci.fs.mtime(cachefile) or 0
285 if not cache or stime > ctime then
286 stat, mod = luci.util.copcall(require, module)
288 if stat and mod and type(mod.index) == "function" then
289 index[module] = mod.index
292 luci.fs.writefile(cachefile, luci.util.get_bytecode(mod.index))
296 index[module] = loadfile(cachefile)
301 --- Create the dispatching tree from the index.
302 -- Build the index before if it does not exist yet.
303 function createtree()
308 context.tree = {nodes={}}
311 -- Load default translation
312 luci.i18n.loadc("default")
314 local scope = luci.util.clone(_G)
315 for k,v in pairs(luci.dispatcher) do
316 if type(v) == "function" then
321 for k, v in pairs(index) do
325 local stat, err = luci.util.copcall(v)
327 error500("createtree failed: " .. k .. ": " .. err)
334 --- Clone a node of the dispatching tree to another position.
335 -- @param path Virtual path destination
336 -- @param clone Virtual path source
337 -- @param title Destination node title (optional)
338 -- @param order Destination node order value (optional)
339 -- @return Dispatching tree node
340 function assign(path, clone, title, order)
341 local obj = node(unpack(path))
348 local c = context.tree
349 for k, v in ipairs(clone) do
350 if not c.nodes[v] then
351 c.nodes[v] = {nodes={}}
357 setmetatable(obj, {__index = c})
362 --- Create a new dispatching node and define common parameters.
363 -- @param path Virtual path
364 -- @param target Target function to call when dispatched.
365 -- @param title Destination node title
366 -- @param order Destination node order value (optional)
367 -- @return Dispatching tree node
368 function entry(path, target, title, order)
369 local c = node(unpack(path))
374 c.module = getfenv(2)._NAME
379 --- Fetch or create a new dispatching node.
380 -- @param ... Virtual path
381 -- @return Dispatching tree node
383 local c = context.tree
386 for k,v in ipairs(arg) do
387 if not c.nodes[v] then
388 c.nodes[v] = {nodes={}, auto=true}
394 c.module = getfenv(2)._NAME
403 --- Create a redirect to another dispatching node.
404 -- @param ... Virtual path destination
412 --- Rewrite the first x path values of the request.
413 -- @param n Number of path values to replace
414 -- @param ... Virtual path to replace removed path values with
415 function rewrite(n, ...)
419 table.remove(context.path, 1)
422 for i,r in ipairs(req) do
423 table.insert(context.path, i, r)
430 --- Create a function-call dispatching target.
431 -- @param name Target function of local controller
432 -- @param ... Additional parameters passed to the function
433 function call(name, ...)
435 return function() return getfenv()[name](unpack(argv)) end
438 --- Create a template render dispatching target.
439 -- @param name Template to be rendered
440 function template(name)
441 require("luci.template")
442 return function() luci.template.render(name) end
445 --- Create a CBI model dispatching target.
446 -- @param model CBI model tpo be rendered
449 require("luci.template")
452 local stat, maps = luci.util.copcall(luci.cbi.load, model, ...)
458 for i, res in ipairs(maps) do
459 local stat, err = luci.util.copcall(res.parse, res)
466 luci.template.render("cbi/header")
467 for i, res in ipairs(maps) do
470 luci.template.render("cbi/footer")
474 --- Create a CBI form model dispatching target.
475 -- @param model CBI form model tpo be rendered
478 require("luci.template")
481 local stat, maps = luci.util.copcall(luci.cbi.load, model, ...)
487 for i, res in ipairs(maps) do
488 local stat, err = luci.util.copcall(res.parse, res)
495 luci.template.render("header")
496 for i, res in ipairs(maps) do
499 luci.template.render("footer")