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