* Added ffluci.util.instanceof function
[project/luci.git] / src / ffluci / dispatcher.lua
1 --[[
2 FFLuCI - Dispatcher
3
4 Description:
5 The request dispatcher and module dispatcher generators
6
7
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
11         
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
15         for this explanation.
16
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
21         and so on.
22
23     The PATH_INFO variable holds the dispatch path and
24         will be split into three parts: /category/module/action
25    
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
30                                 
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.
34                                 
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
38                                 
39         
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.
45                                 
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.
51
52
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
57         
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.
62
63
64 FileId:
65 $Id$
66
67 License:
68 Copyright 2008 Steven Barth <steven@midlink.org>
69
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 
73
74         http://www.apache.org/licenses/LICENSE-2.0 
75
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.
81
82 ]]--
83
84 module("ffluci.dispatcher", package.seeall)
85 require("ffluci.http")
86 require("ffluci.template")
87
88
89 -- Dispatches the "request"
90 function dispatch(req)
91         request = req
92         local m = "ffluci.controller." .. request.category .. "." .. request.module
93         local stat, module = pcall(require, m)
94         if not stat then
95                 return error404()
96         else
97                 module.request = request
98                 setfenv(module.dispatcher, module)
99                 return module.dispatcher(request)
100         end     
101 end
102
103 -- Sends a 404 error code and renders the "error404" template if available
104 function error404(message)
105         message = message or "Not Found"
106         
107         if not pcall(ffluci.template.render, "error404") then
108                 ffluci.http.textheader()
109                 print(message)
110         end
111         return false    
112 end
113
114 -- Sends a 500 error code and renders the "error500" template if available
115 function error500(message)
116         ffluci.http.status(500, "Internal Server Error")
117         
118         if not pcall(ffluci.template.render, "error500") then
119                 ffluci.http.textheader()
120                 print(message)
121         end
122         return false    
123 end
124
125
126 -- Dispatches a request depending on the PATH_INFO variable
127 function httpdispatch()
128         local pathinfo = os.getenv("PATH_INFO") or ""
129         local parts = pathinfo:gmatch("/[%w-]+")
130         
131         local sanitize = function(s, default)
132                 return s and s:sub(2) or default
133         end
134         
135         local cat = sanitize(parts(), "public")
136         local mod = sanitize(parts(), "index")
137         local act = sanitize(parts(), "index")
138         
139         dispatch({category=cat, module=mod, action=act})
140 end
141
142 -- The Simple View Dispatcher directly renders the template
143 -- which is placed in ffluci/views/"request.module"/"request.action" 
144 function simpleview(request)
145         local i18n = require("ffluci.i18n")
146         local tmpl = require("ffluci.template")
147         local disp = require("ffluci.dispatcher")
148         
149         i18n.loadc(request.module)
150         if not pcall(tmpl.render, request.module .. "/" .. request.action) then
151                 disp.error404()
152         end
153 end
154
155 -- The Action Dispatcher searches the module for any function called
156 -- action_"request.action" and calls it
157 function action(request)
158         local i18n = require("ffluci.i18n")
159         local disp = require("ffluci.dispatcher")
160         
161         i18n.loadc(request.module)
162         local action = getfenv()["action_" .. request.action:gsub("-", "_")]
163         if action then
164                 action()
165         else
166                 disp.error404()
167         end
168 end