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