webmcp

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

Impressum / About Us