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