webmcp
view framework/bin/mcp.lua @ 317:732c4d53a823
Invoke Moonbridge's listen function without delay (requires passing http_options as argument); Code cleanup
| author | jbe | 
|---|---|
| date | Mon Mar 23 19:05:32 2015 +0100 (2015-03-23) | 
| parents | a2c733535b8e | 
| children | f5660406ad3b | 
 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 --[[--
    12 _G
    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.
    16 The only exception is the <framework>/env/__init.lua file and the <application>/env/__init.lua file, in which global variables may be set without using the _G reference.
    18 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.
    20 --]]--
    21 local _G = _G
    22 local allowed_globals = {}
    23 local global_metatable = {
    24   __index = _G,
    25   __newindex = function(self, key, value)
    26     _G[key] = value
    27   end
    28 }
    29 _ENV = setmetatable(
    30   {},  -- proxy environment used within mcp.lua and by all chunks loaded through loadcached(...)
    31   global_metatable
    32 ) 
    33 local function protect_globals()  -- called before first configuration file is loaded
    34   function global_metatable.__newindex(self, key, value)
    35     if allowed_globals[key] then
    36       _G[key] = value
    37     else
    38       if type(key) == "string" and string.match(key, "^[A-Za-z_][A-Za-z_0-9]*$") then
    39         error('Attempt to set global variable "' .. key .. '" (hint: use _G.' .. key .. '=<value> to override protection mechnamism)', 2)
    40       else
    41         error('Attempt to set global variable', 2)
    42       end
    43     end
    44   end
    45 end
    47 --[[--
    48 lua_func =   -- compiled Lua function
    49 loadcached(
    50   filename   -- path to a Lua source or byte-code file
    51 )
    53 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.
    55 --]]--
    56 do
    57   local cache = {}
    58   function loadcached(filename)
    59     local cached_func = cache[filename]
    60     if cached_func then
    61       return cached_func
    62     end
    63     local file, read_error = io.open(filename, "r")
    64     if file then
    65       local filedata = assert(file:read("*a"))
    66       assert(file:close())
    67       local func, compile_error = load(filedata, "=" .. filename, nil, _ENV)
    68       if func then
    69         cache[filename] = func
    70         return func
    71       end
    72       error(compile_error, 0)
    73     else
    74       return nil, read_error
    75     end
    76   end
    77 end
    78 --//--
    80 --[[--
    81 WEBMCP_MODE
    83 A constant set to "listen" in case of a network request, or set to "interactive" in case of interactive mode.
    84 --]]--
    85 if listen then  -- defined by moonbridge
    86   WEBMCP_MODE = "listen"
    87 else
    88   WEBMCP_MODE = "interactive"
    89 end
    90 --//--
    92 --[[--
    93 WEBMCP_CONFIG_NAMES
    95 A list of the selected configuration names.
    96 --]]--
    97 -- configuration names are provided as 4th, 5th, etc. command line argument
    98 WEBMCP_CONFIG_NAMES = {select(4, ...)}
    99 --//--
   101 --[[--
   102 WEBMCP_FRAMEWORK_PATH
   104 Directory of the WebMCP framework (always includes a trailing slash).
   105 --]]--
   106 -- set in mcp.lua
   107 --//--
   109 --[[--
   110 WEBMCP_BASE_PATH
   112 Base directory of the application (always includes a trailing slash).
   113 --]]--
   114 -- set in mcp.lua
   115 --//--
   117 --[[--
   118 WEBMCP_APP_NAME
   120 Application name (usually "main"). May be nil in case of interactive mode.
   121 --]]--
   122 -- set in mcp.lua
   123 --//--
   125 -- determine framework and bath path from command line arguments
   126 -- or print usage synopsis (if applicable)
   127 do
   128   local arg1, arg2, arg3 = ...
   129   local helpout
   130   if
   131     arg1 == "-h" or arg1 == "--help" or
   132     arg2 == "-h" or arg2 == "--help"  -- if first arg is provided by wrapper
   133   then
   134     helpout = io.stdout
   135   elseif #WEBMCP_CONFIG_NAMES < 1 then
   136     helpout = io.stderr
   137   end
   138   if helpout then
   139     helpout:write("Usage: moonbridge [moonbr opts] -- <framework path>/bin/mcp.lua <framework path> <app base path> <app name> <config name> [<config name> ...]\n")
   140     helpout:write("   or: lua -i     [Lua opts]    -- <framework path>/bin/mcp.lua <framework path> <app base path> <app name> <config name> [<config name> ...]\n")
   141     if helpout == io.stderr then
   142       return 1
   143     else
   144       return 0
   145     end
   146   end
   147   local function append_trailing_slash(str)
   148     return string.gsub(str, "([^/])$", function(last) return last .. "/" end)
   149   end
   150   WEBMCP_FRAMEWORK_PATH = append_trailing_slash(arg1)
   151   WEBMCP_BASE_PATH      = append_trailing_slash(arg2)
   152   WEBMCP_APP_NAME       = arg3
   153 end
   155 -- check if framework path is correct
   156 do
   157   local file, errmsg = io.open(WEBMCP_FRAMEWORK_PATH .. "webmcp_version", "r")
   158   if not file then
   159     error('Could not find "webmcp_version" file: ' .. errmsg, 0)
   160   end
   161   local version = assert(file:read())
   162   assert(file:close())
   163   if version ~= WEBMCP_VERSION then
   164     error('Version mismatch in file "' .. WEBMCP_FRAMEWORK_PATH .. 'webmcp_version"')
   165   end
   166 end
   168 -- setup search paths for libraries
   169 do
   170   if string.match(package.path, "^[^;]") then
   171     package.path = ";" .. package.path
   172   end
   173   package.path = WEBMCP_FRAMEWORK_PATH .. "lib/?.lua" .. package.path
   174   -- find out which file name extension shared libraries have
   175   local slib_exts = {}
   176   for ext in string.gmatch(package.cpath, "%?%.([A-Za-z0-9_-]+)") do
   177     if not slib_exts[ext] then
   178       slib_exts[#slib_exts+1] = ext
   179       slib_exts[ext] = true
   180     end
   181   end
   182   local paths = {}
   183   for i, ext in ipairs(slib_exts) do
   184     paths[#paths+1] = WEBMCP_FRAMEWORK_PATH .. "accelerator/?." .. ext
   185   end
   186   for i, ext in ipairs(slib_exts) do
   187     paths[#paths+1] = WEBMCP_FRAMEWORK_PATH .. "lib/?." .. ext
   188   end
   189   paths[#paths+1] = package.cpath
   190   package.cpath = table.concat(paths, ";")
   191 end
   193 -- autoloader system for WebMCP environment "$WEBMCP_FRAMEWORK_PATH/env/",
   194 -- application environment extensions "$WEBMCP_BASE_PATH/env/"
   195 -- and models "$WEBMCP_BASE_PATH/model/"
   196 local root_init  -- function which executes the __init.lua file in the environment's root
   197 do
   198   local weakkey_mt = { __mode = "k" }
   199   local autoloader_category = setmetatable({}, weakkey_mt)
   200   local autoloader_path     = setmetatable({}, weakkey_mt)
   201   local autoloader_mt       = {}
   202   local function install_autoloader(self, category, path_fragment)
   203     autoloader_category[self] = category
   204     autoloader_path[self]     = path_fragment
   205     setmetatable(self, autoloader_mt)
   206   end
   207   local function try_exec(filename)
   208     local func = loadcached(filename)
   209     if func then
   210       func()
   211       return true
   212     else
   213       return false
   214     end
   215   end
   216   function autoloader_mt.__index(self, key)
   217     local category, base_path, merge_base_path, file_key
   218     local merge = false
   219     if
   220       string.find(key, "^[a-z_][A-Za-z0-9_]*$") and
   221       not string.find(key, "^__")
   222     then
   223       category        = "env"
   224       base_path       = WEBMCP_FRAMEWORK_PATH .. "env/"
   225       merge           = true
   226       merge_base_path = WEBMCP_BASE_PATH .. "env/"
   227       file_key        = key
   228     elseif string.find(key, "^[A-Z][A-Za-z0-9]*$") then
   229       category        = "model"
   230       base_path       = WEBMCP_BASE_PATH .. "model/"
   231       local first = true
   232       file_key = string.gsub(key, "[A-Z]",
   233         function(c)
   234           if first then
   235             first = false
   236             return string.lower(c)
   237           else
   238             return "_" .. string.lower(c)
   239           end
   240         end
   241       )
   242     else
   243       return
   244     end
   245     local required_category = autoloader_category[self]
   246     if required_category and required_category ~= category then return end
   247     local path_fragment = autoloader_path[self]
   248     local path = base_path .. path_fragment .. file_key
   249     local merge_path
   250     if merge then
   251       merge_path = merge_base_path .. path_fragment .. file_key
   252     end
   253     local function try_dir(dirname)
   254       local dir = io.open(dirname)
   255       if dir then
   256         io.close(dir)
   257         local obj = {}
   258         install_autoloader(obj, category, path_fragment .. file_key .. "/")
   259         rawset(self, key, obj)
   260         try_exec(path .. "/__init.lua")
   261         if merge then try_exec(merge_path .. "/__init.lua") end
   262         return true
   263       else
   264         return false
   265       end
   266     end
   267     if self == _G then
   268       allowed_globals[key] = true
   269     end
   270     if merge and try_exec(merge_path .. ".lua") then
   271     elseif merge and try_dir(merge_path .. "/") then
   272     elseif try_exec(path .. ".lua") then
   273     elseif try_dir(path .. "/") then
   274     else end
   275     if self == _G then
   276       allowed_globals[key] = nil
   277     end
   278     return rawget(self, key)
   279   end
   280   install_autoloader(_G, nil, "")
   281   function root_init()  -- upvalue
   282     try_exec(WEBMCP_FRAMEWORK_PATH .. "env/__init.lua")
   283     try_exec(WEBMCP_BASE_PATH .. "env/__init.lua")
   284   end
   285 end
   287 -- define post-fork initialization function (including loading of "multirand" library)
   288 local function postfork_init()
   289   _G.multirand = require "multirand"
   290   execute.postfork_initializers()
   291 end
   293 -- prepare for interactive or listen mode
   294 if WEBMCP_MODE == "interactive" then
   295   function listen()  -- overwrite Moonbridge's listen function
   296     -- ignore listen function calls for interactive mode
   297   end
   298   trace.disable()  -- avoids memory leakage when scripts are running endlessly
   299 else
   300   local moonbridge_listen = listen
   301   local http = require("moonbridge_http")
   302   function listen(args)  -- overwrite Moonbridge's listen function
   303     local http_options = args.http_options or {}
   304     local min_requests_per_fork = http_options.min_requests_per_fork or 50
   305     local max_requests_per_fork = http_options.max_requests_per_fork or 100
   306     local interval_handlers = {}
   307     for j, listener in ipairs(args) do
   308       if listener.proto == "interval" then
   309         local name = listener.name or "Unnamed interval #" .. #interval_handlers+1
   310         interval_handlers[name] = listener.handler
   311         listener.name = name
   312       end
   313     end
   314     local request_count = 0
   315     local function inner_handler(http_request)
   316       request.initialize()
   317       request.handler(http_request, request_count >= max_requests_per_fork)
   318     end
   319     local outer_handler = http.generate_handler(inner_handler, http_options)
   320     args.prepare = postfork_init
   321     args.connect = function(socket)
   322       request_count = request_count + 1
   323       if socket.interval then
   324         request.initialize()
   325         interval_handlers[socket.interval]()
   326       else
   327         outer_handler(socket)
   328       end
   329       return request_count < min_requests_per_fork
   330     end
   331     args.finish = execute.finalizers
   332     moonbridge_listen(args)
   333   end
   334 end
   336 -- execute the __init.lua file in the environment's root
   337 root_init()
   339 -- prohibit (unintended) definition of new global variables
   340 protect_globals()
   342 -- execute configurations and pre-fork initializers
   343 for i, config_name in ipairs(WEBMCP_CONFIG_NAMES) do
   344   execute.config(config_name)
   345 end
   346 execute.prefork_initializers()
   348 -- perform post-fork initializations in case of interactive mode
   349 if WEBMCP_MODE == "interactive" then
   350   postfork_init()
   351 end
