webmcp

view 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
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

Impressum / About Us