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"):match("^[A-F0-9]+$")
176 local user = luci.sauth.read(sess)
178 if not luci.util.contains(accs, user) then
180 local user = authen(luci.sys.user.checkpasswd, def)
181 if not user or not luci.util.contains(accs, user) then
184 local sid = luci.sys.uniqueid(16)
185 luci.http.header("Set-Cookie", "sysauth=" .. sid.."; path=/")
186 luci.sauth.write(sid, user)
189 luci.http.status(403, "Forbidden")
195 if track.setgroup then
196 luci.sys.process.setgroup(track.setgroup)
199 if track.setuser then
200 luci.sys.process.setuser(track.setuser)
203 if c and type(c.target) == "function" then
204 context.dispatched = c
205 stat, mod = luci.util.copcall(require, c.module)
207 luci.util.updfenv(c.target, mod)
210 stat, err = luci.util.copcall(c.target, unpack(args))
219 --- Generate the dispatching index using the best possible strategy.
220 function createindex()
221 local path = luci.util.libpath() .. "/controller/"
224 if luci.util.copcall(require, "luci.fastindex") then
225 createindex_fastindex(path, suff)
227 createindex_plain(path, suff)
231 --- Generate the dispatching index using the fastindex C-indexer.
232 -- @param path Controller base directory
233 -- @param suffix Controller file suffix
234 function createindex_fastindex(path, suffix)
238 fi = luci.fastindex.new("index")
239 fi.add(path .. "*" .. suffix)
240 fi.add(path .. "*/*" .. suffix)
244 for k, v in pairs(fi.indexes) do
249 --- Generate the dispatching index using the native file-cache based strategy.
250 -- @param path Controller base directory
251 -- @param suffix Controller file suffix
252 function createindex_plain(path, suffix)
257 local controllers = luci.util.combine(
258 luci.fs.glob(path .. "*" .. suffix) or {},
259 luci.fs.glob(path .. "*/*" .. suffix) or {}
263 cache = luci.fs.mtime(indexcache)
266 luci.fs.mkdir(indexcache)
267 luci.fs.chmod(indexcache, "a=,u=rwx")
268 cache = luci.fs.mtime(indexcache)
272 for i,c in ipairs(controllers) do
273 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
279 cachefile = indexcache .. "/" .. module
280 stime = luci.fs.mtime(c) or 0
281 ctime = luci.fs.mtime(cachefile) or 0
284 if not cache or stime > ctime then
285 stat, mod = luci.util.copcall(require, module)
287 if stat and mod and type(mod.index) == "function" then
288 index[module] = mod.index
291 luci.fs.writefile(cachefile, luci.util.get_bytecode(mod.index))
295 index[module] = loadfile(cachefile)
300 --- Create the dispatching tree from the index.
301 -- Build the index before if it does not exist yet.
302 function createtree()
307 context.tree = {nodes={}}
310 -- Load default translation
311 luci.i18n.loadc("default")
313 local scope = luci.util.clone(_G)
314 for k,v in pairs(luci.dispatcher) do
315 if type(v) == "function" then
320 for k, v in pairs(index) do
324 local stat, err = luci.util.copcall(v)
326 error500("createtree failed: " .. k .. ": " .. err)
333 --- Clone a node of the dispatching tree to another position.
334 -- @param path Virtual path destination
335 -- @param clone Virtual path source
336 -- @param title Destination node title (optional)
337 -- @param order Destination node order value (optional)
338 -- @return Dispatching tree node
339 function assign(path, clone, title, order)
340 local obj = node(unpack(path))
347 local c = context.tree
348 for k, v in ipairs(clone) do
349 if not c.nodes[v] then
350 c.nodes[v] = {nodes={}}
356 setmetatable(obj, {__index = c})
361 --- Create a new dispatching node and define common parameters.
362 -- @param path Virtual path
363 -- @param target Target function to call when dispatched.
364 -- @param title Destination node title
365 -- @param order Destination node order value (optional)
366 -- @return Dispatching tree node
367 function entry(path, target, title, order)
368 local c = node(unpack(path))
373 c.module = getfenv(2)._NAME
378 --- Fetch or create a new dispatching node.
379 -- @param ... Virtual path
380 -- @return Dispatching tree node
382 local c = context.tree
385 for k,v in ipairs(arg) do
386 if not c.nodes[v] then
387 c.nodes[v] = {nodes={}, auto=true}
393 c.module = getfenv(2)._NAME
402 --- Create a redirect to another dispatching node.
403 -- @param ... Virtual path destination
411 --- Rewrite the first x path values of the request.
412 -- @param n Number of path values to replace
413 -- @param ... Virtual path to replace removed path values with
414 function rewrite(n, ...)
418 table.remove(context.path, 1)
421 for i,r in ipairs(req) do
422 table.insert(context.path, i, r)
429 --- Create a function-call dispatching target.
430 -- @param name Target function of local controller
431 -- @param ... Additional parameters passed to the function
432 function call(name, ...)
434 return function() return getfenv()[name](unpack(argv)) end
437 --- Create a template render dispatching target.
438 -- @param name Template to be rendered
439 function template(name)
440 require("luci.template")
441 return function() luci.template.render(name) end
444 --- Create a CBI model dispatching target.
445 -- @param model CBI model tpo be rendered
448 require("luci.template")
451 local stat, maps = luci.util.copcall(luci.cbi.load, model, ...)
457 for i, res in ipairs(maps) do
458 local stat, err = luci.util.copcall(res.parse, res)
465 luci.template.render("cbi/header")
466 for i, res in ipairs(maps) do
469 luci.template.render("cbi/footer")
473 --- Create a CBI form model dispatching target.
474 -- @param model CBI form model tpo be rendered
477 require("luci.template")
480 local stat, maps = luci.util.copcall(luci.cbi.load, model, ...)
486 for i, res in ipairs(maps) do
487 local stat, err = luci.util.copcall(res.parse, res)
494 luci.template.render("header")
495 for i, res in ipairs(maps) do
498 luci.template.render("footer")