76 lines
2.9 KiB
Lua
76 lines
2.9 KiB
Lua
local records_key = KEYS[1]
|
|
local owners_key = KEYS[2]
|
|
local worker_owned_key = KEYS[3]
|
|
|
|
local worker_id = ARGV[1]
|
|
local limit = tonumber(ARGV[2])
|
|
|
|
local function reply(status, proxies)
|
|
if not proxies or #proxies == 0 then
|
|
return '{"status":' .. cjson.encode(status) .. ',"proxies":[]}'
|
|
end
|
|
return cjson.encode({status = status, proxies = proxies})
|
|
end
|
|
|
|
local function now_ms()
|
|
local value = redis.call('TIME')
|
|
return tonumber(value[1]) * 1000 + math.floor(tonumber(value[2]) / 1000)
|
|
end
|
|
|
|
local function decode_table(value)
|
|
if not value then
|
|
return nil
|
|
end
|
|
local ok, decoded = pcall(cjson.decode, value)
|
|
if not ok or type(decoded) ~= 'table' then
|
|
return nil
|
|
end
|
|
return decoded
|
|
end
|
|
|
|
local function valid_assignment(value, now)
|
|
return value and value.version == 1 and type(value.proxyId) == 'string' and value.proxyId ~= '' and
|
|
type(value.workerId) == 'string' and value.workerId == worker_id and type(value.epoch) == 'number' and
|
|
value.epoch > 0 and type(value.expiresAtMs) == 'number' and value.expiresAtMs > now and
|
|
type(value.draining) == 'boolean' and not value.draining and
|
|
type(value.workerIndexKey) == 'string' and value.workerIndexKey == worker_owned_key
|
|
end
|
|
|
|
local function valid_record(value, proxy_id, now)
|
|
return value and value.version == 1 and value.id == proxy_id and value.state == 'AVAILABLE' and
|
|
value.ownerWorkerId == worker_id and type(value.scheme) == 'string' and
|
|
(value.scheme == 'http' or value.scheme == 'https' or value.scheme == 'socks5') and
|
|
type(value.host) == 'string' and value.host ~= '' and type(value.port) == 'number' and
|
|
value.port > 0 and value.port <= 65535 and type(value.sourceUpstream) == 'string' and
|
|
value.sourceUpstream ~= '' and type(value.expiresAtMs) == 'number' and value.expiresAtMs > now and
|
|
type(value.usableUntilMs) == 'number' and value.usableUntilMs > now and
|
|
type(value.maxConcurrency) == 'number' and value.maxConcurrency > 0
|
|
end
|
|
|
|
if type(worker_id) ~= 'string' or worker_id == '' or not limit or limit <= 0 then
|
|
return reply('invalid')
|
|
end
|
|
|
|
if redis.call('ZCARD', worker_owned_key) > limit then
|
|
return reply('unavailable')
|
|
end
|
|
|
|
local now = now_ms()
|
|
local ids = redis.call('ZRANGEBYSCORE', worker_owned_key, now + 1, '+inf', 'LIMIT', 0, limit)
|
|
local proxies = cjson.decode('[]')
|
|
for _, proxy_id in ipairs(ids) do
|
|
local assignment = decode_table(redis.call('HGET', owners_key, proxy_id))
|
|
local raw_record = redis.call('HGET', records_key, proxy_id)
|
|
local record = decode_table(raw_record)
|
|
if valid_assignment(assignment, now) and valid_record(record, proxy_id, now) then
|
|
proxies[#proxies + 1] = {
|
|
record = raw_record,
|
|
ownershipEpoch = tostring(assignment.epoch),
|
|
leaseExpiresAtMs = assignment.expiresAtMs,
|
|
}
|
|
else
|
|
redis.call('ZREM', worker_owned_key, proxy_id)
|
|
end
|
|
end
|
|
return reply('ok', proxies)
|