mirror of
https://github.com/l5yth/potato-mesh.git
synced 2026-07-06 09:52:02 +02:00
Gracefully retry federation announcements over HTTP (#355)
This commit is contained in:
@@ -156,6 +156,8 @@ module PotatoMesh
|
|||||||
def announce_instance_to_domain(domain, payload_json)
|
def announce_instance_to_domain(domain, payload_json)
|
||||||
return false unless domain && !domain.empty?
|
return false unless domain && !domain.empty?
|
||||||
|
|
||||||
|
https_failures = []
|
||||||
|
|
||||||
instance_uri_candidates(domain, "/api/instances").each do |uri|
|
instance_uri_candidates(domain, "/api/instances").each do |uri|
|
||||||
begin
|
begin
|
||||||
http = build_remote_http_client(uri)
|
http = build_remote_http_client(uri)
|
||||||
@@ -181,16 +183,51 @@ module PotatoMesh
|
|||||||
status: response.code,
|
status: response.code,
|
||||||
)
|
)
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
warn_log(
|
metadata = {
|
||||||
"Federation announcement raised exception",
|
|
||||||
context: "federation.announce",
|
context: "federation.announce",
|
||||||
target: uri.to_s,
|
target: uri.to_s,
|
||||||
error_class: e.class.name,
|
error_class: e.class.name,
|
||||||
error_message: e.message,
|
error_message: e.message,
|
||||||
|
}
|
||||||
|
|
||||||
|
if uri.scheme == "https" && https_connection_refused?(e)
|
||||||
|
debug_log(
|
||||||
|
"HTTPS federation announcement failed, retrying with HTTP",
|
||||||
|
**metadata,
|
||||||
|
)
|
||||||
|
https_failures << metadata
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
warn_log(
|
||||||
|
"Federation announcement raised exception",
|
||||||
|
**metadata,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
https_failures.each do |metadata|
|
||||||
|
warn_log(
|
||||||
|
"Federation announcement raised exception",
|
||||||
|
**metadata,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
# Determine whether an HTTPS announcement failure should fall back to HTTP.
|
||||||
|
#
|
||||||
|
# @param error [StandardError] failure raised while attempting HTTPS.
|
||||||
|
# @return [Boolean] true when the error corresponds to a refused TCP connection.
|
||||||
|
def https_connection_refused?(error)
|
||||||
|
current = error
|
||||||
|
while current
|
||||||
|
return true if current.is_a?(Errno::ECONNREFUSED)
|
||||||
|
|
||||||
|
current = current.respond_to?(:cause) ? current.cause : nil
|
||||||
|
end
|
||||||
|
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,18 @@ RSpec.describe PotatoMesh::App::Federation do
|
|||||||
def reset_debug_messages
|
def reset_debug_messages
|
||||||
@debug_messages = []
|
@debug_messages = []
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def warn_messages
|
||||||
|
@warn_messages ||= []
|
||||||
|
end
|
||||||
|
|
||||||
|
def warn_log(message, **_metadata)
|
||||||
|
warn_messages << message
|
||||||
|
end
|
||||||
|
|
||||||
|
def reset_warn_messages
|
||||||
|
@warn_messages = []
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -42,6 +54,7 @@ RSpec.describe PotatoMesh::App::Federation do
|
|||||||
federation_helpers.instance_variable_set(:@remote_instance_cert_store, nil)
|
federation_helpers.instance_variable_set(:@remote_instance_cert_store, nil)
|
||||||
federation_helpers.instance_variable_set(:@remote_instance_verify_callback, nil)
|
federation_helpers.instance_variable_set(:@remote_instance_verify_callback, nil)
|
||||||
federation_helpers.reset_debug_messages
|
federation_helpers.reset_debug_messages
|
||||||
|
federation_helpers.reset_warn_messages
|
||||||
end
|
end
|
||||||
|
|
||||||
describe ".remote_instance_cert_store" do
|
describe ".remote_instance_cert_store" do
|
||||||
@@ -208,4 +221,53 @@ RSpec.describe PotatoMesh::App::Federation do
|
|||||||
end.to raise_error(PotatoMesh::App::InstanceFetchError, "Net::ReadTimeout")
|
end.to raise_error(PotatoMesh::App::InstanceFetchError, "Net::ReadTimeout")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe ".announce_instance_to_domain" do
|
||||||
|
let(:payload) { "{}" }
|
||||||
|
let(:https_uri) { URI.parse("https://remote.mesh/api/instances") }
|
||||||
|
let(:http_uri) { URI.parse("http://remote.mesh/api/instances") }
|
||||||
|
let(:http_connection) { instance_double("Net::HTTPConnection") }
|
||||||
|
let(:success_response) { Net::HTTPOK.new("1.1", "200", "OK") }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(success_response).to receive(:code).and_return("200")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "retries over HTTP when HTTPS connections are refused" do
|
||||||
|
https_client = instance_double(Net::HTTP)
|
||||||
|
http_client = instance_double(Net::HTTP)
|
||||||
|
|
||||||
|
allow(federation_helpers).to receive(:build_remote_http_client).with(https_uri).and_return(https_client)
|
||||||
|
allow(federation_helpers).to receive(:build_remote_http_client).with(http_uri).and_return(http_client)
|
||||||
|
|
||||||
|
allow(https_client).to receive(:start).and_raise(Errno::ECONNREFUSED.new("refused"))
|
||||||
|
allow(http_connection).to receive(:request).and_return(success_response)
|
||||||
|
allow(http_client).to receive(:start).and_yield(http_connection).and_return(success_response)
|
||||||
|
|
||||||
|
result = federation_helpers.announce_instance_to_domain("remote.mesh", payload)
|
||||||
|
|
||||||
|
expect(result).to be(true)
|
||||||
|
expect(federation_helpers.debug_messages).to include("HTTPS federation announcement failed, retrying with HTTP")
|
||||||
|
expect(federation_helpers.warn_messages).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it "logs a warning when HTTPS refusal persists after HTTP fallback" do
|
||||||
|
https_client = instance_double(Net::HTTP)
|
||||||
|
http_client = instance_double(Net::HTTP)
|
||||||
|
|
||||||
|
allow(federation_helpers).to receive(:build_remote_http_client).with(https_uri).and_return(https_client)
|
||||||
|
allow(federation_helpers).to receive(:build_remote_http_client).with(http_uri).and_return(http_client)
|
||||||
|
|
||||||
|
allow(https_client).to receive(:start).and_raise(Errno::ECONNREFUSED.new("refused"))
|
||||||
|
allow(http_client).to receive(:start).and_raise(SocketError.new("dns failure"))
|
||||||
|
|
||||||
|
result = federation_helpers.announce_instance_to_domain("remote.mesh", payload)
|
||||||
|
|
||||||
|
expect(result).to be(false)
|
||||||
|
expect(federation_helpers.debug_messages).to include("HTTPS federation announcement failed, retrying with HTTP")
|
||||||
|
expect(
|
||||||
|
federation_helpers.warn_messages.count { |message| message.include?("Federation announcement raised exception") },
|
||||||
|
).to eq(2)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user