rev |
line source |
jbe@203
|
1 #!/usr/bin/env moonbridge
|
jbe/bsw@0
|
2
|
jbe@294
|
3 --[[--
|
jbe@294
|
4 WEBMCP_VERSION
|
jbe@294
|
5
|
jbe@294
|
6 A string containing the WebMCP version, e.g. "2.0.0"
|
jbe@294
|
7 --]]--
|
jbe@278
|
8 WEBMCP_VERSION = "2.0.0"
|
jbe@294
|
9 --//--
|
jbe@64
|
10
|
jbe@317
|
11 --[[--
|
jbe@317
|
12 _G
|
jbe@317
|
13
|
jbe@317
|
14 A reference to the global namespace. To avoid accidental programming errors, global variables cannot be set directly, but they must be set through the _G reference, e.g. use _G.foo = true to set the variable "foo" to a value of true.
|
jbe@317
|
15
|
jbe@317
|
16 Note that the global namespace may or may not be shared between requests (Moonbridge creates multiple forks of the Lua machine). To set variables that are to be cleared after the request has been finished, an application may use the "app" table, e.g. app.foo = true to set the variable app.foo to a value of true, which will be cleared automatically when the request has ended.
|
jbe@317
|
17
|
jbe@317
|
18 --]]--
|
jbe@231
|
19 local _G = _G
|
jbe@237
|
20 local allowed_globals = {}
|
jbe@324
|
21 local protected_environment = setmetatable(
|
jbe@324
|
22 {}, -- proxy environment used all chunks loaded through loadcached(...)
|
jbe@324
|
23 {
|
jbe@324
|
24 __index = _G,
|
jbe@324
|
25 __newindex = function(self, key, value)
|
jbe@324
|
26 if allowed_globals[key] then
|
jbe@324
|
27 _G[key] = value
|
jbe@317
|
28 else
|
jbe@324
|
29 if type(key) == "string" and string.match(key, "^[A-Za-z_][A-Za-z_0-9]*$") then
|
jbe@324
|
30 error('Attempt to set global variable "' .. key .. '" (hint: use _G.' .. key .. '=<value> to override protection mechanism)', 2)
|
jbe@324
|
31 else
|
jbe@324
|
32 error('Attempt to set global variable', 2)
|
jbe@324
|
33 end
|
jbe@317
|
34 end
|
jbe@317
|
35 end
|
jbe@324
|
36 }
|
jbe@324
|
37 )
|
jbe@318
|
38 --//--
|
jbe@231
|
39
|
jbe@294
|
40 --[[--
|
jbe@309
|
41 lua_func = -- compiled Lua function
|
jbe@309
|
42 loadcached(
|
jbe@309
|
43 filename -- path to a Lua source or byte-code file
|
jbe@309
|
44 )
|
jbe@309
|
45
|
jbe@309
|
46 Loads, compiles and caches a Lua chunk. If the file does not exist, nil and an error string are returned. Otherwise the file is loaded, compiled, and cached. The cached value (i.e. the compiled function) is returned. An error is raised if the compilation was not successful.
|
jbe@309
|
47
|
jbe@309
|
48 --]]--
|
jbe@309
|
49 do
|
jbe@309
|
50 local cache = {}
|
jbe@309
|
51 function loadcached(filename)
|
jbe@311
|
52 local cached_func = cache[filename]
|
jbe@311
|
53 if cached_func then
|
jbe@311
|
54 return cached_func
|
jbe@311
|
55 end
|
jbe@309
|
56 local file, read_error = io.open(filename, "r")
|
jbe@309
|
57 if file then
|
jbe@309
|
58 local filedata = assert(file:read("*a"))
|
jbe@309
|
59 assert(file:close())
|
jbe@324
|
60 local func, compile_error = load(filedata, "=" .. filename, nil, protected_environment)
|
jbe@309
|
61 if func then
|
jbe@309
|
62 cache[filename] = func
|
jbe@309
|
63 return func
|
jbe@309
|
64 end
|
jbe@311
|
65 error(compile_error, 0)
|
jbe@309
|
66 else
|
jbe@309
|
67 return nil, read_error
|
jbe@309
|
68 end
|
jbe@309
|
69 end
|
jbe@309
|
70 end
|
jbe@309
|
71 --//--
|
jbe@309
|
72
|
jbe@309
|
73 --[[--
|
jbe@294
|
74 WEBMCP_MODE
|
jbe@294
|
75
|
jbe@294
|
76 A constant set to "listen" in case of a network request, or set to "interactive" in case of interactive mode.
|
jbe@294
|
77 --]]--
|
jbe@323
|
78 if _MOONBRIDGE_VERSION then
|
jbe@203
|
79 WEBMCP_MODE = "listen"
|
jbe@203
|
80 else
|
jbe@203
|
81 WEBMCP_MODE = "interactive"
|
jbe@64
|
82 end
|
jbe@294
|
83 --//--
|
jbe@203
|
84
|
jbe@294
|
85 --[[--
|
jbe@294
|
86 WEBMCP_CONFIG_NAMES
|
jbe@294
|
87
|
jbe@294
|
88 A list of the selected configuration names.
|
jbe@294
|
89 --]]--
|
jbe@294
|
90 -- configuration names are provided as 4th, 5th, etc. command line argument
|
jbe@206
|
91 WEBMCP_CONFIG_NAMES = {select(4, ...)}
|
jbe@294
|
92 --//--
|
jbe@294
|
93
|
jbe@294
|
94 --[[--
|
jbe@294
|
95 WEBMCP_FRAMEWORK_PATH
|
jbe@294
|
96
|
jbe@294
|
97 Directory of the WebMCP framework (always includes a trailing slash).
|
jbe@294
|
98 --]]--
|
jbe@294
|
99 -- set in mcp.lua
|
jbe@294
|
100 --//--
|
jbe@294
|
101
|
jbe@294
|
102 --[[--
|
jbe@294
|
103 WEBMCP_BASE_PATH
|
jbe@294
|
104
|
jbe@294
|
105 Base directory of the application (always includes a trailing slash).
|
jbe@294
|
106 --]]--
|
jbe@294
|
107 -- set in mcp.lua
|
jbe@294
|
108 --//--
|
jbe@294
|
109
|
jbe@294
|
110 --[[--
|
jbe@294
|
111 WEBMCP_APP_NAME
|
jbe@294
|
112
|
jbe@294
|
113 Application name (usually "main"). May be nil in case of interactive mode.
|
jbe@294
|
114 --]]--
|
jbe@294
|
115 -- set in mcp.lua
|
jbe@294
|
116 --//--
|
jbe@203
|
117
|
jbe@203
|
118 -- determine framework and bath path from command line arguments
|
jbe@203
|
119 -- or print usage synopsis (if applicable)
|
jbe@68
|
120 do
|
jbe@206
|
121 local arg1, arg2, arg3 = ...
|
jbe@203
|
122 local helpout
|
jbe@203
|
123 if
|
jbe@203
|
124 arg1 == "-h" or arg1 == "--help" or
|
jbe@206
|
125 arg2 == "-h" or arg2 == "--help" -- if first arg is provided by wrapper
|
jbe@203
|
126 then
|
jbe@203
|
127 helpout = io.stdout
|
jbe@316
|
128 elseif #WEBMCP_CONFIG_NAMES < 1 then
|
jbe@203
|
129 helpout = io.stderr
|
jbe@203
|
130 end
|
jbe@203
|
131 if helpout then
|
jbe@316
|
132 helpout:write("Usage: moonbridge [moonbr opts] -- <framework path>/bin/mcp.lua <framework path> <app base path> <app name> <config name> [<config name> ...]\n")
|
jbe@316
|
133 helpout:write(" or: lua -i [Lua opts] -- <framework path>/bin/mcp.lua <framework path> <app base path> <app name> <config name> [<config name> ...]\n")
|
jbe@203
|
134 if helpout == io.stderr then
|
jbe@203
|
135 return 1
|
jbe@68
|
136 else
|
jbe@203
|
137 return 0
|
jbe@68
|
138 end
|
jbe@68
|
139 end
|
jbe@203
|
140 local function append_trailing_slash(str)
|
jbe@217
|
141 return string.gsub(str, "([^/])$", function(last) return last .. "/" end)
|
jbe@203
|
142 end
|
jbe@203
|
143 WEBMCP_FRAMEWORK_PATH = append_trailing_slash(arg1)
|
jbe@203
|
144 WEBMCP_BASE_PATH = append_trailing_slash(arg2)
|
jbe@316
|
145 WEBMCP_APP_NAME = arg3
|
jbe@68
|
146 end
|
jbe@1
|
147
|
jbe@313
|
148 -- check if framework path is correct
|
jbe@313
|
149 do
|
jbe@313
|
150 local file, errmsg = io.open(WEBMCP_FRAMEWORK_PATH .. "webmcp_version", "r")
|
jbe@313
|
151 if not file then
|
jbe@313
|
152 error('Could not find "webmcp_version" file: ' .. errmsg, 0)
|
jbe@313
|
153 end
|
jbe@313
|
154 local version = assert(file:read())
|
jbe@313
|
155 assert(file:close())
|
jbe@313
|
156 if version ~= WEBMCP_VERSION then
|
jbe@313
|
157 error('Version mismatch in file "' .. WEBMCP_FRAMEWORK_PATH .. 'webmcp_version"')
|
jbe@313
|
158 end
|
jbe@313
|
159 end
|
jbe@313
|
160
|
jbe@203
|
161 -- setup search paths for libraries
|
jbe/bsw@0
|
162 do
|
jbe@217
|
163 if string.match(package.path, "^[^;]") then
|
jbe@217
|
164 package.path = ";" .. package.path
|
jbe@217
|
165 end
|
jbe@217
|
166 package.path = WEBMCP_FRAMEWORK_PATH .. "lib/?.lua" .. package.path
|
jbe/bsw@0
|
167 -- find out which file name extension shared libraries have
|
jbe/bsw@0
|
168 local slib_exts = {}
|
jbe/bsw@0
|
169 for ext in string.gmatch(package.cpath, "%?%.([A-Za-z0-9_-]+)") do
|
jbe@217
|
170 if not slib_exts[ext] then
|
jbe@217
|
171 slib_exts[#slib_exts+1] = ext
|
jbe@217
|
172 slib_exts[ext] = true
|
jbe@217
|
173 end
|
jbe/bsw@0
|
174 end
|
jbe/bsw@0
|
175 local paths = {}
|
jbe@217
|
176 for i, ext in ipairs(slib_exts) do
|
jbe@203
|
177 paths[#paths+1] = WEBMCP_FRAMEWORK_PATH .. "accelerator/?." .. ext
|
jbe/bsw@0
|
178 end
|
jbe@217
|
179 for i, ext in ipairs(slib_exts) do
|
jbe@203
|
180 paths[#paths+1] = WEBMCP_FRAMEWORK_PATH .. "lib/?." .. ext
|
jbe/bsw@0
|
181 end
|
jbe/bsw@0
|
182 paths[#paths+1] = package.cpath
|
jbe/bsw@0
|
183 package.cpath = table.concat(paths, ";")
|
jbe/bsw@0
|
184 end
|
jbe/bsw@0
|
185
|
jbe@203
|
186 -- autoloader system for WebMCP environment "$WEBMCP_FRAMEWORK_PATH/env/",
|
jbe@203
|
187 -- application environment extensions "$WEBMCP_BASE_PATH/env/"
|
jbe@203
|
188 -- and models "$WEBMCP_BASE_PATH/model/"
|
jbe/bsw@0
|
189 do
|
jbe/bsw@0
|
190 local weakkey_mt = { __mode = "k" }
|
jbe/bsw@0
|
191 local autoloader_category = setmetatable({}, weakkey_mt)
|
jbe/bsw@0
|
192 local autoloader_path = setmetatable({}, weakkey_mt)
|
jbe/bsw@0
|
193 local autoloader_mt = {}
|
jbe@219
|
194 local function install_autoloader(self, category, path_fragment)
|
jbe/bsw@0
|
195 autoloader_category[self] = category
|
jbe@219
|
196 autoloader_path[self] = path_fragment
|
jbe/bsw@0
|
197 setmetatable(self, autoloader_mt)
|
jbe/bsw@0
|
198 end
|
jbe/bsw@0
|
199 local function try_exec(filename)
|
jbe@309
|
200 local func = loadcached(filename)
|
jbe@309
|
201 if func then
|
jbe@309
|
202 func()
|
jbe@309
|
203 return true
|
jbe/bsw@0
|
204 else
|
jbe/bsw@0
|
205 return false
|
jbe/bsw@0
|
206 end
|
jbe/bsw@0
|
207 end
|
jbe/bsw@0
|
208 function autoloader_mt.__index(self, key)
|
jbe/bsw@0
|
209 local category, base_path, merge_base_path, file_key
|
jbe/bsw@0
|
210 local merge = false
|
jbe/bsw@0
|
211 if
|
jbe/bsw@0
|
212 string.find(key, "^[a-z_][A-Za-z0-9_]*$") and
|
jbe/bsw@0
|
213 not string.find(key, "^__")
|
jbe/bsw@0
|
214 then
|
jbe/bsw@0
|
215 category = "env"
|
jbe@203
|
216 base_path = WEBMCP_FRAMEWORK_PATH .. "env/"
|
jbe/bsw@0
|
217 merge = true
|
jbe@203
|
218 merge_base_path = WEBMCP_BASE_PATH .. "env/"
|
jbe/bsw@0
|
219 file_key = key
|
jbe/bsw@0
|
220 elseif string.find(key, "^[A-Z][A-Za-z0-9]*$") then
|
jbe/bsw@0
|
221 category = "model"
|
jbe@203
|
222 base_path = WEBMCP_BASE_PATH .. "model/"
|
jbe/bsw@0
|
223 local first = true
|
jbe/bsw@0
|
224 file_key = string.gsub(key, "[A-Z]",
|
jbe/bsw@0
|
225 function(c)
|
jbe/bsw@0
|
226 if first then
|
jbe/bsw@0
|
227 first = false
|
jbe/bsw@0
|
228 return string.lower(c)
|
jbe/bsw@0
|
229 else
|
jbe/bsw@0
|
230 return "_" .. string.lower(c)
|
jbe/bsw@0
|
231 end
|
jbe/bsw@0
|
232 end
|
jbe/bsw@0
|
233 )
|
jbe/bsw@0
|
234 else
|
jbe/bsw@0
|
235 return
|
jbe/bsw@0
|
236 end
|
jbe/bsw@0
|
237 local required_category = autoloader_category[self]
|
jbe/bsw@0
|
238 if required_category and required_category ~= category then return end
|
jbe@219
|
239 local path_fragment = autoloader_path[self]
|
jbe@219
|
240 local path = base_path .. path_fragment .. file_key
|
jbe@219
|
241 local merge_path
|
jbe/bsw@0
|
242 if merge then
|
jbe@219
|
243 merge_path = merge_base_path .. path_fragment .. file_key
|
jbe/bsw@0
|
244 end
|
jbe/bsw@0
|
245 local function try_dir(dirname)
|
jbe/bsw@0
|
246 local dir = io.open(dirname)
|
jbe/bsw@0
|
247 if dir then
|
jbe/bsw@0
|
248 io.close(dir)
|
jbe/bsw@0
|
249 local obj = {}
|
jbe@219
|
250 install_autoloader(obj, category, path_fragment .. file_key .. "/")
|
jbe/bsw@0
|
251 rawset(self, key, obj)
|
jbe@219
|
252 try_exec(path .. "/__init.lua")
|
jbe@219
|
253 if merge then try_exec(merge_path .. "/__init.lua") end
|
jbe/bsw@0
|
254 return true
|
jbe/bsw@0
|
255 else
|
jbe/bsw@0
|
256 return false
|
jbe/bsw@0
|
257 end
|
jbe/bsw@0
|
258 end
|
jbe@238
|
259 if self == _G then
|
jbe@237
|
260 allowed_globals[key] = true
|
jbe@233
|
261 end
|
jbe@219
|
262 if merge and try_exec(merge_path .. ".lua") then
|
jbe@219
|
263 elseif merge and try_dir(merge_path .. "/") then
|
jbe@219
|
264 elseif try_exec(path .. ".lua") then
|
jbe@219
|
265 elseif try_dir(path .. "/") then
|
jbe/bsw@0
|
266 else end
|
jbe@238
|
267 if self == _G then
|
jbe@237
|
268 allowed_globals[key] = nil
|
jbe@233
|
269 end
|
jbe@237
|
270 return rawget(self, key)
|
jbe/bsw@0
|
271 end
|
jbe@219
|
272 install_autoloader(_G, nil, "")
|
jbe@324
|
273 try_exec(WEBMCP_FRAMEWORK_PATH .. "env/__init.lua")
|
jbe@324
|
274 try_exec(WEBMCP_BASE_PATH .. "env/__init.lua")
|
jbe@214
|
275 end
|
jbe@214
|
276
|
jbe@286
|
277 -- define post-fork initialization function (including loading of "multirand" library)
|
jbe@286
|
278 local function postfork_init()
|
jbe@324
|
279 multirand = require "multirand"
|
jbe@286
|
280 execute.postfork_initializers()
|
jbe@286
|
281 end
|
jbe@286
|
282
|
jbe@317
|
283 -- prepare for interactive or listen mode
|
jbe@203
|
284 if WEBMCP_MODE == "interactive" then
|
jbe@317
|
285 function listen() -- overwrite Moonbridge's listen function
|
jbe@317
|
286 -- ignore listen function calls for interactive mode
|
jbe@317
|
287 end
|
jbe@317
|
288 trace.disable() -- avoids memory leakage when scripts are running endlessly
|
jbe@317
|
289 else
|
jbe@317
|
290 local moonbridge_listen = listen
|
jbe@207
|
291 local http = require("moonbridge_http")
|
jbe@317
|
292 function listen(args) -- overwrite Moonbridge's listen function
|
jbe@322
|
293 assert(args, "No argument passed to listen function")
|
jbe@322
|
294 local min_requests_per_fork = args.min_requests_per_fork or 50
|
jbe@322
|
295 local max_requests_per_fork = args.max_requests_per_fork or 100
|
jbe@316
|
296 local interval_handlers = {}
|
jbe@317
|
297 for j, listener in ipairs(args) do
|
jbe@317
|
298 if listener.proto == "interval" then
|
jbe@317
|
299 local name = listener.name or "Unnamed interval #" .. #interval_handlers+1
|
jbe@317
|
300 interval_handlers[name] = listener.handler
|
jbe@317
|
301 listener.name = name
|
jbe@316
|
302 end
|
jbe@316
|
303 end
|
jbe@264
|
304 local request_count = 0
|
jbe@266
|
305 local function inner_handler(http_request)
|
jbe@317
|
306 request.initialize()
|
jbe@288
|
307 request.handler(http_request, request_count >= max_requests_per_fork)
|
jbe@264
|
308 end
|
jbe@326
|
309 local outer_handler = http.generate_handler(inner_handler, args.http_options)
|
jbe@317
|
310 args.prepare = postfork_init
|
jbe@317
|
311 args.connect = function(socket)
|
jbe@316
|
312 request_count = request_count + 1
|
jbe@316
|
313 if socket.interval then
|
jbe@317
|
314 request.initialize()
|
jbe@316
|
315 interval_handlers[socket.interval]()
|
jbe@316
|
316 else
|
jbe@316
|
317 outer_handler(socket)
|
jbe@316
|
318 end
|
jbe@288
|
319 return request_count < min_requests_per_fork
|
jbe@264
|
320 end
|
jbe@317
|
321 args.finish = execute.finalizers
|
jbe@317
|
322 moonbridge_listen(args)
|
jbe@204
|
323 end
|
jbe@204
|
324 end
|
jbe@317
|
325
|
jbe@317
|
326 -- execute configurations and pre-fork initializers
|
jbe@317
|
327 for i, config_name in ipairs(WEBMCP_CONFIG_NAMES) do
|
jbe@317
|
328 execute.config(config_name)
|
jbe@317
|
329 end
|
jbe@317
|
330 execute.prefork_initializers()
|
jbe@317
|
331
|
jbe@324
|
332 -- perform post-fork initializations (once) in case of interactive mode
|
jbe@317
|
333 if WEBMCP_MODE == "interactive" then
|
jbe@317
|
334 postfork_init()
|
jbe@317
|
335 end
|