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
