PageRenderTime 48ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/liquidfeedback/webmcp/framework/bin/mcp.lua

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