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