2025-02-18 17:39:10 +04:00
|
|
|
local json = require("cjson")
|
|
|
|
|
local http = require("resty.http")
|
2025-07-17 15:40:46 +04:00
|
|
|
local cache = require("cache.cache")
|
2025-02-18 17:39:10 +04:00
|
|
|
|
2025-06-26 21:59:37 +04:00
|
|
|
-- HTTP status codes that should trigger retry
|
|
|
|
|
local retry_status = {
|
|
|
|
|
[401] = true, [402] = true, [403] = true, [429] = true,
|
|
|
|
|
[500] = true, [501] = true, [502] = true, [503] = true, [504] = true, [505] = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
-- JSON-RPC error codes that should trigger retry
|
|
|
|
|
local retry_evm = {
|
|
|
|
|
[32005] = true, -- Request timeout
|
|
|
|
|
[33000] = true, -- Rate limit exceeded
|
|
|
|
|
[33300] = true, -- Network error
|
|
|
|
|
[33400] = true -- Provider error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
-- Check if we should retry with next provider
|
|
|
|
|
local function should_retry(res, decoded)
|
|
|
|
|
if not res then return true end
|
|
|
|
|
if retry_status[res.status] then return true end
|
|
|
|
|
if decoded and decoded.error and retry_evm[decoded.error.code] then return true end
|
|
|
|
|
return false
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Read request body once and reuse it throughout the handler
|
|
|
|
|
ngx.req.read_body()
|
|
|
|
|
local body_data = ngx.req.get_body_data() or ""
|
|
|
|
|
|
2025-02-18 17:39:10 +04:00
|
|
|
-- Extract and validate path parameters
|
|
|
|
|
local chain, network, provider_type = ngx.var.uri:match("^/([^/]+)/([^/]+)/?([^/]*)$")
|
|
|
|
|
if not chain or not network then
|
|
|
|
|
ngx.log(ngx.ERR, "Invalid URL format - must be /chain/network or /chain/network/provider_type")
|
|
|
|
|
ngx.status = 400
|
|
|
|
|
ngx.say("Invalid URL format - must be /chain/network or /chain/network/provider_type")
|
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
ngx.log(ngx.INFO, "Chain: ", chain, " Network: ", network, provider_type and (" Provider: " .. provider_type) or "")
|
2025-06-26 21:59:37 +04:00
|
|
|
local sanitized_body_data = body_data:sub(1, 100)
|
|
|
|
|
ngx.log(ngx.INFO, "Request body (truncated): ", sanitized_body_data, body_data:len() > 100 and "..." or "")
|
2025-02-18 17:39:10 +04:00
|
|
|
|
|
|
|
|
-- Get providers for the requested chain/network
|
|
|
|
|
local chain_network_key = chain .. ":" .. network
|
|
|
|
|
local providers_str = ngx.shared.providers:get(chain_network_key)
|
|
|
|
|
|
|
|
|
|
if not providers_str then
|
|
|
|
|
ngx.log(ngx.ERR, "No providers found for ", chain_network_key)
|
|
|
|
|
ngx.status = 404
|
|
|
|
|
ngx.say("No providers available for this chain/network")
|
|
|
|
|
return
|
|
|
|
|
end
|
2025-06-26 21:59:37 +04:00
|
|
|
|
|
|
|
|
-- Safely decode providers JSON with error handling
|
|
|
|
|
local ok, providers = pcall(json.decode, providers_str)
|
|
|
|
|
if not ok then
|
|
|
|
|
ngx.log(ngx.ERR, "Invalid providers JSON for ", chain_network_key, ": ", providers)
|
|
|
|
|
ngx.status = 500
|
|
|
|
|
ngx.say("Internal server error: invalid providers configuration")
|
|
|
|
|
return
|
|
|
|
|
end
|
2025-02-18 17:39:10 +04:00
|
|
|
|
2025-06-26 16:41:44 +04:00
|
|
|
-- Check cache with unified function (handles all cache operations)
|
|
|
|
|
local cache_info = cache.check_cache(chain, network, body_data)
|
|
|
|
|
if cache_info.cached_response then
|
|
|
|
|
ngx.header["Content-Type"] = "application/json"
|
|
|
|
|
ngx.say(cache_info.cached_response)
|
|
|
|
|
return
|
2025-06-25 21:26:02 +04:00
|
|
|
end
|
|
|
|
|
|
2025-02-18 17:39:10 +04:00
|
|
|
if #providers == 0 then
|
|
|
|
|
ngx.log(ngx.ERR, "No providers found for ", chain_network_key)
|
|
|
|
|
ngx.status = 404
|
|
|
|
|
ngx.say("No providers available for this chain/network")
|
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local tried_specific_provider = false
|
2025-06-27 14:37:03 +04:00
|
|
|
local success = false
|
2025-06-26 21:59:37 +04:00
|
|
|
|
2025-02-18 17:39:10 +04:00
|
|
|
for _, provider in ipairs(providers) do
|
2025-06-27 14:37:03 +04:00
|
|
|
local skip_provider = false
|
|
|
|
|
|
2025-02-18 17:39:10 +04:00
|
|
|
-- Skip providers that don't match requested provider_type
|
|
|
|
|
if provider_type and provider_type ~= "" then
|
|
|
|
|
if provider.type ~= provider_type then
|
2025-06-27 14:37:03 +04:00
|
|
|
skip_provider = true
|
2025-02-18 17:39:10 +04:00
|
|
|
else
|
2025-06-27 14:37:03 +04:00
|
|
|
tried_specific_provider = true
|
2025-02-18 17:39:10 +04:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2025-06-27 14:37:03 +04:00
|
|
|
if not skip_provider then
|
|
|
|
|
ngx.log(ngx.INFO, "provider: ", provider.url)
|
|
|
|
|
local httpc = http.new()
|
2025-02-18 17:39:10 +04:00
|
|
|
|
2025-06-27 14:37:03 +04:00
|
|
|
-- Handle authentication based on provider config
|
|
|
|
|
local request_url = provider.url
|
|
|
|
|
local request_headers = {
|
|
|
|
|
["Content-Type"] = "application/json"
|
|
|
|
|
}
|
2025-02-18 17:39:10 +04:00
|
|
|
|
2025-06-27 14:37:03 +04:00
|
|
|
if provider.authType == "token-auth" and provider.authToken then
|
|
|
|
|
if request_url:sub(-1) == "/" then
|
|
|
|
|
request_url = request_url .. provider.authToken
|
|
|
|
|
else
|
|
|
|
|
request_url = request_url .. "/" .. provider.authToken
|
|
|
|
|
end
|
|
|
|
|
elseif provider.authType == "basic-auth" and provider.authLogin and provider.authPassword then
|
|
|
|
|
local auth_str = ngx.encode_base64(provider.authLogin .. ":" .. provider.authPassword)
|
|
|
|
|
request_headers["Authorization"] = "Basic " .. auth_str
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local res, err = httpc:request_uri(request_url, {
|
|
|
|
|
method = ngx.req.get_method(),
|
|
|
|
|
body = body_data,
|
|
|
|
|
headers = request_headers,
|
|
|
|
|
ssl_verify = false,
|
|
|
|
|
options = { family = ngx.AF_INET }
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if res then
|
|
|
|
|
ngx.log(ngx.DEBUG, "Response body: ", res.body)
|
|
|
|
|
|
|
|
|
|
local ok, decoded_response = pcall(json.decode, res.body)
|
|
|
|
|
local decoded = ok and decoded_response or nil
|
|
|
|
|
|
|
|
|
|
-- Check if we should retry with next provider
|
|
|
|
|
if not should_retry(res, decoded) then
|
|
|
|
|
-- Set Content-Type header
|
|
|
|
|
if res.headers["Content-Type"] then
|
|
|
|
|
ngx.header["Content-Type"] = res.headers["Content-Type"]
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Set Content-Length header if present
|
|
|
|
|
if res.headers["Content-Length"] then
|
|
|
|
|
ngx.header["Content-Length"] = res.headers["Content-Length"]
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Set Vary header if present
|
|
|
|
|
if res.headers["Vary"] then
|
|
|
|
|
ngx.header["Vary"] = res.headers["Vary"]
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Cache response if cacheable
|
|
|
|
|
if cache_info.cache_type then
|
|
|
|
|
cache.save_to_cache(cache_info, res.body)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Success! Return response and exit
|
|
|
|
|
ngx.say(res.body)
|
|
|
|
|
success = true
|
|
|
|
|
break
|
|
|
|
|
else
|
|
|
|
|
if res.status then
|
|
|
|
|
ngx.log(ngx.ERR, "Error status ", res.status, ", trying next provider")
|
|
|
|
|
elseif decoded and decoded.error then
|
|
|
|
|
ngx.log(ngx.ERR, "JSON-RPC error code ", decoded.error.code, ", trying next provider")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
ngx.log(ngx.ERR, "HTTP request failed: ", err)
|
2025-06-26 21:59:37 +04:00
|
|
|
end
|
|
|
|
|
end
|
2025-02-18 17:39:10 +04:00
|
|
|
end
|
|
|
|
|
|
2025-06-27 14:37:03 +04:00
|
|
|
if not success then
|
|
|
|
|
if provider_type and provider_type ~= "" and not tried_specific_provider then
|
|
|
|
|
ngx.status = 404
|
|
|
|
|
ngx.say("Provider not found: " .. provider_type)
|
|
|
|
|
else
|
|
|
|
|
ngx.status = 502
|
|
|
|
|
ngx.say("All providers failed")
|
|
|
|
|
end
|
2025-02-20 17:21:04 +04:00
|
|
|
end
|