webmcp

view framework/bin/mcp.lua @ 309:4e69ce9a6365

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

Impressum / About Us