16 static std::optional<std::string> get_redirect_address_for_node(
23 auto node_info =
nodes->get(target_node);
24 if (!node_info.has_value())
26 LOG_FAIL_FMT(
"Node redirection error: Unknown node {}", target_node);
28 HTTP_STATUS_INTERNAL_SERVER_ERROR,
29 ccf::errors::InternalError,
31 "Cannot find node info to produce redirect response for node {}",
36 const auto interface_id = ctx.
rpc_ctx->get_session_context()->interface_id;
37 if (!interface_id.has_value())
41 HTTP_STATUS_INTERNAL_SERVER_ERROR,
42 ccf::errors::InternalError,
43 "Cannot redirect non-RPC request");
47 const auto& interfaces = node_info->rpc_interfaces;
48 const auto interface_it = interfaces.find(interface_id.value());
49 if (interface_it == interfaces.end())
52 "Node redirection error: Target missing interface {}",
53 interface_id.value());
55 HTTP_STATUS_INTERNAL_SERVER_ERROR,
56 ccf::errors::InternalError,
58 "Cannot redirect request. Received on RPC interface {}, which is "
59 "not present on target node {}",
65 const auto&
interface = interface_it->second;
66 return interface.published_address;
69 static void init_file_serving_handlers(
73 static constexpr auto snapshot_since_param_key =
"since";
76 size_t latest_idx = 0;
79 const auto parsed_query =
80 http::parse_query(ctx.
rpc_ctx->get_request_query());
82 std::string error_reason;
83 auto snapshot_since = http::get_query_value_opt<ccf::SeqNo>(
84 parsed_query, snapshot_since_param_key, error_reason);
86 if (snapshot_since.has_value())
88 if (!error_reason.empty())
91 HTTP_STATUS_BAD_REQUEST,
92 ccf::errors::InvalidQueryParameterValue,
93 std::move(error_reason));
96 latest_idx = snapshot_since.value();
101 if (node_operation ==
nullptr)
104 HTTP_STATUS_INTERNAL_SERVER_ERROR,
105 ccf::errors::InternalError,
106 "Unable to access NodeOperation subsystem");
110 if (!node_operation->can_replicate())
114 auto primary_id = node_operation->get_primary();
115 if (primary_id.has_value())
118 get_redirect_address_for_node(ctx, ctx.tx, *primary_id);
119 if (!address.has_value())
125 fmt::format(
"https://{}/node/snapshot", address.value());
129 fmt::format(
"?{}={}", snapshot_since_param_key, latest_idx);
132 ctx.
rpc_ctx->set_response_header(http::headers::LOCATION, location);
134 HTTP_STATUS_PERMANENT_REDIRECT,
135 ccf::errors::NodeCannotHandleRequest,
136 "Node is not primary; redirecting for preferable snapshot");
145 auto node_configuration_subsystem =
147 if (node_configuration_subsystem ==
nullptr)
150 HTTP_STATUS_INTERNAL_SERVER_ERROR,
151 ccf::errors::InternalError,
152 "NodeConfigurationSubsystem is not available");
156 const auto& snapshots_config =
157 node_configuration_subsystem->get().node_config.snapshots;
159 const auto orig_latest = latest_idx;
160 auto latest_committed_snapshot =
162 snapshots_config.directory, latest_idx);
164 if (!latest_committed_snapshot.has_value())
167 HTTP_STATUS_NOT_FOUND,
168 ccf::errors::ResourceNotFound,
170 "This node has no committed snapshots since {}", orig_latest));
174 const auto& snapshot_path = latest_committed_snapshot.value();
177 get_redirect_address_for_node(ctx, ctx.tx, node_context.
get_node_id());
178 if (!address.has_value())
183 auto redirect_url = fmt::format(
184 "https://{}/node/snapshot/{}", address.value(), snapshot_path);
186 ctx.
rpc_ctx->set_response_header(
187 ccf::http::headers::LOCATION, redirect_url);
188 ctx.
rpc_ctx->set_response_status(HTTP_STATUS_PERMANENT_REDIRECT);
192 "/snapshot", HTTP_HEAD, find_snapshot, no_auth_required)
196 .set_openapi_hidden(
true)
201 "/snapshot", HTTP_GET, find_snapshot, no_auth_required)
205 .set_openapi_hidden(
true)
210 auto node_configuration_subsystem =
212 if (node_configuration_subsystem ==
nullptr)
215 HTTP_STATUS_INTERNAL_SERVER_ERROR,
216 ccf::errors::InternalError,
217 "NodeConfigurationSubsystem is not available");
221 const auto& snapshots_config =
222 node_configuration_subsystem->get().node_config.snapshots;
224 std::string snapshot_name;
227 ctx.
rpc_ctx->get_request_path_params(),
233 HTTP_STATUS_BAD_REQUEST,
234 ccf::errors::InvalidResourceName,
239 files::fs::path snapshot_path =
240 files::fs::path(snapshots_config.directory) / snapshot_name;
242 std::ifstream f(snapshot_path, std::ios::binary);
246 HTTP_STATUS_NOT_FOUND,
247 ccf::errors::ResourceNotFound,
249 "This node does not have a snapshot named {}", snapshot_name));
255 f.seekg(0, std::ifstream::end);
256 const auto total_size = (size_t)f.tellg();
258 ctx.
rpc_ctx->set_response_header(
"accept-ranges",
"bytes");
260 ctx.
rpc_ctx->set_response_header(
261 ccf::http::headers::CCF_SNAPSHOT_NAME, snapshot_name);
263 if (ctx.
rpc_ctx->get_request_verb() == HTTP_HEAD)
265 ctx.
rpc_ctx->set_response_status(HTTP_STATUS_OK);
266 ctx.
rpc_ctx->set_response_header(
267 ccf::http::headers::CONTENT_LENGTH, total_size);
271 size_t range_start = 0;
272 size_t range_end = total_size;
274 const auto range_header = ctx.
rpc_ctx->get_request_header(
"range");
275 if (range_header.has_value())
277 LOG_TRACE_FMT(
"Parsing range header {}", range_header.value());
279 auto [unit, ranges] = ccf::nonstd::split_1(range_header.value(),
"=");
283 HTTP_STATUS_BAD_REQUEST,
284 ccf::errors::InvalidHeaderValue,
285 "Only 'bytes' is supported as a Range header unit");
289 if (ranges.find(
',') != std::string::npos)
292 HTTP_STATUS_BAD_REQUEST,
293 ccf::errors::InvalidHeaderValue,
294 "Multiple ranges are not supported");
298 const auto segments = ccf::nonstd::split(ranges,
"-");
299 if (segments.size() != 2)
302 HTTP_STATUS_BAD_REQUEST,
303 ccf::errors::InvalidHeaderValue,
305 "Invalid format, cannot parse range in {}",
306 range_header.value()));
310 const auto s_range_start = segments[0];
311 const auto s_range_end = segments[1];
313 if (!s_range_start.empty())
316 const auto [p, ec] = std::from_chars(
317 s_range_start.begin(), s_range_start.end(), range_start);
318 if (ec != std::errc())
321 HTTP_STATUS_BAD_REQUEST,
322 ccf::errors::InvalidHeaderValue,
324 "Unable to parse start of range value {} in {}",
326 range_header.value()));
331 if (range_start > total_size)
334 HTTP_STATUS_BAD_REQUEST,
335 ccf::errors::InvalidHeaderValue,
337 "Start of range {} is larger than total file size {}",
343 if (!s_range_end.empty())
347 const auto [p, ec] = std::from_chars(
348 s_range_end.begin(), s_range_end.end(), range_end);
349 if (ec != std::errc())
352 HTTP_STATUS_BAD_REQUEST,
353 ccf::errors::InvalidHeaderValue,
355 "Unable to parse end of range value {} in {}",
357 range_header.value()));
362 if (range_end > total_size)
365 "Requested snapshot range ending at {}, but file size is "
366 "only {} - shrinking range end",
369 range_end = total_size;
372 if (range_end < range_start)
375 HTTP_STATUS_BAD_REQUEST,
376 ccf::errors::InvalidHeaderValue,
378 "Invalid range: Start ({}) and end ({}) out of order",
387 range_end = total_size;
392 if (!s_range_end.empty())
397 std::from_chars(s_range_end.begin(), s_range_end.end(), offset);
398 if (ec != std::errc())
401 HTTP_STATUS_BAD_REQUEST,
402 ccf::errors::InvalidHeaderValue,
404 "Unable to parse end of range offset value {} in {}",
406 range_header.value()));
410 range_end = total_size;
411 range_start = range_end - offset;
416 HTTP_STATUS_BAD_REQUEST,
417 ccf::errors::InvalidHeaderValue,
418 "Invalid range: Must contain range-start or range-end");
425 const auto range_size = range_end - range_start;
428 "Reading {}-byte range from {} to {}",
434 std::vector<uint8_t> contents(range_size);
435 f.seekg(range_start);
437 f.read(
reinterpret_cast<char*
>(contents.data()), contents.size());
441 ctx.
rpc_ctx->set_response_status(HTTP_STATUS_PARTIAL_CONTENT);
442 ctx.
rpc_ctx->set_response_header(
443 ccf::http::headers::CONTENT_TYPE,
444 ccf::http::headervalues::contenttype::OCTET_STREAM);
445 ctx.
rpc_ctx->set_response_body(std::move(contents));
449 ctx.
rpc_ctx->set_response_header(
450 ccf::http::headers::CONTENT_RANGE,
451 fmt::format(
"bytes {}-{}/{}", range_start, range_end, total_size));
455 "/snapshot/{snapshot_name}", HTTP_HEAD, get_snapshot, no_auth_required)
462 "/snapshot/{snapshot_name}", HTTP_GET, get_snapshot, no_auth_required)