5 The request dispatcher and module dispatcher generators
8 The dispatching process:
9 For a detailed explanation of the dispatching process we assume:
10 You have installed the FFLuCI CGI-Dispatcher in /cgi-bin/ffluci
12 To enforce a higher level of security only the CGI-Dispatcher
13 resides inside the web server's document root, everything else
14 stays inside an external directory, we assume this is /lua/ffluci
17 All controllers and action are reachable as sub-objects of /cgi-bin/ffluci
18 as if they were virtual folders and files
19 e.g.: /cgi-bin/ffluci/public/info/about
20 /cgi-bin/ffluci/admin/network/interfaces
23 The PATH_INFO variable holds the dispatch path and
24 will be split into three parts: /category/module/action
26 Category: This is the category in which modules are stored in
27 By default there are two categories:
28 "public" - which is the default public category
29 "admin" - which is the default protected category
31 As FFLuCI itself does not implement authentication
32 you should make sure that "admin" and other sensitive
33 categories are protected by the webserver.
35 E.g. for busybox add a line like:
36 /cgi-bin/ffluci/admin:root:$p$root
37 to /etc/httpd.conf to protect the "admin" category
40 Module: This is the controller which will handle the request further
41 It is always a submodule of ffluci.controller, so a module
42 called "helloworld" will be stored in
43 /lua/ffluci/controller/helloworld.lua
44 You are free to submodule your controllers any further.
46 Action: This is action that will be invoked after loading the module.
47 The kind of how the action will be dispatched depends on
48 the module dispatcher that is defined in the controller.
49 See the description of the default module dispatcher down
50 on this page for some examples.
53 The main dispatcher at first searches for the module by trying to
54 include ffluci.controller.category.module
55 (where "category" is the category name and "module" is the module name)
56 If this fails a 404 status code will be send to the client and FFLuCI exits
58 Then the main dispatcher calls the module dispatcher
59 ffluci.controller.category.module.dispatcher with the request object
60 as the only argument. The module dispatcher is then responsible
61 for the further dispatching process.
68 Copyright 2008 Steven Barth <steven@midlink.org>
70 Licensed under the Apache License, Version 2.0 (the "License");
71 you may not use this file except in compliance with the License.
72 You may obtain a copy of the License at
74 http://www.apache.org/licenses/LICENSE-2.0
76 Unless required by applicable law or agreed to in writing, software
77 distributed under the License is distributed on an "AS IS" BASIS,
78 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
79 See the License for the specific language governing permissions and
80 limitations under the License.
84 module("ffluci.dispatcher", package.seeall)
85 require("ffluci.http")
86 require("ffluci.template")
87 require("ffluci.config")
93 -- Sets privilege for given category
94 function assign_privileges(category)
95 local cp = ffluci.config.category_privileges
96 if cp and cp[category] then
97 local u, g = cp[category]:match("([^:]+):([^:]+)")
98 ffluci.sys.process.setuser(u)
99 ffluci.sys.process.setgroup(g)
103 -- Dispatches the "request"
104 function dispatch(req)
106 local m = "ffluci.controller." .. request.category .. "." .. request.module
107 local stat, module = pcall(require, m)
111 module.request = request
112 module.dispatcher = module.dispatcher or dynamic
113 setfenv(module.dispatcher, module)
114 return module.dispatcher(request)
118 -- Sends a 404 error code and renders the "error404" template if available
119 function error404(message)
120 message = message or "Not Found"
122 if not pcall(ffluci.template.render, "error404") then
123 ffluci.http.textheader()
129 -- Sends a 500 error code and renders the "error500" template if available
130 function error500(message)
131 ffluci.http.status(500, "Internal Server Error")
133 if not pcall(ffluci.template.render, "error500", {message=message}) then
134 ffluci.http.textheader()
141 -- Dispatches a request depending on the PATH_INFO variable
142 function httpdispatch()
143 local pathinfo = os.getenv("PATH_INFO") or ""
144 local parts = pathinfo:gmatch("/[%w-]+")
146 local sanitize = function(s, default)
147 return s and s:sub(2) or default
150 local cat = sanitize(parts(), "public")
151 local mod = sanitize(parts(), "index")
152 local act = sanitize(parts(), "index")
154 assign_privileges(cat)
155 dispatch({category=cat, module=mod, action=act})
162 -- The Action Dispatcher searches the module for any function called
163 -- action_"request.action" and calls it
165 local disp = require("ffluci.dispatcher")
166 if not disp._action(...) then
171 -- The CBI dispatcher directly parses and renders the CBI map which is
172 -- placed in ffluci/modles/cbi/"request.module"/"request.action"
174 local disp = require("ffluci.dispatcher")
175 if not disp._cbi(...) then
180 -- The dynamic dispatcher chains the action, submodule, simpleview and CBI dispatcher
181 -- in this particular order. It is the default dispatcher.
182 function dynamic(...)
183 local disp = require("ffluci.dispatcher")
184 if not disp._action(...)
185 and not disp._submodule(...)
186 and not disp._simpleview(...)
187 and not disp._cbi(...) then
192 -- The Simple View Dispatcher directly renders the template
193 -- which is placed in ffluci/views/"request.module"/"request.action"
194 function simpleview(...)
195 local disp = require("ffluci.dispatcher")
196 if not disp._simpleview(...) then
202 -- The submodule dispatcher tries to load a submodule of the controller
203 -- and calls its "action"-function
204 function submodule(...)
205 local disp = require("ffluci.dispatcher")
206 if not disp._submodule(...) then
212 -- Internal Dispatcher Functions --
214 function _action(request)
215 local action = getfenv()["action_" .. request.action:gsub("-", "_")]
216 local i18n = require("ffluci.i18n")
219 i18n.loadc(request.category .. "_" .. request.module)
220 i18n.loadc(request.category .. "_" .. request.module .. "_" .. request.action)
229 function _cbi(request)
230 local disp = require("ffluci.dispatcher")
231 local tmpl = require("ffluci.template")
232 local cbi = require("ffluci.cbi")
233 local i18n = require("ffluci.i18n")
235 local path = request.category.."_"..request.module.."/"..request.action
237 local stat, map = pcall(cbi.load, path)
239 local stat, err = pcall(map.parse, map)
244 i18n.loadc(request.category .. "_" .. request.module)
245 i18n.loadc(request.category .. "_" .. request.module .. "_" .. request.action)
246 tmpl.render("cbi/header")
248 tmpl.render("cbi/footer")
259 function _simpleview(request)
260 local i18n = require("ffluci.i18n")
261 local tmpl = require("ffluci.template")
263 local path = request.category.."_"..request.module.."/"..request.action
265 local stat, t = pcall(tmpl.Template, path)
267 i18n.loadc(request.category .. "_" .. request.module)
268 i18n.loadc(request.category .. "_" .. request.module .. "_" .. request.action)
277 function _submodule(request)
278 local i18n = require("ffluci.i18n")
279 local m = "ffluci.controller." .. request.category .. "." ..
280 request.module .. "." .. request.action
281 local stat, module = pcall(require, m)
283 if stat and module.action then
284 i18n.loadc(request.category .. "_" .. request.module)
285 i18n.loadc(request.category .. "_" .. request.module .. "_" .. request.action)
286 return pcall(module.action)