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")
90 -- Sets privilege for given category
91 function assign_privileges(category)
92 local cp = ffluci.config.category_privileges
93 if cp and cp[category] then
94 local u, g = cp[category]:match("([^:]+):([^:]+)")
95 ffluci.sys.process.setuser(u)
96 ffluci.sys.process.setgroup(g)
101 -- Builds a URL from a triple of category, module and action
102 function build_url(category, module, action)
103 category = category or "public"
104 module = module or "index"
105 action = action or "index"
107 local pattern = ffluci.http.get_script_name() .. "/%s/%s/%s"
108 return pattern:format(category, module, action)
112 -- Dispatches the "request"
113 function dispatch(req)
115 local m = "ffluci.controller." .. request.category .. "." .. request.module
116 local stat, module = pcall(require, m)
120 module.request = request
121 module.dispatcher = module.dispatcher or dynamic
122 setfenv(module.dispatcher, module)
123 return module.dispatcher(request)
127 -- Sends a 404 error code and renders the "error404" template if available
128 function error404(message)
129 ffluci.http.set_status(404, "Not Found")
130 message = message or "Not Found"
132 if not pcall(ffluci.template.render, "error404") then
133 ffluci.http.set_content_type("text/plain")
139 -- Sends a 500 error code and renders the "error500" template if available
140 function error500(message)
141 ffluci.http.set_status(500, "Internal Server Error")
143 if not pcall(ffluci.template.render, "error500", {message=message}) then
144 ffluci.http.set_content_type("text/plain")
151 -- Dispatches a request depending on the PATH_INFO variable
152 function httpdispatch()
153 local pathinfo = os.getenv("PATH_INFO") or ""
154 local parts = pathinfo:gmatch("/[%w-]+")
156 local sanitize = function(s, default)
157 return s and s:sub(2) or default
160 local cat = sanitize(parts(), "public")
161 local mod = sanitize(parts(), "index")
162 local act = sanitize(parts(), "index")
164 assign_privileges(cat)
165 dispatch({category=cat, module=mod, action=act})
172 -- The Action Dispatcher searches the module for any function called
173 -- action_"request.action" and calls it
175 local disp = require("ffluci.dispatcher")
176 if not disp._action(...) then
181 -- The CBI dispatcher directly parses and renders the CBI map which is
182 -- placed in ffluci/modles/cbi/"request.module"/"request.action"
184 local disp = require("ffluci.dispatcher")
185 if not disp._cbi(...) then
190 -- The dynamic dispatcher chains the action, submodule, simpleview and CBI dispatcher
191 -- in this particular order. It is the default dispatcher.
192 function dynamic(...)
193 local disp = require("ffluci.dispatcher")
194 if not disp._action(...)
195 and not disp._submodule(...)
196 and not disp._simpleview(...)
197 and not disp._cbi(...) then
202 -- The Simple View Dispatcher directly renders the template
203 -- which is placed in ffluci/views/"request.module"/"request.action"
204 function simpleview(...)
205 local disp = require("ffluci.dispatcher")
206 if not disp._simpleview(...) then
212 -- The submodule dispatcher tries to load a submodule of the controller
213 -- and calls its "action"-function
214 function submodule(...)
215 local disp = require("ffluci.dispatcher")
216 if not disp._submodule(...) then
222 -- Internal Dispatcher Functions --
224 function _action(request)
225 local action = getfenv(2)["action_" .. request.action:gsub("-", "_")]
226 local i18n = require("ffluci.i18n")
229 i18n.loadc(request.category .. "_" .. request.module)
230 i18n.loadc(request.category .. "_" .. request.module .. "_" .. request.action)
239 function _cbi(request)
240 local disp = require("ffluci.dispatcher")
241 local tmpl = require("ffluci.template")
242 local cbi = require("ffluci.cbi")
243 local i18n = require("ffluci.i18n")
245 local path = request.category.."_"..request.module.."/"..request.action
247 local stat, map = pcall(cbi.load, path)
249 local stat, err = pcall(map.parse, map)
254 i18n.loadc(request.category .. "_" .. request.module)
255 i18n.loadc(request.category .. "_" .. request.module .. "_" .. request.action)
256 tmpl.render("cbi/header")
258 tmpl.render("cbi/footer")
269 function _simpleview(request)
270 local i18n = require("ffluci.i18n")
271 local tmpl = require("ffluci.template")
273 local path = request.category.."_"..request.module.."/"..request.action
275 local stat, t = pcall(tmpl.Template, path)
277 i18n.loadc(request.category .. "_" .. request.module)
278 i18n.loadc(request.category .. "_" .. request.module .. "_" .. request.action)
287 function _submodule(request)
288 local i18n = require("ffluci.i18n")
289 local m = "ffluci.controller." .. request.category .. "." ..
290 request.module .. "." .. request.action
291 local stat, module = pcall(require, m)
293 if stat and module.action then
294 i18n.loadc(request.category .. "_" .. request.module)
295 i18n.loadc(request.category .. "_" .. request.module .. "_" .. request.action)
296 return pcall(module.action)