1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
|
--- 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()
|