jbe@0: #!/usr/bin/env lua jbe@0: jbe@0: -- module preamble jbe@0: local _G, _M = _ENV, {} jbe@0: _ENV = setmetatable({}, { jbe@0: __index = function(self, key) jbe@0: local value = _M[key]; if value ~= nil then return value end jbe@0: return _G[key] jbe@0: end, jbe@63: __newindex = _M jbe@0: }) jbe@0: jbe@0: -- function that encodes certain HTML entities: jbe@0: -- (not used by the library itself) jbe@0: function encode_html(text) jbe@0: return ( jbe@0: string.gsub( jbe@0: text, '[<>&"]', jbe@0: function(char) jbe@0: if char == '<' then jbe@0: return "<" jbe@0: elseif char == '>' then jbe@0: return ">" jbe@0: elseif char == '&' then jbe@0: return "&" jbe@0: elseif char == '"' then jbe@0: return """ jbe@0: end jbe@0: end jbe@0: ) jbe@0: ) jbe@0: jbe@0: end jbe@0: jbe@0: -- function that encodes special characters for URIs: jbe@0: -- (not used by the library itself) jbe@0: function encode_uri(text) jbe@0: return ( jbe@0: string.gsub(text, "[^0-9A-Za-z_%.~-]", jbe@0: function (char) jbe@0: return string.format("%%%02x", string.byte(char)) jbe@0: end jbe@0: ) jbe@0: ) jbe@0: end jbe@0: jbe@0: -- function undoing URL encoding: jbe@0: do jbe@0: local b0 = string.byte("0") jbe@0: local b9 = string.byte("9") jbe@0: local bA = string.byte("A") jbe@0: local bF = string.byte("F") jbe@0: local ba = string.byte("a") jbe@0: local bf = string.byte("f") jbe@0: function decode_uri(str) jbe@0: return ( jbe@0: string.gsub( jbe@0: string.gsub(str, "%+", " "), jbe@0: "%%([0-9A-Fa-f][0-9A-Fa-f])", jbe@0: function(hex) jbe@0: local n1, n2 = string.byte(hex, 1, 2) jbe@0: if n1 >= b0 and n1 <= b9 then n1 = n1 - b0 jbe@0: elseif n1 >= bA and n1 <= bF then n1 = n1 - bA + 10 jbe@0: elseif n1 >= ba and n1 <= bf then n1 = n1 - ba + 10 jbe@0: else error("Assertion failed") end jbe@0: if n2 >= b0 and n2 <= b9 then n2 = n2 - b0 jbe@0: elseif n2 >= bA and n2 <= bF then n2 = n2 - bA + 10 jbe@0: elseif n2 >= ba and n2 <= bf then n2 = n2 - ba + 10 jbe@0: else error("Assertion failed") end jbe@0: return string.char(n1 * 16 + n2) jbe@0: end jbe@0: ) jbe@0: ) jbe@0: end jbe@0: end jbe@0: jbe@0: -- status codes that carry no response body (in addition to 1xx): jbe@0: -- (set to "zero_content_length" if Content-Length header is required) jbe@0: status_without_response_body = { jbe@5: ["101"] = true, -- list 101 to allow protocol switch jbe@0: ["204"] = true, jbe@0: ["205"] = "zero_content_length", jbe@0: ["304"] = true jbe@0: } jbe@0: jbe@154: -- parses URL encoded form data: jbe@154: local function read_urlencoded_form(data) jbe@154: local tbl = {} jbe@154: for rawkey, rawvalue in string.gmatch(data, "([^?=&]*)=([^?=&]*)") do jbe@154: local key = decode_uri(rawkey) jbe@154: local value = decode_uri(rawvalue) jbe@154: local subtbl = tbl[key] jbe@154: if subtbl then jbe@154: subtbl[#subtbl+1] = value jbe@154: else jbe@154: tbl[key] = {value} jbe@35: end jbe@35: end jbe@154: return tbl jbe@0: end jbe@0: jbe@154: -- extracts first value from each subtable: jbe@154: local function get_first_values(tbl) jbe@154: local newtbl = {} jbe@154: for key, subtbl in pairs(tbl) do jbe@154: newtbl[key] = subtbl[1] jbe@0: end jbe@154: return newtbl jbe@154: end jbe@154: jbe@160: function generate_handler(handler, options) jbe@160: -- swap arguments if necessary (for convenience): jbe@160: if type(handler) ~= "function" and type(options) == "function" then jbe@160: handler, options = options, handler jbe@160: end jbe@160: -- helper function to process options: jbe@160: local function default(name, default_value) jbe@160: local value = options[name] jbe@160: if value == nil then jbe@160: return default_value jbe@160: else jbe@160: return value or nil jbe@159: end jbe@160: end jbe@0: -- process options: jbe@0: options = options or {} jbe@160: local preamble = "" -- preamble sent with every(!) HTTP response jbe@0: do jbe@0: -- named arg "static_headers" is used to create the preamble: jbe@0: local s = options.static_headers jbe@0: local t = {} jbe@0: if s then jbe@0: if type(s) == "string" then jbe@0: for line in string.gmatch(s, "[^\r\n]+") do jbe@0: t[#t+1] = line jbe@0: end jbe@0: else jbe@0: for i, kv in ipairs(options.static_headers) do jbe@0: if type(kv) == "string" then jbe@0: t[#t+1] = kv jbe@0: else jbe@0: t[#t+1] = kv[1] .. ": " .. kv[2] jbe@0: end jbe@0: end jbe@0: end jbe@0: end jbe@0: t[#t+1] = "" jbe@160: preamble = table.concat(t, "\r\n") jbe@159: end jbe@160: local input_chunk_size = options.maximum_input_chunk_size or options.chunk_size or 16384 jbe@160: local output_chunk_size = options.minimum_output_chunk_size or options.chunk_size or 1024 jbe@160: local header_size_limit = options.header_size_limit or 1024*1024 jbe@160: local body_size_limit = options.body_size_limit or 64*1024*1024 jbe@160: local request_idle_timeout = default("request_idle_timeout", 330) jbe@160: local request_header_timeout = default("request_header_timeout", 30) jbe@160: local request_body_timeout = default("request_body_timeout", 60) jbe@160: local request_response_timeout = default("request_response_timeout", 1800) jbe@160: local poll = options.poll_function or moonbridge_io.poll jbe@160: -- return socket handler: jbe@160: return function(socket) jbe@160: local socket_set = {[socket] = true} -- used for poll function jbe@160: local survive = true -- set to false if process shall be terminated later jbe@160: local consume -- function that reads some input if possible jbe@160: -- function that drains some input if possible: jbe@160: local function drain() jbe@160: local bytes, status = assert(socket:drain_nb(input_chunk_size)) jbe@160: if status == "eof" then jbe@160: consume = nil jbe@159: end jbe@159: end jbe@160: local function unblock() jbe@160: if consume then jbe@160: poll(socket_set, socket_set) jbe@160: consume() jbe@160: else jbe@160: poll(nil, socket_set) jbe@38: end jbe@154: end jbe@160: repeat jbe@160: local request = { jbe@160: socket = socket, jbe@160: cookies = {} jbe@160: } jbe@160: local function send(...) jbe@160: assert(socket:write_call(unblock, ...)) jbe@156: end jbe@160: -- wait for input: jbe@160: if not poll(socket_set, nil, request_idle_timeout) then jbe@160: -- TODO: send error jbe@160: return survive jbe@156: end jbe@160: until connection_close_responded jbe@160: return survive jbe@156: end jbe@154: end jbe@154: jbe@0: return _M jbe@0: