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 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
|
17
|
jbe@317
|
18 --]]--
|
jbe@231
|
19 local _G = _G
|
jbe@237
|
20 local allowed_globals = {}
|
jbe@324
|
21 local protected_environment = setmetatable(
|
jbe@324
|
22 {}, -- proxy environment used all chunks loaded through loadcached(...)
|
jbe@324
|
23 {
|
jbe@324
|
24 __index = _G,
|
jbe@324
|
25 __newindex = function(self, key, value)
|
jbe@324
|
26 if allowed_globals[key] then
|
jbe@324
|
27 _G[key] = value
|
jbe@317
|
28 else
|
jbe@324
|
29 if type(key) == "string" and string.match(key, "^[A-Za-z_][A-Za-z_0-9]*$") then
|
jbe@333
|
30 error('Attempt to set global variable "' .. key .. '" (Hint: missing local statement? Use _G.' .. key .. '=<value> to really set global variable.)', 2)
|
jbe@324
|
31 else
|
jbe@324
|
32 error('Attempt to set global variable', 2)
|
jbe@324
|
33 end
|
jbe@317
|
34 end
|
jbe@317
|
35 end
|
jbe@324
|
36 }
|
jbe@324
|
37 )
|
jbe@318
|
38 --//--
|
jbe@231
|
39
|
jbe@294
|
40 --[[--
|
jbe@309
|
41 lua_func = -- compiled Lua function
|
jbe@309
|
42 loadcached(
|
jbe@309
|
43 filename -- path to a Lua source or byte-code file
|
jbe@309
|
44 )
|
jbe@309
|
45
|
jbe@309
|
46 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
|
47
|
jbe@309
|
48 --]]--
|
jbe@309
|
49 do
|
jbe@309
|
50 local cache = {}
|
jbe@309
|
51 function loadcached(filename)
|
jbe@311
|
52 local cached_func = cache[filename]
|
jbe@311
|
53 if cached_func then
|
jbe@311
|
54 return cached_func
|
jbe@311
|
55 end
|
jbe@309
|
56 local file, read_error = io.open(filename, "r")
|
jbe@309
|
57 if file then
|
jbe@309
|
58 local filedata = assert(file:read("*a"))
|
jbe@309
|
59 assert(file:close())
|
jbe@324
|
60 local func, compile_error = load(filedata, "=" .. filename, nil, protected_environment)
|
jbe@309
|
61 if func then
|
jbe@309
|
62 cache[filename] = func
|
jbe@309
|
63 return func
|
jbe@309
|
64 end
|
jbe@311
|
65 error(compile_error, 0)
|
jbe@309
|
66 else
|
jbe@309
|
67 return nil, read_error
|
jbe@309
|
68 end
|
jbe@309
|
69 end
|
jbe@309
|
70 end
|
jbe@309
|
71 --//--
|
jbe@309
|
72
|
jbe@309
|
73 --[[--
|
jbe@294
|
74 WEBMCP_MODE
|
jbe@294
|
75
|
jbe@294
|
76 A constant set to "listen" in case of a network request, or set to "interactive" in case of interactive mode.
|
jbe@294
|
77 --]]--
|
jbe@323
|
78 if _MOONBRIDGE_VERSION then
|
jbe@203
|
79 WEBMCP_MODE = "listen"
|
jbe@203
|
80 else
|
jbe@203
|
81 WEBMCP_MODE = "interactive"
|
jbe@64
|
82 end
|
jbe@294
|
83 --//--
|
jbe@203
|
84
|
jbe@294
|
85 --[[--
|
jbe@294
|
86 WEBMCP_CONFIG_NAMES
|
jbe@294
|
87
|
jbe@294
|
88 A list of the selected configuration names.
|
jbe@294
|
89 --]]--
|
jbe@294
|
90 -- configuration names are provided as 4th, 5th, etc. command line argument
|
jbe@206
|
91 WEBMCP_CONFIG_NAMES = {select(4, ...)}
|
jbe@294
|
92 --//--
|
jbe@294
|
93
|
jbe@294
|
94 --[[--
|
jbe@294
|
95 WEBMCP_FRAMEWORK_PATH
|
jbe@294
|
96
|
jbe@294
|
97 Directory of the WebMCP framework (always includes a trailing slash).
|
jbe@294
|
98 --]]--
|
jbe@294
|
99 -- set in mcp.lua
|
jbe@294
|
100 --//--
|
jbe@294
|
101
|
jbe@294
|
102 --[[--
|
jbe@294
|
103 WEBMCP_BASE_PATH
|
jbe@294
|
104
|
jbe@294
|
105 Base directory of the application (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_APP_NAME
|
jbe@294
|
112
|
jbe@294
|
113 Application name (usually "main"). May be nil in case of interactive mode.
|
jbe@294
|
114 --]]--
|
jbe@294
|
115 -- set in mcp.lua
|
jbe@294
|
116 --//--
|
jbe@203
|
117
|
jbe@203
|
118 -- determine framework and bath path from command line arguments
|
jbe@203
|
119 -- or print usage synopsis (if applicable)
|
jbe@68
|
120 do
|
jbe@206
|
121 local arg1, arg2, arg3 = ...
|
jbe@203
|
122 local helpout
|
jbe@203
|
123 if
|
jbe@203
|
124 arg1 == "-h" or arg1 == "--help" or
|
jbe@206
|
125 arg2 == "-h" or arg2 == "--help" -- if first arg is provided by wrapper
|
jbe@203
|
126 then
|
jbe@203
|
127 helpout = io.stdout
|
jbe@316
|
128 elseif #WEBMCP_CONFIG_NAMES < 1 then
|
jbe@203
|
129 helpout = io.stderr
|
jbe@203
|
130 end
|
jbe@203
|
131 if helpout then
|
jbe@316
|
132 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
|
133 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
|
134 if helpout == io.stderr then
|
jbe@203
|
135 return 1
|
jbe@68
|
136 else
|
jbe@203
|
137 return 0
|
jbe@68
|
138 end
|
jbe@68
|
139 end
|
jbe@203
|
140 local function append_trailing_slash(str)
|
jbe@217
|
141 return string.gsub(str, "([^/])$", function(last) return last .. "/" end)
|
jbe@203
|
142 end
|
jbe@203
|
143 WEBMCP_FRAMEWORK_PATH = append_trailing_slash(arg1)
|
jbe@203
|
144 WEBMCP_BASE_PATH = append_trailing_slash(arg2)
|
jbe@316
|
145 WEBMCP_APP_NAME = arg3
|
jbe@68
|
146 end
|
jbe@1
|
147
|
jbe@313
|
148 -- check if framework path is correct
|
jbe@313
|
149 do
|
jbe@313
|
150 local file, errmsg = io.open(WEBMCP_FRAMEWORK_PATH .. "webmcp_version", "r")
|
jbe@313
|
151 if not file then
|
jbe@313
|
152 error('Could not find "webmcp_version" file: ' .. errmsg, 0)
|
jbe@313
|
153 end
|
jbe@313
|
154 local version = assert(file:read())
|
jbe@313
|
155 assert(file:close())
|
jbe@313
|
156 if version ~= WEBMCP_VERSION then
|
jbe@313
|
157 error('Version mismatch in file "' .. WEBMCP_FRAMEWORK_PATH .. 'webmcp_version"')
|
jbe@313
|
158 end
|
jbe@313
|
159 end
|
jbe@313
|
160
|
jbe@203
|
161 -- setup search paths for libraries
|
jbe/bsw@0
|
162 do
|
jbe@217
|
163 if string.match(package.path, "^[^;]") then
|
jbe@217
|
164 package.path = ";" .. package.path
|
jbe@217
|
165 end
|
jbe@217
|
166 package.path = WEBMCP_FRAMEWORK_PATH .. "lib/?.lua" .. package.path
|
jbe/bsw@0
|
167 -- find out which file name extension shared libraries have
|
jbe/bsw@0
|
168 local slib_exts = {}
|
jbe/bsw@0
|
169 for ext in string.gmatch(package.cpath, "%?%.([A-Za-z0-9_-]+)") do
|
jbe@217
|
170 if not slib_exts[ext] then
|
jbe@217
|
171 slib_exts[#slib_exts+1] = ext
|
jbe@217
|
172 slib_exts[ext] = true
|
jbe@217
|
173 end
|
jbe/bsw@0
|
174 end
|
jbe/bsw@0
|
175 local paths = {}
|
jbe@217
|
176 for i, ext in ipairs(slib_exts) do
|
jbe@203
|
177 paths[#paths+1] = WEBMCP_FRAMEWORK_PATH .. "accelerator/?." .. ext
|
jbe/bsw@0
|
178 end
|
jbe@217
|
179 for i, ext in ipairs(slib_exts) do
|
jbe@203
|
180 paths[#paths+1] = WEBMCP_FRAMEWORK_PATH .. "lib/?." .. ext
|
jbe/bsw@0
|
181 end
|
jbe/bsw@0
|
182 paths[#paths+1] = package.cpath
|
jbe/bsw@0
|
183 package.cpath = table.concat(paths, ";")
|
jbe/bsw@0
|
184 end
|
jbe/bsw@0
|
185
|
jbe@203
|
186 -- autoloader system for WebMCP environment "$WEBMCP_FRAMEWORK_PATH/env/",
|
jbe@203
|
187 -- application environment extensions "$WEBMCP_BASE_PATH/env/"
|
jbe@203
|
188 -- and models "$WEBMCP_BASE_PATH/model/"
|
jbe/bsw@0
|
189 do
|
jbe/bsw@0
|
190 local weakkey_mt = { __mode = "k" }
|
jbe/bsw@0
|
191 local autoloader_category = setmetatable({}, weakkey_mt)
|
jbe/bsw@0
|
192 local autoloader_path = setmetatable({}, weakkey_mt)
|
jbe/bsw@0
|
193 local autoloader_mt = {}
|
jbe@219
|
194 local function install_autoloader(self, category, path_fragment)
|
jbe/bsw@0
|
195 autoloader_category[self] = category
|
jbe@219
|
196 autoloader_path[self] = path_fragment
|
jbe/bsw@0
|
197 setmetatable(self, autoloader_mt)
|
jbe/bsw@0
|
198 end
|
jbe/bsw@0
|
199 local function try_exec(filename)
|
jbe@309
|
200 local func = loadcached(filename)
|
jbe@309
|
201 if func then
|
jbe@309
|
202 func()
|
jbe@309
|
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/bsw@0
|
208 function autoloader_mt.__index(self, key)
|
jbe/bsw@0
|
209 local category, base_path, merge_base_path, file_key
|
jbe/bsw@0
|
210 local merge = false
|
jbe/bsw@0
|
211 if
|
jbe/bsw@0
|
212 string.find(key, "^[a-z_][A-Za-z0-9_]*$") and
|
jbe/bsw@0
|
213 not string.find(key, "^__")
|
jbe/bsw@0
|
214 then
|
jbe/bsw@0
|
215 category = "env"
|
jbe@203
|
216 base_path = WEBMCP_FRAMEWORK_PATH .. "env/"
|
jbe/bsw@0
|
217 merge = true
|
jbe@203
|
218 merge_base_path = WEBMCP_BASE_PATH .. "env/"
|
jbe/bsw@0
|
219 file_key = key
|
jbe/bsw@0
|
220 elseif string.find(key, "^[A-Z][A-Za-z0-9]*$") then
|
jbe/bsw@0
|
221 category = "model"
|
jbe@203
|
222 base_path = WEBMCP_BASE_PATH .. "model/"
|
jbe/bsw@0
|
223 local first = true
|
jbe/bsw@0
|
224 file_key = string.gsub(key, "[A-Z]",
|
jbe/bsw@0
|
225 function(c)
|
jbe/bsw@0
|
226 if first then
|
jbe/bsw@0
|
227 first = false
|
jbe/bsw@0
|
228 return string.lower(c)
|
jbe/bsw@0
|
229 else
|
jbe/bsw@0
|
230 return "_" .. string.lower(c)
|
jbe/bsw@0
|
231 end
|
jbe/bsw@0
|
232 end
|
jbe/bsw@0
|
233 )
|
jbe/bsw@0
|
234 else
|
jbe/bsw@0
|
235 return
|
jbe/bsw@0
|
236 end
|
jbe/bsw@0
|
237 local required_category = autoloader_category[self]
|
jbe/bsw@0
|
238 if required_category and required_category ~= category then return end
|
jbe@219
|
239 local path_fragment = autoloader_path[self]
|
jbe@219
|
240 local path = base_path .. path_fragment .. file_key
|
jbe@219
|
241 local merge_path
|
jbe/bsw@0
|
242 if merge then
|
jbe@219
|
243 merge_path = merge_base_path .. path_fragment .. file_key
|
jbe/bsw@0
|
244 end
|
jbe/bsw@0
|
245 local function try_dir(dirname)
|
jbe/bsw@0
|
246 local dir = io.open(dirname)
|
jbe/bsw@0
|
247 if dir then
|
jbe/bsw@0
|
248 io.close(dir)
|
jbe/bsw@0
|
249 local obj = {}
|
jbe@219
|
250 install_autoloader(obj, category, path_fragment .. file_key .. "/")
|
jbe/bsw@0
|
251 rawset(self, key, obj)
|
jbe@219
|
252 try_exec(path .. "/__init.lua")
|
jbe@219
|
253 if merge then try_exec(merge_path .. "/__init.lua") end
|
jbe/bsw@0
|
254 return true
|
jbe/bsw@0
|
255 else
|
jbe/bsw@0
|
256 return false
|
jbe/bsw@0
|
257 end
|
jbe/bsw@0
|
258 end
|
jbe@238
|
259 if self == _G then
|
jbe@237
|
260 allowed_globals[key] = true
|
jbe@233
|
261 end
|
jbe@219
|
262 if merge and try_exec(merge_path .. ".lua") then
|
jbe@219
|
263 elseif merge and try_dir(merge_path .. "/") then
|
jbe@219
|
264 elseif try_exec(path .. ".lua") then
|
jbe@219
|
265 elseif try_dir(path .. "/") then
|
jbe/bsw@0
|
266 else end
|
jbe@238
|
267 if self == _G then
|
jbe@237
|
268 allowed_globals[key] = nil
|
jbe@233
|
269 end
|
jbe@237
|
270 return rawget(self, key)
|
jbe/bsw@0
|
271 end
|
jbe@219
|
272 install_autoloader(_G, nil, "")
|
jbe@324
|
273 try_exec(WEBMCP_FRAMEWORK_PATH .. "env/__init.lua")
|
jbe@324
|
274 try_exec(WEBMCP_BASE_PATH .. "env/__init.lua")
|
jbe@214
|
275 end
|
jbe@214
|
276
|
jbe@286
|
277 -- define post-fork initialization function (including loading of "multirand" library)
|
jbe@286
|
278 local function postfork_init()
|
jbe@324
|
279 multirand = require "multirand"
|
jbe@286
|
280 execute.postfork_initializers()
|
jbe@286
|
281 end
|
jbe@286
|
282
|
jbe@336
|
283 --[[--
|
jbe@336
|
284 listen{
|
jbe@336
|
285 {
|
jbe@336
|
286 proto = proto, -- "local", "tcp4", "tcp6", or "interval"
|
jbe@336
|
287 path = path, -- path to unix domain socket if proto == "local"
|
jbe@336
|
288 port = port, -- TCP port number
|
jbe@336
|
289 localhost = localhost_only, -- set to true to only listen on localhost (127.0.0.1 or ::1) interface
|
jbe@336
|
290 name = interval_name, -- optional interval name (may be useful for log output)
|
jbe@336
|
291 handler = interval_handler -- interval handler if proto == "interval"
|
jbe@336
|
292 },
|
jbe@336
|
293 {
|
jbe@336
|
294 ... -- second listener
|
jbe@336
|
295 },
|
jbe@336
|
296 ... -- more listeners
|
jbe@336
|
297 -- the following options are all optional and have default values:
|
jbe@336
|
298 pre_fork = pre_fork, -- desired number of spare (idle) processes
|
jbe@336
|
299 min_fork = min_fork, -- minimum number of processes
|
jbe@336
|
300 max_fork = max_fork, -- maximum number of processes (hard limit)
|
jbe@336
|
301 fork_delay = fork_delay, -- delay (seconds) between creation of spare processes
|
jbe@336
|
302 fork_error_delay = fork_error_delay, -- delay (seconds) before retry of failed process creation
|
jbe@336
|
303 exit_delay = exit_delay, -- delay (seconds) between destruction of excessive spare processes
|
jbe@336
|
304 idle_timeout = idle_timeout, -- idle time (seconds) after a fork gets terminated (0 for no timeout)
|
jbe@336
|
305 memory_limit = memory_limit, -- maximum memory consumption (bytes) before process gets terminated
|
jbe@336
|
306 min_requests_per_fork = min_requests_per_fork, -- minimum count of requests handled before fork is terminated
|
jbe@336
|
307 max_requests_per_fork = max_requests_per_fork, -- maximum count of requests handled before fork is terminated
|
jbe@336
|
308 http_options = {
|
jbe@336
|
309 static_headers = static_headers, -- string or table of static headers to be returned with every request
|
jbe@336
|
310 request_body_size_limit = request_body_size_limit, -- maximum size of request body sent by client
|
jbe@336
|
311 request_header_timeout = request_header_timeout, -- time after which request headers must have been received and processed
|
jbe@336
|
312 timeout = timeout, -- time in which request body and response must be sent
|
jbe@336
|
313 minimum_output_chunk_size = minimum_output_chunk_size -- chunk size for chunked-transfer-encoding
|
jbe@336
|
314 }
|
jbe@336
|
315 }
|
jbe@336
|
316
|
jbe@336
|
317 The listen{...} function determines on which TCP port an application is answering requests. A typical call looks as follows:
|
jbe@336
|
318
|
jbe@336
|
319 listen{
|
jbe@336
|
320 { proto = "tcp4", port = 8080, localhost = true },
|
jbe@336
|
321 { proto = "tcp6", port = 8080, localhost = true }
|
jbe@336
|
322 }
|
jbe@336
|
323
|
jbe@336
|
324 This function must be called in a configuration file (in the config/ directory) or in pre-fork initializers (in the app/_prefork/ or app/<application name>/_prefork/ directories), unless WebMCP is invoked in interactive mode (in which case any calls of listen{...} are ignored).
|
jbe@336
|
325
|
jbe@336
|
326 This function is a variant of Moonbridge's listen{...} function which has been wrapped for WebMCP. No "prepare", "conenct", or "finish" handler can be set. Instead WebMCP automatically dispatches incoming connections. For interval timers, an interval handler may be specified in each listener.
|
jbe@336
|
327
|
jbe@336
|
328 --]]--
|
jbe@317
|
329 -- prepare for interactive or listen mode
|
jbe@203
|
330 if WEBMCP_MODE == "interactive" then
|
jbe@317
|
331 function listen() -- overwrite Moonbridge's listen function
|
jbe@317
|
332 -- ignore listen function calls for interactive mode
|
jbe@317
|
333 end
|
jbe@317
|
334 trace.disable() -- avoids memory leakage when scripts are running endlessly
|
jbe@317
|
335 else
|
jbe@317
|
336 local moonbridge_listen = listen
|
jbe@207
|
337 local http = require("moonbridge_http")
|
jbe@317
|
338 function listen(args) -- overwrite Moonbridge's listen function
|
jbe@322
|
339 assert(args, "No argument passed to listen function")
|
jbe@322
|
340 local min_requests_per_fork = args.min_requests_per_fork or 50
|
jbe@335
|
341 local max_requests_per_fork = args.max_requests_per_fork or 200
|
jbe@316
|
342 local interval_handlers = {}
|
jbe@317
|
343 for j, listener in ipairs(args) do
|
jbe@317
|
344 if listener.proto == "interval" then
|
jbe@317
|
345 local name = listener.name or "Unnamed interval #" .. #interval_handlers+1
|
jbe@336
|
346 if interval_handlers[name] ~= nil then
|
jbe@336
|
347 error('Interval handler with duplicate name "' .. name .. '"')
|
jbe@336
|
348 end
|
jbe@317
|
349 interval_handlers[name] = listener.handler
|
jbe@317
|
350 listener.name = name
|
jbe@316
|
351 end
|
jbe@316
|
352 end
|
jbe@264
|
353 local request_count = 0
|
jbe@266
|
354 local function inner_handler(http_request)
|
jbe@328
|
355 request_count = request_count + 1
|
jbe@328
|
356 if request_count >= max_requests_per_fork then
|
jbe@328
|
357 http_request:close_after_finish()
|
jbe@328
|
358 end
|
jbe@317
|
359 request.initialize()
|
jbe@328
|
360 return request.handler(http_request)
|
jbe@264
|
361 end
|
jbe@326
|
362 local outer_handler = http.generate_handler(inner_handler, args.http_options)
|
jbe@317
|
363 args.prepare = postfork_init
|
jbe@317
|
364 args.connect = function(socket)
|
jbe@316
|
365 if socket.interval then
|
jbe@328
|
366 request_count = request_count + 1
|
jbe@317
|
367 request.initialize()
|
jbe@316
|
368 interval_handlers[socket.interval]()
|
jbe@316
|
369 else
|
jbe@327
|
370 local success = outer_handler(socket)
|
jbe@327
|
371 if not success then
|
jbe@327
|
372 return false
|
jbe@327
|
373 end
|
jbe@316
|
374 end
|
jbe@288
|
375 return request_count < min_requests_per_fork
|
jbe@264
|
376 end
|
jbe@317
|
377 args.finish = execute.finalizers
|
jbe@317
|
378 moonbridge_listen(args)
|
jbe@204
|
379 end
|
jbe@204
|
380 end
|
jbe@336
|
381 --//--
|
jbe@317
|
382
|
jbe@317
|
383 -- execute configurations and pre-fork initializers
|
jbe@317
|
384 for i, config_name in ipairs(WEBMCP_CONFIG_NAMES) do
|
jbe@317
|
385 execute.config(config_name)
|
jbe@317
|
386 end
|
jbe@317
|
387 execute.prefork_initializers()
|
jbe@317
|
388
|
jbe@324
|
389 -- perform post-fork initializations (once) in case of interactive mode
|
jbe@317
|
390 if WEBMCP_MODE == "interactive" then
|
jbe@317
|
391 postfork_init()
|
jbe@317
|
392 end
|