Files
spring/plugins/http_plugin/http_plugin.cpp

647 lines
33 KiB
C++

#include <eosio/http_plugin/http_plugin.hpp>
#include <eosio/http_plugin/common.hpp>
#include <eosio/http_plugin/beast_http_session.hpp>
#include <eosio/chain/exceptions.hpp>
#include <fc/log/logger_config.hpp>
#include <fc/reflect/variant.hpp>
#include <fc/network/listener.hpp>
#include <boost/asio.hpp>
#include <memory>
#include <regex>
namespace eosio {
namespace {
inline fc::logger& logger() {
static fc::logger log{ "http_plugin" };
return log;
}
}
static auto _http_plugin = application::register_plugin<http_plugin>();
using std::vector;
using std::string;
using std::regex;
using boost::asio::ip::tcp;
using std::shared_ptr;
static http_plugin_defaults current_http_plugin_defaults;
static bool verbose_http_errors = false;
void http_plugin::set_defaults(const http_plugin_defaults& config) {
current_http_plugin_defaults = config;
}
std::string http_plugin::get_server_header() {
return current_http_plugin_defaults.server_header;
}
using http_plugin_impl_ptr = std::shared_ptr<class http_plugin_impl>;
api_category to_category(std::string_view name) {
if (name == "chain_ro") return api_category::chain_ro;
if (name == "chain_rw") return api_category::chain_rw;
if (name == "db_size") return api_category::db_size;
if (name == "net_ro") return api_category::net_ro;
if (name == "net_rw") return api_category::net_rw;
if (name == "producer_ro") return api_category::producer_ro;
if (name == "producer_rw") return api_category::producer_rw;
if (name == "snapshot") return api_category::snapshot;
if (name == "trace_api") return api_category::trace_api;
if (name == "prometheus") return api_category::prometheus;
if (name == "test_control") return api_category::test_control;
return api_category::unknown;
}
const char* from_category(api_category category) {
if (category == api_category::chain_ro) return "chain_ro";
if (category == api_category::chain_rw) return "chain_rw";
if (category == api_category::db_size) return "db_size";
if (category == api_category::net_ro) return "net_ro";
if (category == api_category::net_rw) return "net_rw";
if (category == api_category::producer_ro) return "producer_ro";
if (category == api_category::producer_rw) return "producer_rw";
if (category == api_category::snapshot) return "snapshot";
if (category == api_category::trace_api) return "trace_api";
if (category == api_category::prometheus) return "prometheus";
if (category == api_category::test_control) return "test_control";
if (category == api_category::node) return "node";
// It's a programming error when the control flow reaches this point,
// please make sure all the category names are returned from above statements.
assert(false && "No correspding category name for the category value");
return "";
}
std::string category_plugin_name(api_category category) {
if (category == api_category::db_size)
return "eosio::db_size_api_plugin";
if (category == api_category::trace_api)
return "eosio::trace_api_plugin";
if (category == api_category::prometheus)
return "eosio::prometheus_plugin";
if (category == api_category::test_control)
return "eosio::test_control_plugin";
if (api_category_set({api_category::chain_ro, api_category::chain_rw}).contains(category))
return "eosio::chain_api_plugin";
if (api_category_set({api_category::net_ro, api_category::net_rw}).contains(category))
return "eosio::net_api_plugin";
if (api_category_set({api_category::producer_ro, api_category::producer_rw, api_category::snapshot})
.contains(category))
return "eosio::producer_api_plugin";
// It's a programming error when the control flow reaches this point,
// please make sure all the plugin names are returned from above statements.
assert(false && "No correspding plugin for the category value");
return {};
}
std::string category_names(api_category_set set) {
if (set == api_category_set::all()) return "all";
std::string result;
for (uint32_t i = 1; i <= static_cast<uint32_t>(api_category::test_control); i<<=1) {
if (set.contains(api_category(i))) {
result += from_category(api_category(i));
result += " ";
}
}
return result;
}
class http_plugin_impl : public std::enable_shared_from_this<http_plugin_impl> {
public:
http_plugin_impl() = default;
http_plugin_impl(const http_plugin_impl&) = delete;
http_plugin_impl(http_plugin_impl&&) = delete;
http_plugin_impl& operator=(const http_plugin_impl&) = delete;
http_plugin_impl& operator=(http_plugin_impl&&) = delete;
std::map<std::string, api_category_set> categories_by_address;
std::shared_ptr<http_plugin_state> plugin_state{new http_plugin_state(logger())};
std::atomic<bool> listening;
/**
* Make an internal_url_handler that will run the url_handler on the app() thread and then
* return to the http thread pool for response processing
*
* @pre b.size() has been added to bytes_in_flight by caller
* @param priority - priority to post to the app thread at
* @param to_queue - execution queue to post to
* @param next - the next handler for responses
* @param my - the http_plugin_impl
* @param content_type - json or plain txt
* @return the constructed internal_url_handler
*/
static detail::internal_url_handler make_app_thread_url_handler(api_entry&& entry, appbase::exec_queue to_queue, int priority, http_content_type content_type ) {
detail::internal_url_handler handler;
handler.content_type = content_type;
handler.category = entry.category;
auto next_ptr = std::make_shared<url_handler>(std::move(entry.handler));
handler.fn = [priority, to_queue, next_ptr=std::move(next_ptr)]
( detail::abstract_conn_ptr conn, string&& r, string&& b, url_response_callback&& then ) {
if (auto error_str = conn->verify_max_bytes_in_flight(b.size()); !error_str.empty()) {
conn->send_busy_response(std::move(error_str));
return;
}
url_response_callback wrapped_then = [then=std::move(then)](int code, std::optional<fc::variant> resp) {
then(code, std::move(resp));
};
// post to the app thread taking shared ownership of next (via std::shared_ptr),
// sole ownership of the tracked body and the passed in parameters
// we can't std::move() next_ptr because we post a new lambda for each http request and we need to keep the original
app().executor().post( priority, to_queue, [next_ptr, conn=std::move(conn), r=std::move(r), b = std::move(b), wrapped_then=std::move(wrapped_then)]() mutable {
try {
if( app().is_quiting() ) return; // http_plugin shutting down, do not call callback
// call the `next` url_handler and wrap the response handler
(*next_ptr)( std::move(r), std::move(b), std::move(wrapped_then)) ;
} catch( ... ) {
conn->handle_exception();
}
} );
};
return handler;
}
/**
* Make an internal_url_handler that will run the url_handler directly
*
* @pre b.size() has been added to bytes_in_flight by caller
* @param next - the next handler for responses
* @return the constructed internal_url_handler
*/
static detail::internal_url_handler make_http_thread_url_handler(api_entry&& entry, http_content_type content_type) {
detail::internal_url_handler handler;
handler.content_type = content_type;
handler.category = entry.category;
handler.fn = [next=std::move(entry.handler)]( const detail::abstract_conn_ptr& conn, string&& r, string&& b, url_response_callback&& then ) mutable {
try {
next(std::move(r), std::move(b), std::move(then));
} catch( ... ) {
conn->handle_exception();
}
};
return handler;
}
bool is_unix_socket_address(const std::string& address) const {
return address.starts_with("/") || address.starts_with("./") || address.starts_with("../");
}
bool on_loopback_only(const std::string& address) {
if (is_unix_socket_address(address))
return true;
auto [host, port] = fc::split_host_port(address);
boost::system::error_code ec;
tcp::resolver resolver(plugin_state->thread_pool.get_executor());
auto endpoints = resolver.resolve(host, port, boost::asio::ip::tcp::resolver::passive, ec);
if (ec) {
fc_wlog(logger(), "Cannot resolve address ${addr}: ${msg}", ("addr", address)("msg", ec.message()));
return false;
}
return std::all_of(endpoints.begin(), endpoints.end(), [](const auto& ep) {
return ep.endpoint().address().is_loopback();
});
}
template <typename Protocol>
void create_listener(const std::string& address, api_category_set categories) {
const boost::posix_time::milliseconds accept_timeout(500);
auto extra_listening_log_info = " for API categories: " + category_names(categories);
using socket_type = typename Protocol::socket;
auto create_session = [this, categories, address](socket_type&& socket) {
std::string remote_endpoint;
if constexpr (std::is_same_v<socket_type, tcp>) {
boost::system::error_code re_ec;
auto re = socket.remote_endpoint(re_ec);
remote_endpoint = re_ec ? "unknown" : fc::to_string(re);
} else {
remote_endpoint = address;
}
std::make_shared<beast_http_session<socket_type>>(
std::move(socket), plugin_state, std::move(remote_endpoint), categories, address)
->run_session();
};
fc::create_listener<Protocol>(plugin_state->thread_pool.get_executor(), logger(), accept_timeout, address,
extra_listening_log_info,
[this](const auto&) -> boost::asio::io_context& { return plugin_state->thread_pool.get_executor(); },
create_session);
}
void create_beast_server(const std::string& address, api_category_set categories) {
try {
if (is_unix_socket_address(address)) {
namespace fs = std::filesystem;
fs::path sock_path = address;
if (sock_path.is_relative())
sock_path = fs::weakly_canonical(app().data_dir() / sock_path);
create_listener<boost::asio::local::stream_protocol>(sock_path.string(), categories);
} else {
create_listener<tcp>(address, categories);
}
} catch (const fc::exception& e) {
fc_elog(logger(), "http service failed to start for ${addr}: ${e}",
("addr", address)("e", e.to_detail_string()));
throw;
} catch (const std::exception& e) {
fc_elog(logger(), "http service failed to start for ${addr}: ${e}", ("addr", address)("e", e.what()));
throw;
} catch (...) {
fc_elog(logger(), "error thrown from http io service");
throw;
}
}
std::string addresses_for_category(api_category category) const {
std::string result;
for (const auto& [address, categories] : categories_by_address) {
if (categories.contains(category)) {
result += address;
result += " ";
}
}
return result;
}
};
http_plugin::http_plugin():my(new http_plugin_impl()){
}
http_plugin::~http_plugin() = default;
void http_plugin::set_program_options(options_description&, options_description& cfg) {
if(current_http_plugin_defaults.default_unix_socket_path.length())
cfg.add_options()
("unix-socket-path", bpo::value<string>()->default_value(current_http_plugin_defaults.default_unix_socket_path),
"The filename (relative to data-dir) to create a unix socket for HTTP RPC; set blank to disable.");
else
cfg.add_options()
("unix-socket-path", bpo::value<string>(),
"The filename (relative to data-dir) to create a unix socket for HTTP RPC; set blank to disable.");
if(current_http_plugin_defaults.default_http_port)
cfg.add_options()
("http-server-address", bpo::value<string>()->default_value("127.0.0.1:" + std::to_string(current_http_plugin_defaults.default_http_port)),
"The local IP and port to listen for incoming http connections; set blank to disable.");
else
cfg.add_options()
("http-server-address", bpo::value<string>(),
"The local IP and port to listen for incoming http connections; "
"setting to http-category-address to enable http-category-address option. leave blank to disable.");
if (current_http_plugin_defaults.support_categories) {
cfg.add_options()
("http-category-address", bpo::value<std::vector<string>>(),
"The local IP and port to listen for incoming http category connections."
" Syntax: category,address\n"
" Where the address can be <hostname>:port, <ipaddress>:port or unix socket path;\n"
" in addition, unix socket path must starts with '/', './' or '../'. When relative path\n"
" is used, it is relative to the data path.\n\n"
" Valid categories include chain_ro, chain_rw, db_size, net_ro, net_rw, producer_ro\n"
" producer_rw, snapshot, trace_api, prometheus, and test_control.\n\n"
" A single `hostname:port` specification can be used by multiple categories\n"
" However, two specifications having the same port with different hostname strings\n"
" are always considered as configuration error regardless of whether they can be resolved\n"
" into the same set of IP addresses.\n\n"
" Examples:\n"
" chain_ro,127.0.0.1:8080\n"
" chain_ro,127.0.0.1:8081\n"
" chain_rw,localhost:8081 # ERROR!, same port with different addresses\n"
" chain_rw,[::1]:8082\n"
" net_ro,localhost:8083\n"
" net_rw,server.domain.net:8084\n"
" producer_ro,/tmp/absolute_unix_path.sock\n"
" producer_rw,./relative_unix_path.sock\n"
" trace_api,:8086 # listen on all network interfaces\n\n"
" Notice that the behavior for `[::1]` is platform dependent. For system with IPv4 mapped IPv6 networking\n"
" is enabled, using `[::1]` will listen on both IPv4 and IPv6; other systems like FreeBSD, it will only\n"
" listen on IPv6. On the other hand, the specfications without hostnames like `:8086` will always listen on\n"
" both IPv4 and IPv6 on all platforms.");
}
cfg.add_options()
("access-control-allow-origin", bpo::value<string>()->notifier([this](const string& v) {
my->plugin_state->access_control_allow_origin = v;
fc_ilog( logger(), "configured http with Access-Control-Allow-Origin: ${o}",
("o", my->plugin_state->access_control_allow_origin) );
}),
"Specify the Access-Control-Allow-Origin to be returned on each request")
("access-control-allow-headers", bpo::value<string>()->notifier([this](const string& v) {
my->plugin_state->access_control_allow_headers = v;
fc_ilog( logger(), "configured http with Access-Control-Allow-Headers : ${o}",
("o", my->plugin_state->access_control_allow_headers) );
}),
"Specify the Access-Control-Allow-Headers to be returned on each request")
("access-control-max-age", bpo::value<string>()->notifier([this](const string& v) {
my->plugin_state->access_control_max_age = v;
fc_ilog( logger(), "configured http with Access-Control-Max-Age : ${o}",
("o", my->plugin_state->access_control_max_age) );
}),
"Specify the Access-Control-Max-Age to be returned on each request.")
("access-control-allow-credentials",
bpo::bool_switch()->notifier([this](bool v) {
my->plugin_state->access_control_allow_credentials = v;
if( v ) fc_ilog( logger(), "configured http with Access-Control-Allow-Credentials: true" );
})->default_value(false),
"Specify if Access-Control-Allow-Credentials: true should be returned on each request.")
("max-body-size", bpo::value<uint32_t>()->default_value(my->plugin_state->max_body_size),
"The maximum body size in bytes allowed for incoming RPC requests")
("http-max-bytes-in-flight-mb", bpo::value<int64_t>()->default_value(500),
"Maximum size in megabytes http_plugin should use for processing http requests. -1 for unlimited. 503 error response when exceeded." )
("http-max-in-flight-requests", bpo::value<int32_t>()->default_value(-1),
"Maximum number of requests http_plugin should use for processing http requests. 503 error response when exceeded." )
("http-max-response-time-ms", bpo::value<int64_t>()->default_value(15),
"Maximum time on main thread for processing a request, -1 for unlimited")
("verbose-http-errors", bpo::bool_switch()->default_value(false),
"Append the error log to HTTP responses")
("http-validate-host", boost::program_options::value<bool>()->default_value(true),
"If set to false, then any incoming \"Host\" header is considered valid")
("http-alias", bpo::value<std::vector<string>>()->composing(),
"Additionally acceptable values for the \"Host\" header of incoming HTTP requests, can be specified multiple times. Includes http/s_server_address by default.")
("http-threads", bpo::value<uint16_t>()->default_value( my->plugin_state->thread_pool_size ),
"Number of worker threads in http thread pool")
("http-keep-alive", bpo::value<bool>()->default_value(true),
"If set to false, do not keep HTTP connections alive, even if client requests.")
;
}
void http_plugin::plugin_initialize(const variables_map& options) {
try {
handle_sighup(); // setup logging
my->plugin_state->max_body_size = options.at( "max-body-size" ).as<uint32_t>();
verbose_http_errors = options.at( "verbose-http-errors" ).as<bool>();
my->plugin_state->thread_pool_size = options.at( "http-threads" ).as<uint16_t>();
EOS_ASSERT( my->plugin_state->thread_pool_size > 0, chain::plugin_config_exception,
"http-threads ${num} must be greater than 0", ("num", my->plugin_state->thread_pool_size));
auto max_bytes_mb = options.at( "http-max-bytes-in-flight-mb" ).as<int64_t>();
EOS_ASSERT( (max_bytes_mb >= -1 && max_bytes_mb < std::numeric_limits<int64_t>::max() / (1024 * 1024)), chain::plugin_config_exception,
"http-max-bytes-in-flight-mb (${max_bytes_mb}) must be equal to or greater than -1 and less than ${max}", ("max_bytes_mb", max_bytes_mb) ("max", std::numeric_limits<int64_t>::max() / (1024 * 1024)) );
if ( max_bytes_mb == -1 ) {
my->plugin_state->max_bytes_in_flight = std::numeric_limits<size_t>::max();
} else {
my->plugin_state->max_bytes_in_flight = max_bytes_mb * 1024 * 1024;
}
my->plugin_state->max_requests_in_flight = options.at( "http-max-in-flight-requests" ).as<int32_t>();
int64_t max_reponse_time_ms = options.at("http-max-response-time-ms").as<int64_t>();
EOS_ASSERT( max_reponse_time_ms == -1 || max_reponse_time_ms >= 0, chain::plugin_config_exception,
"http-max-response-time-ms must be -1, or non-negative: ${m}", ("m", max_reponse_time_ms) );
my->plugin_state->max_response_time = max_reponse_time_ms == -1 ?
fc::microseconds::maximum() : fc::microseconds( max_reponse_time_ms * 1000 );
my->plugin_state->validate_host = options.at("http-validate-host").as<bool>();
if( options.count( "http-alias" )) {
const auto& aliases = options["http-alias"].as<vector<string>>();
for (const auto& alias : aliases ) {
auto [host, port] = fc::split_host_port(alias);
my->plugin_state->valid_hosts.insert(host);
}
}
my->plugin_state->keep_alive = options.at("http-keep-alive").as<bool>();
std::string http_server_address;
if (options.count("http-server-address")) {
http_server_address = options.at("http-server-address").as<string>();
if (http_server_address.size() && http_server_address != "http-category-address") {
my->categories_by_address[http_server_address].insert(api_category::node);
}
}
if (options.count("unix-socket-path") && !options.at("unix-socket-path").as<string>().empty()) {
std::string unix_sock_path = options.at("unix-socket-path").as<string>();
if (unix_sock_path.size()) {
if (unix_sock_path[0] != '/') unix_sock_path = "./" + unix_sock_path;
my->categories_by_address[unix_sock_path].insert(api_category::node);
}
}
if (options.count("http-category-address") != 0) {
auto plugins = options["plugin"].as<std::vector<std::string>>();
auto has_plugin = [&plugins](const std::string& s) {
return std::find(plugins.begin(), plugins.end(), s) != plugins.end();
};
EOS_ASSERT(http_server_address == "http-category-address" && options.count("unix-socket-path") == 0,
chain::plugin_config_exception,
"when http-category-address is specified, http-server-address must be set as "
"`http-category-address` and `unix-socket-path` must be left unspecified");
std::map<std::string, std::string> hostnames;
auto addresses = options["http-category-address"].as<vector<string>>();
for (const auto& spec : addresses) {
auto comma_pos = spec.find(',');
EOS_ASSERT(comma_pos > 0 && comma_pos != std::string_view::npos, chain::plugin_config_exception,
"http-category-address '${spec}' does not contain a required comma to separate the category and address",
("spec", spec));
auto category_name = spec.substr(0, comma_pos);
auto category = to_category(category_name);
EOS_ASSERT(category != api_category::unknown, chain::plugin_config_exception,
"invalid category name `${name}` for http_category_address", ("name", std::string(category_name)));
EOS_ASSERT(has_plugin(category_plugin_name(category)), chain::plugin_config_exception,
"--plugin=${plugin_name} is required for --http-category-address=${spec}",
("plugin_name", category_plugin_name(category))("spec", spec));
auto address = spec.substr(comma_pos+1);
auto [host, port] = fc::split_host_port(address);
if (port.size()) {
auto [itr, inserted] = hostnames.try_emplace(port, host);
EOS_ASSERT(inserted || host == itr->second, chain::plugin_config_exception,
"unable to listen to port ${port} for both ${host} and ${prev}",
("port", port)("host", host)("prev", itr->second));
}
my->categories_by_address[address].insert(category);
}
}
my->plugin_state->server_header = current_http_plugin_defaults.server_header;
//watch out for the returns above when adding new code here
} FC_LOG_AND_RETHROW()
}
void http_plugin::plugin_startup() {
// post here because *_api_plugins that register api handlers depend on http_plugin. Since they depend on the
// http_plugin, this plugin_startup is called before any *_api_plugin::plugin_startup. The post avoid situation
// where the application is running and accepting http requests, but it doesn't have the request api handler
// registered yet.
app().executor().post(appbase::priority::high, exec_queue::read_write, [this]() {
try {
my->plugin_state->thread_pool.start( my->plugin_state->thread_pool_size, [](const fc::exception& e) {
fc_elog( logger(), "Exception in http thread pool, exiting: ${e}", ("e", e.to_detail_string()) );
app().quit();
} );
for (const auto& [address, categories]: my->categories_by_address) {
my->create_beast_server(address, categories);
}
my->listening.store(true);
} catch(fc::exception& e) {
fc_elog(logger(), "http_plugin startup fails for ${e}", ("e", e.to_detail_string()));
throw; // allow application exec() to exit with error
} catch(std::exception& e) {
fc_elog(logger(), "http_plugin startup fails for ${e}", ("e", e.what()));
throw; // allow application exec() to exit with error
} catch (...) {
fc_elog(logger(), "http_plugin startup fails, shutting down");
throw; // allow application exec() to exit with error
}
});
}
void http_plugin::handle_sighup() {
fc::logger::update( logger().get_name(), logger() );
}
void http_plugin::plugin_shutdown() {
my->plugin_state->thread_pool.stop();
fc_dlog( logger(), "exit shutdown");
}
void log_add_handler(http_plugin_impl* my, api_entry& entry) {
auto addrs = my->addresses_for_category(entry.category);
if (addrs.size())
addrs = "on " + addrs;
else
addrs = "disabled for category address not configured";
fc_ilog(logger(), "add ${category} api url: ${c} ${addrs}",
("category", from_category(entry.category))("c", entry.path)("addrs", addrs));
}
void http_plugin::add_handler(api_entry&& entry, appbase::exec_queue q, int priority, http_content_type content_type) {
log_add_handler(my.get(), entry);
std::string path = entry.path;
auto p = my->plugin_state->url_handlers.emplace(path, my->make_app_thread_url_handler(std::move(entry), q, priority, content_type));
EOS_ASSERT( p.second, chain::plugin_config_exception, "http url ${u} is not unique", ("u", path) );
}
void http_plugin::add_async_handler(api_entry&& entry, http_content_type content_type) {
log_add_handler(my.get(), entry);
std::string path = entry.path;
auto p = my->plugin_state->url_handlers.emplace(path, my->make_http_thread_url_handler(std::move(entry), content_type));
EOS_ASSERT( p.second, chain::plugin_config_exception, "http url ${u} is not unique", ("u", path) );
}
void http_plugin::post_http_thread_pool(std::function<void()> f) {
if( f )
boost::asio::post( my->plugin_state->thread_pool.get_executor(), f );
}
void http_plugin::handle_exception( const char* api_name, const char* call_name, const string& body, const url_response_callback& cb) {
try {
try {
throw;
} catch (chain::unknown_block_exception& e) {
fc_dlog( logger(), "Unknown block while processing ${api}.${call}: ${e}",
("api", api_name)("call", call_name)("e", e.to_detail_string()) );
error_results results{400, "Unknown Block", error_results::error_info(e, verbose_http_errors)};
cb( 400, fc::variant( results ));
} catch (chain::invalid_http_request& e) {
fc_dlog( logger(), "Invalid http request while processing ${api}.${call}: ${e}",
("api", api_name)("call", call_name)("e", e.to_detail_string()) );
error_results results{400, "Invalid Request", error_results::error_info(e, verbose_http_errors)};
cb( 400, fc::variant( results ));
} catch (chain::account_query_exception& e) {
fc_dlog( logger(), "Account query exception while processing ${api}.${call}: ${e}",
("api", api_name)("call", call_name)("e", e.to_detail_string()) );
error_results results{400, "Account lookup", error_results::error_info(e, verbose_http_errors)};
cb( 400, fc::variant( results ));
} catch (chain::unsatisfied_authorization& e) {
fc_dlog( logger(), "Auth error while processing ${api}.${call}: ${e}",
("api", api_name)("call", call_name)("e", e.to_detail_string()) );
error_results results{401, "UnAuthorized", error_results::error_info(e, verbose_http_errors)};
cb( 401, fc::variant( results ));
} catch (chain::tx_duplicate& e) {
fc_dlog( logger(), "Duplicate trx while processing ${api}.${call}: ${e}",
("api", api_name)("call", call_name)("e", e.to_detail_string()) );
error_results results{409, "Conflict", error_results::error_info(e, verbose_http_errors)};
cb( 409, fc::variant( results ));
} catch (fc::eof_exception& e) {
fc_elog( logger(), "Unable to parse arguments to ${api}.${call}", ("api", api_name)( "call", call_name ) );
fc_dlog( logger(), "Bad arguments: ${args}", ("args", body) );
error_results results{422, "Unprocessable Entity", error_results::error_info(e, verbose_http_errors)};
cb( 422, fc::variant( results ));
} catch (fc::exception& e) {
fc_dlog( logger(), "Exception while processing ${api}.${call}: ${e}",
("api", api_name)( "call", call_name )("e", e.to_detail_string()) );
error_results results{500, "Internal Service Error", error_results::error_info(e, verbose_http_errors)};
cb( 500, fc::variant( results ));
} catch (std::exception& e) {
fc_dlog( logger(), "STD Exception encountered while processing ${api}.${call}: ${e}",
("api", api_name)("call", call_name)("e", e.what()) );
error_results results{500, "Internal Service Error", error_results::error_info(fc::exception( FC_LOG_MESSAGE( error, e.what())), verbose_http_errors)};
cb( 500, fc::variant( results ));
} catch (...) {
fc_elog( logger(), "Unknown Exception encountered while processing ${api}.${call}",
("api", api_name)( "call", call_name ) );
error_results results{500, "Internal Service Error",
error_results::error_info(fc::exception( FC_LOG_MESSAGE( error, "Unknown Exception" )), verbose_http_errors)};
cb( 500, fc::variant( results ));
}
} catch (...) {
std::cerr << "Exception attempting to handle exception for " << api_name << "." << call_name << std::endl;
}
}
bool http_plugin::is_on_loopback(api_category category) const {
return std::all_of(my->categories_by_address.begin(), my->categories_by_address.end(),
[&category, this](const auto& entry) {
const auto& [address, categories] = entry;
return !categories.contains(category) || my->on_loopback_only(address);
});
}
// returns true if `category` is enabled in http_plugin
bool http_plugin::is_enabled(api_category category) const {
return std::any_of(my->categories_by_address.begin(), my->categories_by_address.end(),
[&category](const auto& entry) {
const auto& [address, categories] = entry;
return categories.contains(category);
});
}
bool http_plugin::verbose_errors() {
return verbose_http_errors;
}
fc::microseconds http_plugin::get_max_response_time()const {
return my->plugin_state->max_response_time;
}
size_t http_plugin::get_max_body_size()const {
return my->plugin_state->max_body_size;
}
void http_plugin::register_update_metrics(std::function<void(metrics)>&& fun) {
my->plugin_state->update_metrics = std::move(fun);
}
size_t http_plugin::requests_in_flight() const {
return my->plugin_state->requests_in_flight;
}
size_t http_plugin::bytes_in_flight() const {
return my->plugin_state->bytes_in_flight;
}
std::atomic<bool>& http_plugin::listening() {
return my->listening;
}
}