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")
91 -- Sets privilege for given category
92 function assign_privileges(category)
93 local cp = ffluci.config.category_privileges
94 if cp and cp[category] then
95 local u, g = cp[category]:match("([^:]+):([^:]+)")
96 ffluci.sys.process.setuser(u)
97 ffluci.sys.process.setgroup(g)
101 -- Dispatches the "request"
102 function dispatch(req)
104 local m = "ffluci.controller." .. request.category .. "." .. request.module
105 local stat, module = pcall(require, m)
109 module.request = request
110 module.dispatcher = module.dispatcher or dynamic
111 setfenv(module.dispatcher, module)
112 return module.dispatcher(request)
116 -- Sends a 404 error code and renders the "error404" template if available
117 function error404(message)
118 message = message or "Not Found"
120 if not pcall(ffluci.template.render, "error404") then
121 ffluci.http.textheader()
127 -- Sends a 500 error code and renders the "error500" template if available
128 function error500(message)
129 ffluci.http.status(500, "Internal Server Error")
131 if not pcall(ffluci.template.render, "error500", {message=message}) then
132 ffluci.http.textheader()
139 -- Dispatches a request depending on the PATH_INFO variable
140 function httpdispatch()
141 local pathinfo = os.getenv("PATH_INFO") or ""
142 local parts = pathinfo:gmatch("/[%w-]+")
144 local sanitize = function(s, default)
145 return s and s:sub(2) or default
148 local cat = sanitize(parts(), "public")
149 local mod = sanitize(parts(), "index")
150 local act = sanitize(parts(), "index")
152 assign_privileges(cat)
153 dispatch({category=cat, module=mod, action=act})
160 -- The Action Dispatcher searches the module for any function called
161 -- action_"request.action" and calls it
162 function action(request)
163 local i18n = require("ffluci.i18n")
164 local disp = require("ffluci.dispatcher")
166 i18n.loadc(request.module)
167 local action = getfenv()["action_" .. request.action:gsub("-", "_")]
175 -- The CBI dispatcher directly parses and renders the CBI map which is
176 -- placed in ffluci/modles/cbi/"request.module"/"request.action"
177 function cbi(request)
178 local i18n = require("ffluci.i18n")
179 local disp = require("ffluci.dispatcher")
180 local tmpl = require("ffluci.template")
181 local cbi = require("ffluci.cbi")
183 local path = request.category.."_"..request.module.."/"..request.action
185 i18n.loadc(request.module)
187 local stat, map = pcall(cbi.load, path)
189 local stat, err = pcall(map.parse, map)
194 tmpl.render("cbi/header")
196 tmpl.render("cbi/footer")
204 -- The dynamic dispatchers combines the action, simpleview and cbi dispatchers
205 -- in one dispatcher. It tries to lookup the request in this order.
206 function dynamic(request)
207 local i18n = require("ffluci.i18n")
208 local disp = require("ffluci.dispatcher")
209 local tmpl = require("ffluci.template")
210 local cbi = require("ffluci.cbi")
212 i18n.loadc(request.module)
214 local action = getfenv()["action_" .. request.action:gsub("-", "_")]
220 local path = request.category.."_"..request.module.."/"..request.action
221 if pcall(tmpl.render, path) then
225 local stat, map = pcall(cbi.load, path)
227 local stat, err = pcall(map.parse, map)
232 tmpl.render("cbi/header")
234 tmpl.render("cbi/footer")
244 -- The Simple View Dispatcher directly renders the template
245 -- which is placed in ffluci/views/"request.module"/"request.action"
246 function simpleview(request)
247 local i18n = require("ffluci.i18n")
248 local tmpl = require("ffluci.template")
249 local disp = require("ffluci.dispatcher")
251 local path = request.category.."_"..request.module.."/"..request.action
253 i18n.loadc(request.module)
254 if not pcall(tmpl.render, path) then