42 static std::optional<SnapshotResponse> fetch_from_peer(
43 const std::string& peer_address,
44 const std::string& path_to_peer_cert,
45 size_t latest_local_snapshot)
51 std::string snapshot_url;
54 curl_easy.
set_opt(CURLOPT_CAINFO, path_to_peer_cert.c_str());
56 auto initial_url = fmt::format(
57 "https://{}/node/snapshot?since={}",
59 latest_local_snapshot);
66 std::move(initial_url),
74 CURLcode curl_response = CURLE_OK;
75 request.synchronous_perform(curl_response, status_code);
76 if (curl_response != CURLE_OK)
78 throw std::runtime_error(fmt::format(
79 "Error fetching snapshot redirect from {}: {} ({})",
81 curl_easy_strerror(curl_response),
84 if (status_code == HTTP_STATUS_NOT_FOUND)
87 "Peer has no snapshot newer than {}", latest_local_snapshot);
91 request, status_code, HTTP_STATUS_PERMANENT_REDIRECT);
93 auto& response_headers = request.get_response_headers();
95 response_headers.data.find(ccf::http::headers::LOCATION);
96 if (location_it == response_headers.data.end())
98 throw std::runtime_error(fmt::format(
99 "Expected {} header in redirect response from {} {}, none found",
100 ccf::http::headers::LOCATION,
101 request.get_method().c_str(),
105 LOG_TRACE_FMT(
"Snapshot fetch redirected to {}", location_it->second);
108 fmt::format(
"https://{}{}", peer_address, location_it->second);
112 size_t content_size = 0;
115 curl_easy.
set_opt(CURLOPT_CAINFO, path_to_peer_cert.c_str());
119 std::string current_snapshot_url = snapshot_url;
122 std::move(curl_easy),
124 std::move(current_snapshot_url),
131 CURLcode snapshot_size_curl_code = CURLE_OK;
132 long snapshot_size_status_code = 0;
134 snapshot_size_curl_code, snapshot_size_status_code);
136 if (snapshot_size_curl_code != CURLE_OK)
138 throw std::runtime_error(fmt::format(
139 "Error fetching snapshot size from {}: {} ({})",
140 snapshot_size_request.
get_url(),
141 curl_easy_strerror(snapshot_size_curl_code),
142 snapshot_size_status_code));
146 snapshot_size_request, snapshot_size_status_code, HTTP_STATUS_OK);
148 auto& snapshot_size_response_headers =
151 auto content_size_it = snapshot_size_response_headers.
data.find(
152 ccf::http::headers::CONTENT_LENGTH);
154 if (content_size_it == snapshot_size_response_headers.data.end())
156 throw std::runtime_error(fmt::format(
157 "Expected {} header in response from {} {}, none found",
158 ccf::http::headers::CONTENT_LENGTH,
160 snapshot_size_request.
get_url()));
163 const auto& content_size_s = content_size_it->second;
164 const auto [p, ec] = std::from_chars(
165 content_size_s.data(),
166 content_size_s.data() + content_size_s.size(),
168 if (ec != std::errc())
170 throw std::runtime_error(fmt::format(
171 "Failed to parse {} header in response from {} {}: {}",
172 ccf::http::headers::CONTENT_LENGTH,
174 snapshot_size_request.
get_url(),
180 constexpr size_t range_size = 4L * 1024 * 1024;
182 "Preparing to fetch {}-byte snapshot from peer, {} bytes per-request",
186 auto snapshot_response =
187 std::make_unique<ccf::curl::ResponseBody>(content_size);
190 auto range_start = 0;
191 auto range_end = std::min(content_size, range_size);
196 curl_easy.
set_opt(CURLOPT_CAINFO, path_to_peer_cert.c_str());
200 "Range", fmt::format(
"bytes={}-{}", range_start, range_end));
202 std::string current_snapshot_url = snapshot_url;
205 std::move(curl_easy),
207 std::move(current_snapshot_url),
210 std::move(snapshot_response),
214 CURLcode curl_response = CURLE_OK;
215 long snapshot_range_status_code = 0;
217 curl_response, snapshot_range_status_code);
218 if (curl_response != CURLE_OK)
220 throw std::runtime_error(fmt::format(
221 "Error fetching snapshot chunk range from {}: {} ({})",
222 snapshot_range_request.
get_url(),
223 curl_easy_strerror(curl_response),
224 snapshot_range_status_code));
227 snapshot_range_request,
228 snapshot_range_status_code,
229 HTTP_STATUS_PARTIAL_CONTENT);
232 "Received {}-byte chunk from {}: {} bytes",
233 range_end - range_start,
234 snapshot_range_request.
get_url(),
235 snapshot_range_status_code);
240 if (range_end == content_size)
245 range_start = range_end;
246 range_end = std::min(content_size, range_start + range_size);
250 const auto url_components = ccf::nonstd::split(snapshot_url,
"/");
251 const std::string snapshot_name(url_components.back());
254 snapshot_name, std::move(snapshot_response->buffer)};
256 catch (
const std::exception& e)
258 LOG_FAIL_FMT(
"Error during snapshot fetch: {}", e.what());