--- Programmable HTTP server used by the suite, run as a separate process. -- -- Lua has no threads, and the client blocks on its socket, so the server -- cannot share this interpreter with the code under test. It is spawned once -- per run, binds a free loopback port and writes it to the file named in -- `arg[1]`, then answers until it is told to stop or nothing has connected for -- a while. -- -- Three control routes drive it from the test process: -- -- POST /__program replace the routes and clear the call log -- GET /__calls the request targets seen so far, as a JSON array -- GET /__stop shut down -- -- A route maps a path (query string excluded) to `{ responses = { ... } }`. -- Each request consumes the next response; the last one repeats once the list -- runs out, which is how retrying is tested. A route may instead set -- `csv_from_query`, and the server then generates one CSV row per CEP in the -- query — that keeps the 501-CEP chunking test from shipping a body through -- the control channel. local socket = require("socket") local json = require("dkjson") local IDLE_TIMEOUT = 60 local REASON = { [200] = "OK", [400] = "Bad Request", [404] = "Not Found", [429] = "Too Many Requests", [500] = "Internal Server Error", [502] = "Bad Gateway", [503] = "Service Unavailable", } local CSV_HEADER = "cep,logradouro,complemento,unidade,bairro,localidade,uf,estado,regiao,ibge,gia,ddd,siafi" local routes = {} local cursor = {} local calls = {} local running = true local function unescape(value) return (value:gsub("%%(%x%x)", function(hex) return string.char(tonumber(hex, 16)) end)) end local function read_request(client) local line = client:receive("*l") if not line then return nil end local method, target = line:match("^(%u+)%s+(%S+)") if not method then return nil end local headers = {} while true do local header = client:receive("*l") if not header or header == "" then break end local name, value = header:match("^([^:]+):%s*(.*)$") if name then headers[name:lower()] = value end end local body = "" local length = tonumber(headers["content-length"] or "0") or 0 if length > 0 then body = client:receive(length) or "" end return method, target, body end local function send(client, status, body, headers) local out = { string.format("HTTP/1.1 %d %s\r\n", status, REASON[status] or "Status"), } for name, value in pairs(headers or {}) do out[#out + 1] = name .. ": " .. tostring(value) .. "\r\n" end out[#out + 1] = "Content-Length: " .. #body .. "\r\n" out[#out + 1] = "Connection: close\r\n\r\n" out[#out + 1] = body client:send(table.concat(out)) end local function csv_for(target) local raw = target:match("[?&]ceps=([^&]*)") or "" local rows = { CSV_HEADER } for cep in unescape(raw):gmatch("[^,]+") do rows[#rows + 1] = cep .. ",,,,,X,SP,São Paulo,Sudeste,1,,11,1" end return table.concat(rows, "\n") .. "\n" end local function next_response(path) local route = routes[path] if not route then return nil end local responses = route.responses if not responses or #responses == 0 then return route end local index = cursor[path] or 1 if index < #responses then cursor[path] = index + 1 end return responses[index] end local function serve(client, target, body) local path = target:match("^([^?]*)") if path == "/__program" then routes = json.decode(body) or {} cursor = {} calls = {} send(client, 200, "{}") return end if path == "/__calls" then send(client, 200, json.encode(calls)) return end if path == "/__stop" then running = false send(client, 200, "{}") return end calls[#calls + 1] = target local response = next_response(path) if not response then send(client, 404, '{"erro":"cep_nao_encontrado","mensagem":"CEP nao existe"}') return end if response.csv_from_query then send(client, response.status or 200, csv_for(target), response.headers) return end send(client, response.status or 200, response.body or "", response.headers) end local server = assert(socket.bind("127.0.0.1", 0)) local _, port = server:getsockname() server:settimeout(IDLE_TIMEOUT) local handle = assert(io.open(arg[1], "w")) handle:write(tostring(port)) handle:close() while running do local client = server:accept() if not client then break end client:settimeout(10) local _, target, body = read_request(client) if target then serve(client, target, body) end client:close() end server:close()