mirror of
https://github.com/status-im/eth-rpc-proxy.git
synced 2026-08-30 19:21:09 +00:00
* extract filter providers function * use standard way to remove hop‑by‑hop headers * keep decoded body * fix providers reload scheduler
176 lines
5.9 KiB
Lua
176 lines
5.9 KiB
Lua
local json = require("cjson")
|
|
local http = require("resty.http")
|
|
local cache = require("cache.cache")
|
|
|
|
-- 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
|
|
|
|
-- Filter providers by provider_type if specified
|
|
local function filter_providers(providers, provider_type)
|
|
local providers_to_try = {}
|
|
local tried_specific_provider = false
|
|
|
|
if provider_type and provider_type ~= "" then
|
|
for _, provider in ipairs(providers) do
|
|
if provider.type == provider_type then
|
|
table.insert(providers_to_try, provider)
|
|
end
|
|
end
|
|
tried_specific_provider = true
|
|
else
|
|
providers_to_try = providers
|
|
end`
|
|
|
|
return providers_to_try, tried_specific_provider
|
|
end
|
|
|
|
-- Read request body once and reuse it throughout the handler
|
|
ngx.req.read_body()
|
|
local body_data = ngx.req.get_body_data() or ""
|
|
|
|
-- 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 "")
|
|
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 "")
|
|
|
|
-- 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
|
|
|
|
-- 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
|
|
|
|
-- 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
|
|
end
|
|
|
|
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
|
|
|
|
-- Filter providers by provider_type if specified
|
|
local providers_to_try, tried_specific_provider = filter_providers(providers, provider_type)
|
|
|
|
if #providers_to_try == 0 then
|
|
ngx.log(ngx.ERR, "No providers found for provider_type: ", provider_type or "none")
|
|
ngx.status = 404
|
|
ngx.say("No providers available for this provider type")
|
|
return
|
|
end
|
|
|
|
local success = false
|
|
|
|
for _, provider in ipairs(providers_to_try) do
|
|
ngx.log(ngx.INFO, "provider: ", provider.url)
|
|
local httpc = http.new()
|
|
|
|
-- Handle authentication based on provider config
|
|
local request_url = provider.url
|
|
local request_headers = {
|
|
["Content-Type"] = "application/json"
|
|
}
|
|
|
|
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
|
|
-- Cache response if cacheable
|
|
if cache_info.cache_type then
|
|
cache.save_to_cache(cache_info, res.body)
|
|
end
|
|
|
|
-- Success! Use proxy_response to handle headers and body automatically
|
|
httpc:proxy_response(res)
|
|
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)
|
|
end
|
|
end
|
|
|
|
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
|
|
end |