webmcp

annotate framework/bin/mcp.lua @ 366:4dbf4374f24a

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

Impressum / About Us