webmcp
view framework/bin/mcp.lua @ 342:061ee100f1c1
Bugfix: respect "index" argument to request.get_param{...} function; Allow access to metadata of POST fields in request.get_param{...}
| author | jbe | 
|---|---|
| date | Tue Mar 24 23:54:14 2015 +0100 (2015-03-24) | 
| parents | 3687294cb955 | 
| children | 2b5bdf9028fb | 
 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 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.
    18 --]]--
    19 local _G = _G
    20 local allowed_globals = {}
    21 local protected_environment = setmetatable(
    22   {},  -- proxy environment used all chunks loaded through loadcached(...)
    23   {
    24     __index = _G,
    25     __newindex = function(self, key, value)
    26       if allowed_globals[key] then
    27         _G[key] = value
    28       else
    29         if type(key) == "string" and string.match(key, "^[A-Za-z_][A-Za-z_0-9]*$") then
    30           error('Attempt to set global variable "' .. key .. '" (Hint: missing local statement? Use _G.' .. key .. '=<value> to really set global variable.)', 2)
    31         else
    32           error('Attempt to set global variable', 2)
    33         end
    34       end
    35     end
    36   }
    37 )
    38 --//--
    40 --[[--
    41 lua_func =   -- compiled Lua function
    42 loadcached(
    43   filename   -- path to a Lua source or byte-code file
    44 )
    46 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.
    48 --]]--
    49 do
    50   local cache = {}
    51   function loadcached(filename)
    52     local cached_func = cache[filename]
    53     if cached_func then
    54       return cached_func
    55     end
    56     local file, read_error = io.open(filename, "r")
    57     if file then
    58       local filedata = assert(file:read("*a"))
    59       assert(file:close())
    60       local func, compile_error = load(filedata, "=" .. filename, nil, protected_environment)
    61       if func then
    62         cache[filename] = func
    63         return func
    64       end
    65       error(compile_error, 0)
    66     else
    67       return nil, read_error
    68     end
    69   end
    70 end
    71 --//--
    73 --[[--
    74 WEBMCP_MODE
    76 A constant set to "listen" in case of a network request, or set to "interactive" in case of interactive mode.
    77 --]]--
    78 if _MOONBRIDGE_VERSION then
    79   WEBMCP_MODE = "listen"
    80 else
    81   WEBMCP_MODE = "interactive"
    82 end
    83 --//--
    85 --[[--
    86 WEBMCP_CONFIG_NAMES
    88 A list of the selected configuration names.
    89 --]]--
    90 -- configuration names are provided as 4th, 5th, etc. command line argument
    91 WEBMCP_CONFIG_NAMES = {select(4, ...)}
    92 --//--
    94 --[[--
    95 WEBMCP_FRAMEWORK_PATH
    97 Directory of the WebMCP framework (always includes a trailing slash).
    98 --]]--
    99 -- set in mcp.lua
   100 --//--
   102 --[[--
   103 WEBMCP_BASE_PATH
   105 Base directory of the application (always includes a trailing slash).
   106 --]]--
   107 -- set in mcp.lua
   108 --//--
   110 --[[--
   111 WEBMCP_APP_NAME
   113 Application name (usually "main"). May be nil in case of interactive mode.
   114 --]]--
   115 -- set in mcp.lua
   116 --//--
   118 -- determine framework and bath path from command line arguments
   119 -- or print usage synopsis (if applicable)
   120 do
   121   local arg1, arg2, arg3 = ...
   122   local helpout
   123   if
   124     arg1 == "-h" or arg1 == "--help" or
   125     arg2 == "-h" or arg2 == "--help"  -- if first arg is provided by wrapper
   126   then
   127     helpout = io.stdout
   128   elseif #WEBMCP_CONFIG_NAMES < 1 then
   129     helpout = io.stderr
   130   end
   131   if helpout then
   132     helpout:write("Usage: moonbridge [moonbr opts] -- <framework path>/bin/mcp.lua <framework path> <app base path> <app name> <config name> [<config name> ...]\n")
   133     helpout:write("   or: lua -i     [Lua opts]    -- <framework path>/bin/mcp.lua <framework path> <app base path> <app name> <config name> [<config name> ...]\n")
   134     if helpout == io.stderr then
   135       return 1
   136     else
   137       return 0
   138     end
   139   end
   140   local function append_trailing_slash(str)
   141     return string.gsub(str, "([^/])$", function(last) return last .. "/" end)
   142   end
   143   WEBMCP_FRAMEWORK_PATH = append_trailing_slash(arg1)
   144   WEBMCP_BASE_PATH      = append_trailing_slash(arg2)
   145   WEBMCP_APP_NAME       = arg3
   146 end
   148 -- check if framework path is correct
   149 do
   150   local file, errmsg = io.open(WEBMCP_FRAMEWORK_PATH .. "webmcp_version", "r")
   151   if not file then
   152     error('Could not find "webmcp_version" file: ' .. errmsg, 0)
   153   end
   154   local version = assert(file:read())
   155   assert(file:close())
   156   if version ~= WEBMCP_VERSION then
   157     error('Version mismatch in file "' .. WEBMCP_FRAMEWORK_PATH .. 'webmcp_version"')
   158   end
   159 end
   161 -- setup search paths for libraries
   162 do
   163   if string.match(package.path, "^[^;]") then
   164     package.path = ";" .. package.path
   165   end
   166   package.path = WEBMCP_FRAMEWORK_PATH .. "lib/?.lua" .. package.path
   167   -- find out which file name extension shared libraries have
   168   local slib_exts = {}
   169   for ext in string.gmatch(package.cpath, "%?%.([A-Za-z0-9_-]+)") do
   170     if not slib_exts[ext] then
   171       slib_exts[#slib_exts+1] = ext
   172       slib_exts[ext] = true
   173     end
   174   end
   175   local paths = {}
   176   for i, ext in ipairs(slib_exts) do
   177     paths[#paths+1] = WEBMCP_FRAMEWORK_PATH .. "accelerator/?." .. ext
   178   end
   179   for i, ext in ipairs(slib_exts) do
   180     paths[#paths+1] = WEBMCP_FRAMEWORK_PATH .. "lib/?." .. ext
   181   end
   182   paths[#paths+1] = package.cpath
   183   package.cpath = table.concat(paths, ";")
   184 end
   186 -- autoloader system for WebMCP environment "$WEBMCP_FRAMEWORK_PATH/env/",
   187 -- application environment extensions "$WEBMCP_BASE_PATH/env/"
   188 -- and models "$WEBMCP_BASE_PATH/model/"
   189 do
   190   local weakkey_mt = { __mode = "k" }
   191   local autoloader_category = setmetatable({}, weakkey_mt)
   192   local autoloader_path     = setmetatable({}, weakkey_mt)
   193   local autoloader_mt       = {}
   194   local function install_autoloader(self, category, path_fragment)
   195     autoloader_category[self] = category
   196     autoloader_path[self]     = path_fragment
   197     setmetatable(self, autoloader_mt)
   198   end
   199   local function try_exec(filename)
   200     local func = loadcached(filename)
   201     if func then
   202       func()
   203       return true
   204     else
   205       return false
   206     end
   207   end
   208   function autoloader_mt.__index(self, key)
   209     local category, base_path, merge_base_path, file_key
   210     local merge = false
   211     if
   212       string.find(key, "^[a-z_][A-Za-z0-9_]*$") and
   213       not string.find(key, "^__")
   214     then
   215       category        = "env"
   216       base_path       = WEBMCP_FRAMEWORK_PATH .. "env/"
   217       merge           = true
   218       merge_base_path = WEBMCP_BASE_PATH .. "env/"
   219       file_key        = key
   220     elseif string.find(key, "^[A-Z][A-Za-z0-9]*$") then
   221       category        = "model"
   222       base_path       = WEBMCP_BASE_PATH .. "model/"
   223       local first = true
   224       file_key = string.gsub(key, "[A-Z]",
   225         function(c)
   226           if first then
   227             first = false
   228             return string.lower(c)
   229           else
   230             return "_" .. string.lower(c)
   231           end
   232         end
   233       )
   234     else
   235       return
   236     end
   237     local required_category = autoloader_category[self]
   238     if required_category and required_category ~= category then return end
   239     local path_fragment = autoloader_path[self]
   240     local path = base_path .. path_fragment .. file_key
   241     local merge_path
   242     if merge then
   243       merge_path = merge_base_path .. path_fragment .. file_key
   244     end
   245     local function try_dir(dirname)
   246       local dir = io.open(dirname)
   247       if dir then
   248         io.close(dir)
   249         local obj = {}
   250         install_autoloader(obj, category, path_fragment .. file_key .. "/")
   251         rawset(self, key, obj)
   252         try_exec(path .. "/__init.lua")
   253         if merge then try_exec(merge_path .. "/__init.lua") end
   254         return true
   255       else
   256         return false
   257       end
   258     end
   259     if self == _G then
   260       allowed_globals[key] = true
   261     end
   262     if merge and try_exec(merge_path .. ".lua") then
   263     elseif merge and try_dir(merge_path .. "/") then
   264     elseif try_exec(path .. ".lua") then
   265     elseif try_dir(path .. "/") then
   266     else end
   267     if self == _G then
   268       allowed_globals[key] = nil
   269     end
   270     return rawget(self, key)
   271   end
   272   install_autoloader(_G, nil, "")
   273   try_exec(WEBMCP_FRAMEWORK_PATH .. "env/__init.lua")
   274   try_exec(WEBMCP_BASE_PATH .. "env/__init.lua")
   275 end
   277 -- define post-fork initialization function (including loading of "multirand" library)
   278 local function postfork_init()
   279   multirand = require "multirand"
   280   execute.postfork_initializers()
   281 end
   283 --[[--
   284 listen{
   285   {
   286     proto     = proto,            -- "local", "tcp4", "tcp6", or "interval"
   287     path      = path,             -- path to unix domain socket if proto == "local"
   288     port      = port,             -- TCP port number
   289     localhost = localhost_only,   -- set to true to only listen on localhost (127.0.0.1 or ::1) interface
   290     name      = interval_name,    -- optional interval name (may be useful for log output)
   291     handler   = interval_handler  -- interval handler if proto == "interval"
   292   },
   293   {
   294     ...                           -- second listener
   295   },
   296   ...                             -- more listeners
   297   -- the following options are all optional and have default values:
   298   pre_fork         = pre_fork,          -- desired number of spare (idle) processes
   299   min_fork         = min_fork,          -- minimum number of processes
   300   max_fork         = max_fork,          -- maximum number of processes (hard limit)
   301   fork_delay       = fork_delay,        -- delay (seconds) between creation of spare processes
   302   fork_error_delay = fork_error_delay,  -- delay (seconds) before retry of failed process creation
   303   exit_delay       = exit_delay,        -- delay (seconds) between destruction of excessive spare processes
   304   idle_timeout     = idle_timeout,      -- idle time (seconds) after a fork gets terminated (0 for no timeout)
   305   memory_limit     = memory_limit,      -- maximum memory consumption (bytes) before process gets terminated
   306   min_requests_per_fork = min_requests_per_fork,  -- minimum count of requests handled before fork is terminated
   307   max_requests_per_fork = max_requests_per_fork,  -- maximum count of requests handled before fork is terminated
   308   http_options = {
   309     static_headers            = static_headers,             -- string or table of static headers to be returned with every request
   310     request_header_size_limit = request_header_size_limit,  -- maximum size of request headers sent by client
   311     request_body_size_limit   = request_body_size_limit,    -- maximum size of request body sent by client
   312     request_header_timeout    = request_header_timeout,     -- time after which request headers must have been received and processed
   313     timeout                   = timeout,                    -- time in which request body and response must be sent
   314     maximum_input_chunk_size  = maximum_input_chunk_size,   -- tweaks behavior of request-body parser
   315     minimum_output_chunk_size = minimum_output_chunk_size   -- chunk size for chunked-transfer-encoding
   316   }
   317 }
   319 The listen{...} function determines on which TCP port an application is answering requests. A typical call looks as follows:
   321 listen{
   322   { proto = "tcp4", port = 8080, localhost = true },
   323   { proto = "tcp6", port = 8080, localhost = true }
   324 }
   326 This function must be called in a configuration file (in the config/ directory) or in pre-fork initializers (in the app/_prefork/ or app/<application name>/_prefork/ directories), unless WebMCP is invoked in interactive mode (in which case any calls of listen{...} are ignored).
   328 This function is a variant of Moonbridge's listen{...} function which has been wrapped for WebMCP. No "prepare", "conenct", or "finish" handler can be set. Instead WebMCP automatically dispatches incoming connections. For interval timers, an interval handler may be specified in each listener.
   330 --]]--
   331 -- prepare for interactive or listen mode
   332 if WEBMCP_MODE == "interactive" then
   333   function listen()  -- overwrite Moonbridge's listen function
   334     -- ignore listen function calls for interactive mode
   335   end
   336   trace.disable()  -- avoids memory leakage when scripts are running endlessly
   337 else
   338   local moonbridge_listen = listen
   339   local http = require("moonbridge_http")
   340   function listen(args)  -- overwrite Moonbridge's listen function
   341     assert(args, "No argument passed to listen function")
   342     local min_requests_per_fork = args.min_requests_per_fork or 50
   343     local max_requests_per_fork = args.max_requests_per_fork or 200
   344     local interval_handlers = {}
   345     for j, listener in ipairs(args) do
   346       if listener.proto == "interval" then
   347         local name = listener.name or "Unnamed interval #" .. #interval_handlers+1
   348         if interval_handlers[name] ~= nil then
   349           error('Interval handler with duplicate name "' .. name .. '"')
   350         end
   351         interval_handlers[name] = listener.handler
   352         listener.name = name
   353       end
   354     end
   355     local request_count = 0
   356     local function inner_handler(http_request)
   357       request_count = request_count + 1
   358       if request_count >= max_requests_per_fork then
   359         http_request:close_after_finish()
   360       end
   361       request.initialize()
   362       return request.handler(http_request)
   363     end
   364     local outer_handler = http.generate_handler(inner_handler, args.http_options)
   365     args.prepare = postfork_init
   366     args.connect = function(socket)
   367       if socket.interval then
   368         request_count = request_count + 1
   369         request.initialize()
   370         interval_handlers[socket.interval]()
   371       else
   372         local success = outer_handler(socket)
   373         if not success then
   374           return false
   375         end
   376       end
   377       return request_count < min_requests_per_fork
   378     end
   379     args.finish = execute.finalizers
   380     moonbridge_listen(args)
   381   end
   382 end
   383 --//--
   385 -- execute configurations and pre-fork initializers
   386 for i, config_name in ipairs(WEBMCP_CONFIG_NAMES) do
   387   execute.config(config_name)
   388 end
   389 execute.prefork_initializers()
   391 -- perform post-fork initializations (once) in case of interactive mode
   392 if WEBMCP_MODE == "interactive" then
   393   postfork_init()
   394 end
