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@203
|
111 elseif
|
jbe@217
|
112 #WEBMCP_CONFIG_NAMES < 1 or
|
jbe@206
|
113 (WEBMCP_MODE == "interactive") ~= (arg3 == "INTERACTIVE")
|
jbe@203
|
114 then
|
jbe@203
|
115 helpout = io.stderr
|
jbe@203
|
116 end
|
jbe@203
|
117 if helpout then
|
jbe@217
|
118 helpout:write("Usage: moonbridge -- <framework path>/bin/mcp.lua <framework path> <app base path> <app name> <config name> [<config name> ...]\n")
|
jbe@217
|
119 helpout:write(" or: lua -i <framework path>/bin/mcp.lua <framework path> <app base path> INTERACTIVE <config name> [<config name> ...]\n")
|
jbe@203
|
120 if helpout == io.stderr then
|
jbe@203
|
121 return 1
|
jbe@68
|
122 else
|
jbe@203
|
123 return 0
|
jbe@68
|
124 end
|
jbe@68
|
125 end
|
jbe@203
|
126 local function append_trailing_slash(str)
|
jbe@217
|
127 return string.gsub(str, "([^/])$", function(last) return last .. "/" end)
|
jbe@203
|
128 end
|
jbe@203
|
129 WEBMCP_FRAMEWORK_PATH = append_trailing_slash(arg1)
|
jbe@203
|
130 WEBMCP_BASE_PATH = append_trailing_slash(arg2)
|
jbe@206
|
131 if WEBMCP_MODE == "listen" then
|
jbe@206
|
132 WEBMCP_APP_NAME = arg3
|
jbe@206
|
133 end
|
jbe@68
|
134 end
|
jbe@1
|
135
|
jbe@313
|
136 -- check if framework path is correct
|
jbe@313
|
137 do
|
jbe@313
|
138 local file, errmsg = io.open(WEBMCP_FRAMEWORK_PATH .. "webmcp_version", "r")
|
jbe@313
|
139 if not file then
|
jbe@313
|
140 error('Could not find "webmcp_version" file: ' .. errmsg, 0)
|
jbe@313
|
141 end
|
jbe@313
|
142 local version = assert(file:read())
|
jbe@313
|
143 assert(file:close())
|
jbe@313
|
144 if version ~= WEBMCP_VERSION then
|
jbe@313
|
145 error('Version mismatch in file "' .. WEBMCP_FRAMEWORK_PATH .. 'webmcp_version"')
|
jbe@313
|
146 end
|
jbe@313
|
147 end
|
jbe@313
|
148
|
jbe@203
|
149 -- setup search paths for libraries
|
jbe/bsw@0
|
150 do
|
jbe@217
|
151 if string.match(package.path, "^[^;]") then
|
jbe@217
|
152 package.path = ";" .. package.path
|
jbe@217
|
153 end
|
jbe@217
|
154 package.path = WEBMCP_FRAMEWORK_PATH .. "lib/?.lua" .. package.path
|
jbe/bsw@0
|
155 -- find out which file name extension shared libraries have
|
jbe/bsw@0
|
156 local slib_exts = {}
|
jbe/bsw@0
|
157 for ext in string.gmatch(package.cpath, "%?%.([A-Za-z0-9_-]+)") do
|
jbe@217
|
158 if not slib_exts[ext] then
|
jbe@217
|
159 slib_exts[#slib_exts+1] = ext
|
jbe@217
|
160 slib_exts[ext] = true
|
jbe@217
|
161 end
|
jbe/bsw@0
|
162 end
|
jbe/bsw@0
|
163 local paths = {}
|
jbe@217
|
164 for i, ext in ipairs(slib_exts) do
|
jbe@203
|
165 paths[#paths+1] = WEBMCP_FRAMEWORK_PATH .. "accelerator/?." .. ext
|
jbe/bsw@0
|
166 end
|
jbe@217
|
167 for i, ext in ipairs(slib_exts) do
|
jbe@203
|
168 paths[#paths+1] = WEBMCP_FRAMEWORK_PATH .. "lib/?." .. ext
|
jbe/bsw@0
|
169 end
|
jbe/bsw@0
|
170 paths[#paths+1] = package.cpath
|
jbe/bsw@0
|
171 package.cpath = table.concat(paths, ";")
|
jbe/bsw@0
|
172 end
|
jbe/bsw@0
|
173
|
jbe@203
|
174 -- autoloader system for WebMCP environment "$WEBMCP_FRAMEWORK_PATH/env/",
|
jbe@203
|
175 -- application environment extensions "$WEBMCP_BASE_PATH/env/"
|
jbe@203
|
176 -- and models "$WEBMCP_BASE_PATH/model/"
|
jbe/bsw@0
|
177 do
|
jbe/bsw@0
|
178 local weakkey_mt = { __mode = "k" }
|
jbe/bsw@0
|
179 local autoloader_category = setmetatable({}, weakkey_mt)
|
jbe/bsw@0
|
180 local autoloader_path = setmetatable({}, weakkey_mt)
|
jbe/bsw@0
|
181 local autoloader_mt = {}
|
jbe@219
|
182 local function install_autoloader(self, category, path_fragment)
|
jbe/bsw@0
|
183 autoloader_category[self] = category
|
jbe@219
|
184 autoloader_path[self] = path_fragment
|
jbe/bsw@0
|
185 setmetatable(self, autoloader_mt)
|
jbe/bsw@0
|
186 end
|
jbe/bsw@0
|
187 local function try_exec(filename)
|
jbe@309
|
188 local func = loadcached(filename)
|
jbe@309
|
189 if func then
|
jbe@309
|
190 func()
|
jbe@309
|
191 return true
|
jbe/bsw@0
|
192 else
|
jbe/bsw@0
|
193 return false
|
jbe/bsw@0
|
194 end
|
jbe/bsw@0
|
195 end
|
jbe/bsw@0
|
196 function autoloader_mt.__index(self, key)
|
jbe/bsw@0
|
197 local category, base_path, merge_base_path, file_key
|
jbe/bsw@0
|
198 local merge = false
|
jbe/bsw@0
|
199 if
|
jbe/bsw@0
|
200 string.find(key, "^[a-z_][A-Za-z0-9_]*$") and
|
jbe/bsw@0
|
201 not string.find(key, "^__")
|
jbe/bsw@0
|
202 then
|
jbe/bsw@0
|
203 category = "env"
|
jbe@203
|
204 base_path = WEBMCP_FRAMEWORK_PATH .. "env/"
|
jbe/bsw@0
|
205 merge = true
|
jbe@203
|
206 merge_base_path = WEBMCP_BASE_PATH .. "env/"
|
jbe/bsw@0
|
207 file_key = key
|
jbe/bsw@0
|
208 elseif string.find(key, "^[A-Z][A-Za-z0-9]*$") then
|
jbe/bsw@0
|
209 category = "model"
|
jbe@203
|
210 base_path = WEBMCP_BASE_PATH .. "model/"
|
jbe/bsw@0
|
211 local first = true
|
jbe/bsw@0
|
212 file_key = string.gsub(key, "[A-Z]",
|
jbe/bsw@0
|
213 function(c)
|
jbe/bsw@0
|
214 if first then
|
jbe/bsw@0
|
215 first = false
|
jbe/bsw@0
|
216 return string.lower(c)
|
jbe/bsw@0
|
217 else
|
jbe/bsw@0
|
218 return "_" .. string.lower(c)
|
jbe/bsw@0
|
219 end
|
jbe/bsw@0
|
220 end
|
jbe/bsw@0
|
221 )
|
jbe/bsw@0
|
222 else
|
jbe/bsw@0
|
223 return
|
jbe/bsw@0
|
224 end
|
jbe/bsw@0
|
225 local required_category = autoloader_category[self]
|
jbe/bsw@0
|
226 if required_category and required_category ~= category then return end
|
jbe@219
|
227 local path_fragment = autoloader_path[self]
|
jbe@219
|
228 local path = base_path .. path_fragment .. file_key
|
jbe@219
|
229 local merge_path
|
jbe/bsw@0
|
230 if merge then
|
jbe@219
|
231 merge_path = merge_base_path .. path_fragment .. file_key
|
jbe/bsw@0
|
232 end
|
jbe/bsw@0
|
233 local function try_dir(dirname)
|
jbe/bsw@0
|
234 local dir = io.open(dirname)
|
jbe/bsw@0
|
235 if dir then
|
jbe/bsw@0
|
236 io.close(dir)
|
jbe/bsw@0
|
237 local obj = {}
|
jbe@219
|
238 install_autoloader(obj, category, path_fragment .. file_key .. "/")
|
jbe/bsw@0
|
239 rawset(self, key, obj)
|
jbe@219
|
240 try_exec(path .. "/__init.lua")
|
jbe@219
|
241 if merge then try_exec(merge_path .. "/__init.lua") end
|
jbe/bsw@0
|
242 return true
|
jbe/bsw@0
|
243 else
|
jbe/bsw@0
|
244 return false
|
jbe/bsw@0
|
245 end
|
jbe/bsw@0
|
246 end
|
jbe@238
|
247 if self == _G then
|
jbe@237
|
248 allowed_globals[key] = true
|
jbe@233
|
249 end
|
jbe@219
|
250 if merge and try_exec(merge_path .. ".lua") then
|
jbe@219
|
251 elseif merge and try_dir(merge_path .. "/") then
|
jbe@219
|
252 elseif try_exec(path .. ".lua") then
|
jbe@219
|
253 elseif try_dir(path .. "/") then
|
jbe/bsw@0
|
254 else end
|
jbe@238
|
255 if self == _G then
|
jbe@237
|
256 allowed_globals[key] = nil
|
jbe@233
|
257 end
|
jbe@237
|
258 return rawget(self, key)
|
jbe/bsw@0
|
259 end
|
jbe@219
|
260 install_autoloader(_G, nil, "")
|
jbe@203
|
261 try_exec(WEBMCP_FRAMEWORK_PATH .. "env/__init.lua")
|
jbe@203
|
262 try_exec(WEBMCP_BASE_PATH .. "env/__init.lua")
|
jbe/bsw@0
|
263 end
|
jbe/bsw@0
|
264
|
jbe@214
|
265 -- replace Moonbridge listen function
|
jbe@214
|
266 local moonbridge_listen = listen
|
jbe@225
|
267 local listeners = {}
|
jbe@214
|
268 function listen(args)
|
jbe@214
|
269 listeners[#listeners+1] = args
|
jbe@214
|
270 end
|
jbe@214
|
271
|
jbe@203
|
272 -- prohibit (unintended) definition of new global variables
|
jbe@237
|
273 function global_metatable.__newindex(self, key, value)
|
jbe@237
|
274 if not allowed_globals[key] then
|
jbe@237
|
275 error("Setting of global variable prohibited", 2)
|
jbe@237
|
276 end
|
jbe@239
|
277 _G[key] = value
|
jbe@231
|
278 end
|
jbe@203
|
279
|
jbe@220
|
280 -- execute configurations and pre-fork initializers
|
jbe@206
|
281 for i, config_name in ipairs(WEBMCP_CONFIG_NAMES) do
|
jbe@206
|
282 execute.config(config_name)
|
jbe@206
|
283 end
|
jbe@220
|
284 execute.prefork_initializers()
|
jbe@206
|
285
|
jbe@286
|
286 -- define post-fork initialization function (including loading of "multirand" library)
|
jbe@286
|
287 local function postfork_init()
|
jbe@286
|
288 _G.multirand = require "multirand"
|
jbe@286
|
289 execute.postfork_initializers()
|
jbe@286
|
290 end
|
jbe@286
|
291
|
jbe/bsw@0
|
292 -- interactive console mode
|
jbe@203
|
293 if WEBMCP_MODE == "interactive" then
|
jbe@286
|
294 postfork_init()
|
jbe@289
|
295 trace.disable() -- avoids memory leakage
|
jbe/bsw@0
|
296 end
|
jbe/bsw@0
|
297
|
jbe@204
|
298 -- invoke moonbridge
|
jbe@206
|
299 if WEBMCP_MODE == "listen" then
|
jbe@264
|
300 local http_options = request.get_http_options()
|
jbe@288
|
301 local min_requests_per_fork = http_options.min_requests_per_fork or 50
|
jbe@288
|
302 local max_requests_per_fork = http_options.max_requests_per_fork or 100
|
jbe@207
|
303 local http = require("moonbridge_http")
|
jbe@211
|
304 for i, listener in ipairs(listeners) do
|
jbe@264
|
305 local request_count = 0
|
jbe@266
|
306 local function inner_handler(http_request)
|
jbe@264
|
307 request_count = request_count + 1
|
jbe@288
|
308 request.handler(http_request, request_count >= max_requests_per_fork)
|
jbe@264
|
309 end
|
jbe@264
|
310 local outer_handler = http.generate_handler(inner_handler, http_options)
|
jbe@286
|
311 listener.prepare = postfork_init
|
jbe@264
|
312 listener.connect = function(socket)
|
jbe@264
|
313 outer_handler(socket)
|
jbe@288
|
314 return request_count < min_requests_per_fork
|
jbe@264
|
315 end
|
jbe@211
|
316 listener.finish = execute.finalizers
|
jbe@211
|
317 moonbridge_listen(listener)
|
jbe@204
|
318 end
|
jbe@204
|
319 end
|
jbe@204
|
320
|