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