18 static std::string format_repr_digest(
19 const std::string& algo_name,
25 auto digest = hp->hash(data, size, md);
27 return fmt::format(
"{}=:{}:", algo_name, b64);
35 static std::optional<std::string> get_redirect_address_for_node(
42 auto node_info =
nodes->get(target_node);
43 if (!node_info.has_value())
45 LOG_FAIL_FMT(
"Node redirection error: Unknown node {}", target_node);
47 HTTP_STATUS_INTERNAL_SERVER_ERROR,
48 ccf::errors::InternalError,
50 "Cannot find node info to produce redirect response for node {}",
55 const auto interface_id = ctx.
rpc_ctx->get_session_context()->interface_id;
56 if (!interface_id.has_value())
60 HTTP_STATUS_INTERNAL_SERVER_ERROR,
61 ccf::errors::InternalError,
62 "Cannot redirect non-RPC request");
66 const auto& interfaces = node_info->rpc_interfaces;
67 const auto interface_it = interfaces.find(interface_id.value());
68 if (interface_it == interfaces.end())
71 "Node redirection error: Target missing interface {}",
72 interface_id.value());
74 HTTP_STATUS_INTERNAL_SERVER_ERROR,
75 ccf::errors::InternalError,
77 "Cannot redirect request. Received on RPC interface {}, which is "
78 "not present on target node {}",
84 const auto&
interface = interface_it->second;
85 return interface.published_address;
92 static std::optional<std::string> get_redirect_address_for_next_node(
98 std::set<ccf::NodeId> other_node_ids;
100 if (node_id != self_node_id)
102 other_node_ids.insert(node_id);
107 if (other_node_ids.empty())
110 "Node redirection error: No other nodes present in the network");
112 HTTP_STATUS_INTERNAL_SERVER_ERROR,
113 ccf::errors::InternalError,
114 "Cannot redirect request. No other nodes present in the network");
118 auto it = other_node_ids.upper_bound(self_node_id);
119 std::optional<ccf::NodeId> next_node_id;
120 if (it != other_node_ids.end())
126 next_node_id = *other_node_ids.begin();
129 return get_redirect_address_for_node(ctx, ro_tx, next_node_id.value());
134 static std::shared_ptr<NodeConfigurationSubsystem>
135 get_node_configuration_subsystem(
139 auto node_configuration_subsystem =
141 if (node_configuration_subsystem ==
nullptr)
144 "NodeConfigurationSubsystem is not available in NodeContext");
146 HTTP_STATUS_INTERNAL_SERVER_ERROR,
147 ccf::errors::InternalError,
148 "NodeConfigurationSubsystem is not available");
150 return node_configuration_subsystem;
172 static void fill_range_response_from_file(
175 f.seekg(0, std::ifstream::end);
176 const auto total_size = (size_t)f.tellg();
184 HTTP_STATUS_INTERNAL_SERVER_ERROR,
185 ccf::errors::EmptyFile,
190 const bool is_head = ctx.
rpc_ctx->get_request_verb() == HTTP_HEAD;
192 ctx.
rpc_ctx->set_response_header(
"accept-ranges",
"bytes");
195 const auto want_digest =
196 ctx.
rpc_ctx->get_request_header(ccf::http::headers::WANT_REPR_DIGEST);
197 std::optional<std::pair<std::string, ccf::crypto::MDType>> digest_algo;
198 if (want_digest.has_value())
200 digest_algo = ccf::http::parse_want_repr_digest(want_digest.value());
203 size_t range_start = 0;
204 size_t range_end = total_size;
205 bool has_range_header =
false;
207 const auto range_header =
208 ctx.
rpc_ctx->get_request_header(ccf::http::headers::RANGE);
209 if (range_header.has_value())
211 has_range_header =
true;
212 LOG_TRACE_FMT(
"Parsing range header {}", range_header.value());
214 auto [unit, ranges] = ccf::nonstd::split_1(range_header.value(),
"=");
218 HTTP_STATUS_BAD_REQUEST,
219 ccf::errors::InvalidHeaderValue,
220 "Only 'bytes' is supported as a Range header unit");
224 if (ranges.find(
',') != std::string::npos)
227 HTTP_STATUS_BAD_REQUEST,
228 ccf::errors::InvalidHeaderValue,
229 "Multiple ranges are not supported");
233 const auto segments = ccf::nonstd::split(ranges,
"-");
234 if (segments.size() != 2)
237 HTTP_STATUS_BAD_REQUEST,
238 ccf::errors::InvalidHeaderValue,
240 "Invalid format, cannot parse range in {}",
241 range_header.value()));
245 const auto s_range_start = segments[0];
246 const auto s_range_end = segments[1];
248 if (!s_range_start.empty())
251 const auto [p, ec] = std::from_chars(
252 s_range_start.begin(), s_range_start.end(), range_start);
253 if (ec != std::errc())
256 HTTP_STATUS_BAD_REQUEST,
257 ccf::errors::InvalidHeaderValue,
259 "Unable to parse start of range value {} in {}",
261 range_header.value()));
266 if (range_start > total_size)
269 HTTP_STATUS_BAD_REQUEST,
270 ccf::errors::InvalidHeaderValue,
272 "Start of range {} is larger than total file size {}",
278 if (!s_range_end.empty())
282 size_t inclusive_range_end = 0;
286 const auto [p, ec] = std::from_chars(
287 s_range_end.begin(), s_range_end.end(), inclusive_range_end);
288 if (ec != std::errc())
291 HTTP_STATUS_BAD_REQUEST,
292 ccf::errors::InvalidHeaderValue,
294 "Unable to parse end of range value {} in {}",
296 range_header.value()));
301 range_end = inclusive_range_end + 1;
303 if (range_end > total_size)
306 "Requested ledger chunk range ending at {}, but file size is "
307 "only {} - shrinking range end",
310 range_end = total_size;
313 if (range_end < range_start)
316 HTTP_STATUS_BAD_REQUEST,
317 ccf::errors::InvalidHeaderValue,
319 "Invalid range: Start ({}) and end ({}) out of order",
328 range_end = total_size;
333 if (!s_range_end.empty())
338 std::from_chars(s_range_end.begin(), s_range_end.end(), offset);
339 if (ec != std::errc())
342 HTTP_STATUS_BAD_REQUEST,
343 ccf::errors::InvalidHeaderValue,
345 "Unable to parse end of range offset value {} in {}",
347 range_header.value()));
351 range_end = total_size;
352 range_start = range_end - offset;
357 HTTP_STATUS_BAD_REQUEST,
358 ccf::errors::InvalidHeaderValue,
359 "Invalid range: Must contain range-start or range-end");
366 const auto range_size = range_end - range_start;
369 "Reading {}-byte range from {} to {}",
378 std::vector<uint8_t> contents;
379 if (digest_algo.has_value())
383 std::vector<uint8_t> full_contents(total_size);
385 f.read(
reinterpret_cast<char*
>(full_contents.data()), total_size);
388 auto bytes_read =
static_cast<size_t>(f.gcount());
389 if (bytes_read < range_end)
392 HTTP_STATUS_INTERNAL_SERVER_ERROR,
393 ccf::errors::InternalError,
394 "Server was unable to read the file correctly");
398 if (bytes_read == total_size)
400 ctx.
rpc_ctx->set_response_header(
401 ccf::http::headers::REPR_DIGEST,
405 full_contents.data(),
406 full_contents.size()));
411 full_contents.begin() + range_start, full_contents.begin() + range_end);
416 contents.resize(range_size);
417 f.seekg(range_start);
419 f.read(
reinterpret_cast<char*
>(contents.data()), contents.size());
426 auto sha256_hash = hash_provider->hash(
430 auto sha256_etag = fmt::format(
"sha-256=:{}:", sha256_b64);
432 ctx.
rpc_ctx->set_response_header(
433 ccf::http::headers::ETAG, fmt::format(
"\"{}\"", sha256_etag));
436 const auto if_none_match =
437 ctx.
rpc_ctx->get_request_header(ccf::http::headers::IF_NONE_MATCH);
438 if (if_none_match.has_value())
444 bool matched = matcher.
is_any() || matcher.
matches(sha256_etag);
448 auto sha384_hash = hash_provider->hash(
452 matched = matcher.
matches(fmt::format(
"sha-384=:{}:", sha384_b64));
457 auto sha512_hash = hash_provider->hash(
461 matched = matcher.
matches(fmt::format(
"sha-512=:{}:", sha512_b64));
466 ctx.
rpc_ctx->set_response_status(HTTP_STATUS_NOT_MODIFIED);
467 ctx.
rpc_ctx->set_response_body(std::vector<uint8_t>{});
474 HTTP_STATUS_BAD_REQUEST, ccf::errors::InvalidHeaderValue, e.
what());
480 ctx.
rpc_ctx->set_response_header(
481 ccf::http::headers::CONTENT_TYPE,
482 ccf::http::headervalues::contenttype::OCTET_STREAM);
484 if (has_range_header)
486 ctx.
rpc_ctx->set_response_status(HTTP_STATUS_PARTIAL_CONTENT);
489 const auto inclusive_range_end = range_end - 1;
493 ctx.
rpc_ctx->set_response_header(
494 ccf::http::headers::CONTENT_RANGE,
496 "bytes {}-{}/{}", range_start, inclusive_range_end, total_size));
500 ctx.
rpc_ctx->set_response_status(HTTP_STATUS_OK);
507 ctx.
rpc_ctx->set_response_header(
508 ccf::http::headers::CONTENT_LENGTH,
509 has_range_header ? range_size : total_size);
513 ctx.
rpc_ctx->set_response_body(std::move(contents));
517 static void init_file_serving_handlers(
520 static constexpr auto file_since_param_key =
"since";
523 size_t latest_idx = 0;
526 const auto parsed_query =
527 http::parse_query(ctx.
rpc_ctx->get_request_query());
529 std::string error_reason;
530 auto snapshot_since = http::get_query_value_opt<ccf::SeqNo>(
531 parsed_query, file_since_param_key, error_reason);
533 if (snapshot_since.has_value())
535 if (!error_reason.empty())
538 HTTP_STATUS_BAD_REQUEST,
539 ccf::errors::InvalidQueryParameterValue,
540 std::move(error_reason));
543 latest_idx = snapshot_since.value();
548 if (node_operation ==
nullptr)
551 HTTP_STATUS_INTERNAL_SERVER_ERROR,
552 ccf::errors::InternalError,
553 "Unable to access NodeOperation subsystem");
557 if (!node_operation->can_replicate())
561 auto primary_id = node_operation->get_primary();
562 if (primary_id.has_value())
565 get_redirect_address_for_node(ctx, ctx.tx, *primary_id);
566 if (!address.has_value())
572 fmt::format(
"https://{}/node/snapshot", address.value());
575 location += fmt::format(
"?{}={}", file_since_param_key, latest_idx);
578 ctx.
rpc_ctx->set_response_header(http::headers::LOCATION, location);
580 HTTP_STATUS_PERMANENT_REDIRECT,
581 ccf::errors::NodeCannotHandleRequest,
582 "Node is not primary; redirecting for preferable snapshot");
591 auto node_configuration_subsystem =
592 get_node_configuration_subsystem(node_context, ctx);
593 if (node_configuration_subsystem ==
nullptr)
598 const auto& snapshots_config =
599 node_configuration_subsystem->get().node_config.snapshots;
601 const auto orig_latest = latest_idx;
602 auto latest_committed_snapshot =
604 snapshots_config.directory, latest_idx);
606 if (!latest_committed_snapshot.has_value())
609 HTTP_STATUS_NOT_FOUND,
610 ccf::errors::ResourceNotFound,
612 "This node has no committed snapshots since {}", orig_latest));
616 const auto& snapshot_name = latest_committed_snapshot->filename();
619 get_redirect_address_for_node(ctx, ctx.tx, node_context.
get_node_id());
620 if (!address.has_value())
625 auto redirect_url = fmt::format(
626 "https://{}/node/snapshot/{}", address.value(), snapshot_name);
628 ctx.
rpc_ctx->set_response_header(
629 ccf::http::headers::LOCATION, redirect_url);
630 ctx.
rpc_ctx->set_response_status(HTTP_STATUS_PERMANENT_REDIRECT);
634 "/snapshot", HTTP_HEAD, find_snapshot, no_auth_required)
642 "/snapshot", HTTP_GET, find_snapshot, no_auth_required)
651 size_t since_idx = 0;
654 const auto parsed_query =
655 http::parse_query(ctx.
rpc_ctx->get_request_query());
657 std::string error_reason;
658 auto chunk_since = http::get_query_value_opt<ccf::SeqNo>(
659 parsed_query, file_since_param_key, error_reason);
661 if (chunk_since.has_value())
663 if (!error_reason.empty())
666 HTTP_STATUS_BAD_REQUEST,
667 ccf::errors::InvalidQueryParameterValue,
668 std::move(error_reason));
671 since_idx = chunk_since.value();
676 HTTP_STATUS_BAD_REQUEST,
677 ccf::errors::InvalidQueryParameterValue,
679 "Missing required query parameter '{}'", file_since_param_key));
683 LOG_DEBUG_FMT(
"Finding ledger chunk including index {}", since_idx);
686 if (node_operation ==
nullptr)
689 HTTP_STATUS_INTERNAL_SERVER_ERROR,
690 ccf::errors::InternalError,
691 "Unable to access NodeOperation subsystem");
696 get_redirect_address_for_node(ctx, ctx.tx, node_context.
get_node_id());
697 if (!address.has_value())
702 auto read_ledger_subsystem =
704 if (read_ledger_subsystem ==
nullptr)
707 HTTP_STATUS_INTERNAL_SERVER_ERROR,
708 ccf::errors::InternalError,
709 "LedgerReadSubsystem is not available");
713 const auto chunk_path =
714 read_ledger_subsystem->committed_ledger_path_with_idx(since_idx);
717 if (chunk_path.has_value())
719 const auto chunk_filename = chunk_path.value().filename();
721 auto redirect_url = fmt::format(
722 "https://{}/node/ledger_chunk/{}", address.value(), chunk_filename);
723 LOG_DEBUG_FMT(
"Redirecting to ledger chunk: {}", redirect_url);
724 ctx.
rpc_ctx->set_response_header(
725 ccf::http::headers::LOCATION, redirect_url);
726 ctx.
rpc_ctx->set_response_status(HTTP_STATUS_PERMANENT_REDIRECT);
732 const size_t init_idx = read_ledger_subsystem->get_init_idx();
733 if (since_idx < init_idx)
736 "This node cannot serve ledger chunk including index {} which is "
737 "before its init index {} - trying to redirect to next node",
741 address = get_redirect_address_for_next_node(
743 if (!address.has_value())
748 auto location = fmt::format(
749 "https://{}/node/ledger_chunk?{}={}",
751 file_since_param_key,
753 ctx.
rpc_ctx->set_response_header(http::headers::LOCATION, location);
755 HTTP_STATUS_PERMANENT_REDIRECT,
756 ccf::errors::NodeCannotHandleRequest,
757 "Node does not have ledger chunk; redirecting to next node");
763 if (!node_operation->can_replicate())
766 "This node cannot serve ledger chunk including index {} - trying "
767 "to redirect to primary",
769 auto primary_id = node_operation->get_primary();
770 if (primary_id.has_value())
772 address = get_redirect_address_for_node(ctx, ctx.tx, *primary_id);
773 if (address.has_value())
776 fmt::format(
"https://{}/node/ledger_chunk", address.value());
777 location += fmt::format(
"?{}={}", file_since_param_key, since_idx);
779 ctx.
rpc_ctx->set_response_header(http::headers::LOCATION, location);
781 HTTP_STATUS_PERMANENT_REDIRECT,
782 ccf::errors::NodeCannotHandleRequest,
784 "Ledger chunk including index {} not found locally; "
785 "redirecting to primary",
794 HTTP_STATUS_NOT_FOUND,
795 ccf::errors::ResourceNotFound,
797 "This node has no ledger chunk including index {}", since_idx));
802 "/ledger_chunk", HTTP_HEAD, find_chunk, no_auth_required)
809 "Redirect to the corresponding /node/ledger_chunk/{chunk_name} "
810 "endpoint for the ledger chunk including the sequence number specified "
811 "in the 'since' query parameter.")
815 "/ledger_chunk", HTTP_GET, find_chunk, no_auth_required)
822 "Redirect to the corresponding /node/ledger_chunk/{chunk_name} "
823 "endpoint for the ledger chunk including the sequence number specified "
824 "in the 'since' query parameter.")
828 auto node_configuration_subsystem =
829 get_node_configuration_subsystem(node_context, ctx);
830 if (node_configuration_subsystem ==
nullptr)
835 const auto& snapshots_config =
836 node_configuration_subsystem->get().node_config.snapshots;
838 std::string snapshot_name;
841 ctx.
rpc_ctx->get_request_path_params(),
847 HTTP_STATUS_BAD_REQUEST,
848 ccf::errors::InvalidResourceName,
853 files::fs::path snapshot_path =
854 files::fs::path(snapshots_config.directory) / snapshot_name;
856 std::ifstream f(snapshot_path, std::ios::binary);
860 HTTP_STATUS_NOT_FOUND,
861 ccf::errors::ResourceNotFound,
863 "This node does not have a snapshot named {}", snapshot_name));
869 ctx.
rpc_ctx->set_response_header(
870 ccf::http::headers::CCF_SNAPSHOT_NAME, snapshot_name);
872 fill_range_response_from_file(ctx, f);
877 "/snapshot/{snapshot_name}", HTTP_HEAD, get_snapshot, no_auth_required)
883 "/snapshot/{snapshot_name}", HTTP_GET, get_snapshot, no_auth_required)
889 auto node_configuration_subsystem =
890 get_node_configuration_subsystem(node_context, ctx);
891 if (node_configuration_subsystem ==
nullptr)
896 const auto& ledger_config =
897 node_configuration_subsystem->get().node_config.ledger;
899 std::string chunk_name;
902 ctx.
rpc_ctx->get_request_path_params(),
908 HTTP_STATUS_BAD_REQUEST,
909 ccf::errors::InvalidResourceName,
916 files::fs::path chunk_path =
917 files::fs::path(ledger_config.directory) / chunk_name;
919 std::ifstream f(chunk_path, std::ios::binary);
923 HTTP_STATUS_NOT_FOUND,
924 ccf::errors::ResourceNotFound,
926 "This node does not have a ledger chunk named {}", chunk_name));
930 LOG_DEBUG_FMT(
"Found ledger chunk: {}", chunk_path.string());
932 ctx.
rpc_ctx->set_response_header(
933 ccf::http::headers::CCF_LEDGER_CHUNK_NAME, chunk_name);
935 fill_range_response_from_file(ctx, f);
941 "/ledger_chunk/{chunk_name}",
949 "Metadata about a specific ledger chunk (Content-Length and "
950 "x-ms-ccf-ledger-chunk-name)")
954 "/ledger_chunk/{chunk_name}",
962 "Download a specific ledger chunk by name. Supports HTTP Range header "
963 "for partial downloads.")