webmcp

annotate framework/bin/mcp.lua @ 338:3687294cb955

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

Impressum / About Us