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