CCF
Loading...
Searching...
No Matches
fetch.h
Go to the documentation of this file.
1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the Apache 2.0 License.
3#pragma once
4
5#include "ccf/ds/logger.h"
6#include "ccf/ds/nonstd.h"
7#include "ccf/rest_verb.h"
8#include "http/curl.h"
9#include "http/http_builder.h"
10
11#include <charconv>
12#include <curl/curl.h>
13#include <llhttp/llhttp.h>
14#include <optional>
15#include <span>
16#include <stdexcept>
17#include <string>
18#include <vector>
19
20#define EXPECT_HTTP_RESPONSE_STATUS(request, status_code, expected) \
21 do \
22 { \
23 if (status_code != expected) \
24 { \
25 throw std::runtime_error(fmt::format( \
26 "Expected {} response from {} {}, instead received {}", \
27 ccf::http_status_str(expected), \
28 request.get_method().c_str(), \
29 request.get_url(), \
30 status_code)); \
31 } \
32 } while (0)
33
34namespace snapshots
35{
37 {
38 std::string snapshot_name;
39 std::vector<uint8_t> snapshot_data;
40 };
41
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)
46 {
47 try
48 {
49 // Make initial request, which returns a redirect response to specific
50 // snapshot
51 std::string snapshot_url;
52 {
53 ccf::curl::UniqueCURL curl_easy;
54 curl_easy.set_opt(CURLOPT_CAINFO, path_to_peer_cert.c_str());
55
56 auto initial_url = fmt::format(
57 "https://{}/node/snapshot?since={}",
58 peer_address,
59 latest_local_snapshot);
60
62
63 auto request = ccf::curl::CurlRequest(
64 std::move(curl_easy),
65 HTTP_HEAD,
66 std::move(initial_url),
67 std::move(headers),
68 nullptr, // No request body
69 nullptr, // No response body
70 std::nullopt // No response callback
71 );
72
73 long status_code = 0;
74 CURLcode curl_response = CURLE_OK;
75 request.synchronous_perform(curl_response, status_code);
76 if (curl_response != CURLE_OK)
77 {
78 throw std::runtime_error(fmt::format(
79 "Error fetching snapshot redirect from {}: {} ({})",
80 request.get_url(),
81 curl_easy_strerror(curl_response),
82 status_code));
83 }
84 if (status_code == HTTP_STATUS_NOT_FOUND)
85 {
87 "Peer has no snapshot newer than {}", latest_local_snapshot);
88 return std::nullopt;
89 }
91 request, status_code, HTTP_STATUS_PERMANENT_REDIRECT);
92
93 auto& response_headers = request.get_response_headers();
94 auto location_it =
95 response_headers.data.find(ccf::http::headers::LOCATION);
96 if (location_it == response_headers.data.end())
97 {
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(),
102 request.get_url()));
103 }
104
105 LOG_TRACE_FMT("Snapshot fetch redirected to {}", location_it->second);
106
107 snapshot_url =
108 fmt::format("https://{}{}", peer_address, location_it->second);
109 }
110
111 // Make follow-up request to redirected URL, to fetch total content size
112 size_t content_size = 0;
113 {
114 ccf::curl::UniqueCURL curl_easy;
115 curl_easy.set_opt(CURLOPT_CAINFO, path_to_peer_cert.c_str());
116
118
119 std::string current_snapshot_url = snapshot_url;
120
121 ccf::curl::CurlRequest snapshot_size_request(
122 std::move(curl_easy),
123 HTTP_HEAD,
124 std::move(current_snapshot_url),
125 std::move(headers),
126 nullptr, // No request body
127 nullptr, // No response body
128 std::nullopt // No response callback
129 );
130
131 CURLcode snapshot_size_curl_code = CURLE_OK;
132 long snapshot_size_status_code = 0;
133 snapshot_size_request.synchronous_perform(
134 snapshot_size_curl_code, snapshot_size_status_code);
135
136 if (snapshot_size_curl_code != CURLE_OK)
137 {
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));
143 }
144
146 snapshot_size_request, snapshot_size_status_code, HTTP_STATUS_OK);
147
148 auto& snapshot_size_response_headers =
149 snapshot_size_request.get_response_headers();
150
151 auto content_size_it = snapshot_size_response_headers.data.find(
152 ccf::http::headers::CONTENT_LENGTH);
153
154 if (content_size_it == snapshot_size_response_headers.data.end())
155 {
156 throw std::runtime_error(fmt::format(
157 "Expected {} header in response from {} {}, none found",
158 ccf::http::headers::CONTENT_LENGTH,
159 snapshot_size_request.get_method().c_str(),
160 snapshot_size_request.get_url()));
161 }
162
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(),
167 content_size);
168 if (ec != std::errc())
169 {
170 throw std::runtime_error(fmt::format(
171 "Failed to parse {} header in response from {} {}: {}",
172 ccf::http::headers::CONTENT_LENGTH,
173 snapshot_size_request.get_method().c_str(),
174 snapshot_size_request.get_url(),
175 ec));
176 }
177 }
178
179 // Fetch 4MB chunks at a time
180 constexpr size_t range_size = 4L * 1024 * 1024;
182 "Preparing to fetch {}-byte snapshot from peer, {} bytes per-request",
183 content_size,
184 range_size);
185
186 auto snapshot_response =
187 std::make_unique<ccf::curl::ResponseBody>(content_size);
188
189 {
190 auto range_start = 0;
191 auto range_end = std::min(content_size, range_size);
192
193 while (true)
194 {
195 ccf::curl::UniqueCURL curl_easy;
196 curl_easy.set_opt(CURLOPT_CAINFO, path_to_peer_cert.c_str());
197
199 headers.append(
200 "Range", fmt::format("bytes={}-{}", range_start, range_end));
201
202 std::string current_snapshot_url = snapshot_url;
203
204 ccf::curl::CurlRequest snapshot_range_request(
205 std::move(curl_easy),
206 HTTP_GET,
207 std::move(current_snapshot_url),
208 std::move(headers),
209 nullptr, // No request body
210 std::move(snapshot_response),
211 std::nullopt // No response callback
212 );
213
214 CURLcode curl_response = CURLE_OK;
215 long snapshot_range_status_code = 0;
216 snapshot_range_request.synchronous_perform(
217 curl_response, snapshot_range_status_code);
218 if (curl_response != CURLE_OK)
219 {
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));
225 }
227 snapshot_range_request,
228 snapshot_range_status_code,
229 HTTP_STATUS_PARTIAL_CONTENT);
230
232 "Received {}-byte chunk from {}: {} bytes",
233 range_end - range_start,
234 snapshot_range_request.get_url(),
235 snapshot_range_status_code);
236
237 snapshot_response =
238 std::move(snapshot_range_request.get_response_ptr());
239
240 if (range_end == content_size)
241 {
242 break;
243 }
244
245 range_start = range_end;
246 range_end = std::min(content_size, range_start + range_size);
247 }
248 }
249
250 const auto url_components = ccf::nonstd::split(snapshot_url, "/");
251 const std::string snapshot_name(url_components.back());
252
253 return SnapshotResponse{
254 snapshot_name, std::move(snapshot_response->buffer)};
255 }
256 catch (const std::exception& e)
257 {
258 LOG_FAIL_FMT("Error during snapshot fetch: {}", e.what());
259 return std::nullopt;
260 }
261 }
262}
const char * c_str() const
Definition rest_verb.h:62
Definition curl.h:360
RESTVerb get_method() const
Definition curl.h:489
void synchronous_perform(CURLcode &curl_code, long &status_code)
Definition curl.h:468
std::string get_url() const
Definition curl.h:494
ResponseHeaders & get_response_headers()
Definition curl.h:509
std::unique_ptr< ResponseBody > & get_response_ptr()
Definition curl.h:504
HeaderMap data
Definition curl.h:288
Definition curl.h:53
void set_opt(auto option, auto value)
Definition curl.h:93
Definition curl.h:135
void append(const char *str)
Definition curl.h:151
#define EXPECT_HTTP_RESPONSE_STATUS(request, status_code, expected)
Definition fetch.h:20
#define LOG_INFO_FMT
Definition logger.h:362
#define LOG_TRACE_FMT
Definition logger.h:356
#define LOG_FAIL_FMT
Definition logger.h:363
Definition fetch.h:35
Definition fetch.h:37
std::string snapshot_name
Definition fetch.h:38
std::vector< uint8_t > snapshot_data
Definition fetch.h:39