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
130 for i, s in ipairs(request) do
132 if not c or c.leaf then
136 for k, v in pairs(c) do
142 require("luci.i18n").loadc(track.i18n)
145 -- Init template engine
146 local tpl = require("luci.template")
148 tpl.context.viewns = viewns
149 viewns.write = luci.http.write
150 viewns.translate = function(...) return require("luci.i18n").translate(...) end
151 viewns.controller = luci.http.getenv("SCRIPT_NAME")
152 viewns.media = luci.config.main.mediaurlbase
153 viewns.resource = luci.config.main.resourcebase
154 viewns.REQUEST_URI = luci.http.getenv("SCRIPT_NAME") .. (luci.http.getenv("PATH_INFO") or "")
156 if track.dependent then
157 local stat, err = pcall(assert, not track.auto)
164 if track.sysauth then
165 require("luci.sauth")
166 local def = (type(track.sysauth) == "string") and track.sysauth
167 local accs = def and {track.sysauth} or track.sysauth
168 local user = luci.sauth.read(luci.http.getcookie("sysauth"))
171 if not luci.util.contains(accs, user) then
172 if not sysauth(def) then
178 if track.setgroup then
179 luci.sys.process.setgroup(track.setgroup)
182 if track.setuser then
183 luci.sys.process.setuser(track.setuser)
186 if c and type(c.target) == "function" then
187 context.dispatched = c
188 stat, mod = luci.util.copcall(require, c.module)
190 luci.util.updfenv(c.target, mod)
193 stat, err = luci.util.copcall(c.target)
202 --- Generate the dispatching index using the best possible strategy.
203 function createindex()
204 local path = luci.util.libpath() .. "/controller/"
207 if luci.util.copcall(require, "luci.fastindex") then
208 createindex_fastindex(path, suff)
210 createindex_plain(path, suff)
214 --- Generate the dispatching index using the fastindex C-indexer.
215 -- @param path Controller base directory
216 -- @param suffix Controller file suffix
217 function createindex_fastindex(path, suffix)
221 fi = luci.fastindex.new("index")
222 fi.add(path .. "*" .. suffix)
223 fi.add(path .. "*/*" .. suffix)
227 for k, v in pairs(fi.indexes) do
232 --- Generate the dispatching index using the native file-cache based strategy.
233 -- @param path Controller base directory
234 -- @param suffix Controller file suffix
235 function createindex_plain(path, suffix)
240 local controllers = luci.util.combine(
241 luci.fs.glob(path .. "*" .. suffix) or {},
242 luci.fs.glob(path .. "*/*" .. suffix) or {}
246 cache = luci.fs.mtime(indexcache)
249 luci.fs.mkdir(indexcache)
250 luci.fs.chmod(indexcache, "a=,u=rwx")
251 cache = luci.fs.mtime(indexcache)
255 for i,c in ipairs(controllers) do
256 local module = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".")
262 cachefile = indexcache .. "/" .. module
263 stime = luci.fs.mtime(c) or 0
264 ctime = luci.fs.mtime(cachefile) or 0
267 if not cache or stime > ctime then
268 stat, mod = luci.util.copcall(require, module)
270 if stat and mod and type(mod.index) == "function" then
271 index[module] = mod.index
274 luci.fs.writefile(cachefile, luci.util.get_bytecode(mod.index))
278 index[module] = loadfile(cachefile)
283 --- Create the dispatching tree from the index.
284 -- Build the index before if it does not exist yet.
285 function createtree()
290 context.tree = {nodes={}}
293 -- Load default translation
294 luci.i18n.loadc("default")
296 local scope = luci.util.clone(_G)
297 for k,v in pairs(luci.dispatcher) do
298 if type(v) == "function" then
303 for k, v in pairs(index) do
307 local stat, err = luci.util.copcall(v)
309 error500("createtree failed: " .. k .. ": " .. err)
316 --- Clone a node of the dispatching tree to another position.
317 -- @param path Virtual path destination
318 -- @param clone Virtual path source
319 -- @param title Destination node title (optional)
320 -- @param order Destination node order value (optional)
321 -- @return Dispatching tree node
322 function assign(path, clone, title, order)
323 local obj = node(unpack(path))
330 local c = context.tree
331 for k, v in ipairs(clone) do
332 if not c.nodes[v] then
333 c.nodes[v] = {nodes={}}
339 setmetatable(obj, {__index = c})
344 --- Create a new dispatching node and define common parameters.
345 -- @param path Virtual path
346 -- @param target Target function to call when dispatched.
347 -- @param title Destination node title
348 -- @param order Destination node order value (optional)
349 -- @return Dispatching tree node
350 function entry(path, target, title, order)
351 local c = node(unpack(path))
356 c.module = getfenv(2)._NAME
361 --- Fetch or create a new dispatching node.
362 -- @param ... Virtual path
363 -- @return Dispatching tree node
365 local c = context.tree
368 for k,v in ipairs(arg) do
369 if not c.nodes[v] then
370 c.nodes[v] = {nodes={}, auto=true}
376 c.module = getfenv(2)._NAME
385 --- Create a redirect to another dispatching node.
386 -- @param ... Virtual path destination
394 --- Rewrite the first x path values of the request.
395 -- @param n Number of path values to replace
396 -- @param ... Virtual path to replace removed path values with
397 function rewrite(n, ...)
401 table.remove(context.path, 1)
404 for i,r in ipairs(req) do
405 table.insert(context.path, i, r)
412 --- Create a function-call dispatching target.
413 -- @param name Target function of local controller
414 -- @param ... Additional parameters passed to the function
415 function call(name, ...)
417 return function() return getfenv()[name](unpack(argv)) end
420 --- Create a template render dispatching target.
421 -- @param name Template to be rendered
422 function template(name)
423 require("luci.template")
424 return function() luci.template.render(name) end
427 --- Create a CBI model dispatching target.
428 -- @param model CBI model tpo be rendered
431 require("luci.template")
434 local stat, maps = luci.util.copcall(luci.cbi.load, model)
440 for i, res in ipairs(maps) do
441 local stat, err = luci.util.copcall(res.parse, res)
448 luci.template.render("cbi/header")
449 for i, res in ipairs(maps) do
452 luci.template.render("cbi/footer")