webmcp

annotate framework/bin/mcp.lua @ 333:51b17cee8432

Updated error message for accidentally setting globals
author jbe
date Tue Mar 24 17:27:28 2015 +0100 (2015-03-24)
parents 22275c74023a
children 75be5e3f3581
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@317 283 -- prepare for interactive or listen mode
jbe@203 284 if WEBMCP_MODE == "interactive" then
jbe@317 285 function listen() -- overwrite Moonbridge's listen function
jbe@317 286 -- ignore listen function calls for interactive mode
jbe@317 287 end
jbe@317 288 trace.disable() -- avoids memory leakage when scripts are running endlessly
jbe@317 289 else
jbe@317 290 local moonbridge_listen = listen
jbe@207 291 local http = require("moonbridge_http")
jbe@317 292 function listen(args) -- overwrite Moonbridge's listen function
jbe@322 293 assert(args, "No argument passed to listen function")
jbe@322 294 local min_requests_per_fork = args.min_requests_per_fork or 50
jbe@322 295 local max_requests_per_fork = args.max_requests_per_fork or 100
jbe@316 296 local interval_handlers = {}
jbe@317 297 for j, listener in ipairs(args) do
jbe@317 298 if listener.proto == "interval" then
jbe@317 299 local name = listener.name or "Unnamed interval #" .. #interval_handlers+1
jbe@317 300 interval_handlers[name] = listener.handler
jbe@317 301 listener.name = name
jbe@316 302 end
jbe@316 303 end
jbe@264 304 local request_count = 0
jbe@266 305 local function inner_handler(http_request)
jbe@328 306 request_count = request_count + 1
jbe@328 307 if request_count >= max_requests_per_fork then
jbe@328 308 http_request:close_after_finish()
jbe@328 309 end
jbe@317 310 request.initialize()
jbe@328 311 return request.handler(http_request)
jbe@264 312 end
jbe@326 313 local outer_handler = http.generate_handler(inner_handler, args.http_options)
jbe@317 314 args.prepare = postfork_init
jbe@317 315 args.connect = function(socket)
jbe@316 316 if socket.interval then
jbe@328 317 request_count = request_count + 1
jbe@317 318 request.initialize()
jbe@316 319 interval_handlers[socket.interval]()
jbe@316 320 else
jbe@327 321 local success = outer_handler(socket)
jbe@327 322 if not success then
jbe@327 323 return false
jbe@327 324 end
jbe@316 325 end
jbe@288 326 return request_count < min_requests_per_fork
jbe@264 327 end
jbe@317 328 args.finish = execute.finalizers
jbe@317 329 moonbridge_listen(args)
jbe@204 330 end
jbe@204 331 end
jbe@317 332
jbe@317 333 -- execute configurations and pre-fork initializers
jbe@317 334 for i, config_name in ipairs(WEBMCP_CONFIG_NAMES) do
jbe@317 335 execute.config(config_name)
jbe@317 336 end
jbe@317 337 execute.prefork_initializers()
jbe@317 338
jbe@324 339 -- perform post-fork initializations (once) in case of interactive mode
jbe@317 340 if WEBMCP_MODE == "interactive" then
jbe@317 341 postfork_init()
jbe@317 342 end

Impressum / About Us