Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multiple http endpoints #1137

Merged
merged 14 commits into from May 15, 2023
18 changes: 10 additions & 8 deletions plugins/chain_api_plugin/chain_api_plugin.cpp
Expand Up @@ -41,8 +41,9 @@ parse_params<chain_apis::read_only::get_transaction_status_params, http_params_t
}
}

#define CALL_WITH_400(api_name, api_handle, api_namespace, call_name, http_response_code, params_type) \
#define CALL_WITH_400(api_name, category, api_handle, api_namespace, call_name, http_response_code, params_type) \
heifner marked this conversation as resolved.
Show resolved Hide resolved
{std::string("/v1/" #api_name "/" #call_name), \
api_category::category,\
[api_handle](string&&, string&& body, url_response_callback&& cb) mutable { \
auto deadline = api_handle.start(); \
try { \
Expand All @@ -55,13 +56,13 @@ parse_params<chain_apis::read_only::get_transaction_status_params, http_params_t
} \
}}

#define CHAIN_RO_CALL(call_name, http_response_code, params_type) CALL_WITH_400(chain, ro_api, chain_apis::read_only, call_name, http_response_code, params_type)
#define CHAIN_RW_CALL(call_name, http_response_code, params_type) CALL_WITH_400(chain, rw_api, chain_apis::read_write, call_name, http_response_code, params_type)
#define CHAIN_RO_CALL_POST(call_name, call_result, http_response_code, params_type) CALL_WITH_400_POST(chain, ro_api, chain_apis::read_only, call_name, call_result, http_response_code, params_type)
#define CHAIN_RO_CALL_ASYNC(call_name, call_result, http_response_code, params_type) CALL_ASYNC_WITH_400(chain, ro_api, chain_apis::read_only, call_name, call_result, http_response_code, params_type)
#define CHAIN_RW_CALL_ASYNC(call_name, call_result, http_response_code, params_type) CALL_ASYNC_WITH_400(chain, rw_api, chain_apis::read_write, call_name, call_result, http_response_code, params_type)
#define CHAIN_RO_CALL(call_name, http_response_code, params_type) CALL_WITH_400(chain, chain_ro, ro_api, chain_apis::read_only, call_name, http_response_code, params_type)
#define CHAIN_RW_CALL(call_name, http_response_code, params_type) CALL_WITH_400(chain, chain_rw, rw_api, chain_apis::read_write, call_name, http_response_code, params_type)
#define CHAIN_RO_CALL_POST(call_name, call_result, http_response_code, params_type) CALL_WITH_400_POST(chain, chain_ro, ro_api, chain_apis::read_only, call_name, call_result, http_response_code, params_type)
#define CHAIN_RO_CALL_ASYNC(call_name, call_result, http_response_code, params_type) CALL_ASYNC_WITH_400(chain, chain_ro, ro_api, chain_apis::read_only, call_name, call_result, http_response_code, params_type)
#define CHAIN_RW_CALL_ASYNC(call_name, call_result, http_response_code, params_type) CALL_ASYNC_WITH_400(chain, chain_rw, rw_api, chain_apis::read_write, call_name, call_result, http_response_code, params_type)

#define CHAIN_RO_CALL_WITH_400(call_name, http_response_code, params_type) CALL_WITH_400(chain, ro_api, chain_apis::read_only, call_name, http_response_code, params_type)
#define CHAIN_RO_CALL_WITH_400(call_name, http_response_code, params_type) CALL_WITH_400(chain, chain_ro, ro_api, chain_apis::read_only, call_name, http_response_code, params_type)

void chain_api_plugin::plugin_startup() {
ilog( "starting chain_api_plugin" );
Expand All @@ -76,7 +77,8 @@ void chain_api_plugin::plugin_startup() {
ro_api.set_shorten_abi_errors( !http_plugin::verbose_errors() );

_http_plugin.add_api( {
CHAIN_RO_CALL(get_info, 200, http_params_types::no_params)}, appbase::exec_queue::read_only, appbase::priority::medium_high);
CALL_WITH_400(chain, node, ro_api, chain_apis::read_only, get_info, 200, http_params_types::no_params)
}, appbase::exec_queue::read_only, appbase::priority::medium_high);
_http_plugin.add_api({
CHAIN_RO_CALL(get_activated_protocol_features, 200, http_params_types::possible_no_params),
CHAIN_RO_CALL_POST(get_block, fc::variant, 200, http_params_types::params_required), // _POST because get_block() returns a lambda to be executed on the http thread pool
Expand Down
1 change: 1 addition & 0 deletions plugins/db_size_api_plugin/db_size_api_plugin.cpp
Expand Up @@ -11,6 +11,7 @@ using namespace eosio;

#define CALL_WITH_400(api_name, api_handle, call_name, INVOKE, http_response_code) \
{std::string("/v1/" #api_name "/" #call_name), \
api_category::db_size, \
[api_handle](string&&, string&& body, url_response_callback&& cb) mutable { \
try { \
body = parse_params<std::string, http_params_types::no_params>(body); \
Expand Down
345 changes: 214 additions & 131 deletions plugins/http_plugin/http_plugin.cpp

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions plugins/http_plugin/include/eosio/http_plugin/api_category.hpp
@@ -0,0 +1,36 @@
#pragma once
#include <stdint.h>

namespace eosio {

enum class api_category : uint32_t {
unknown = 0,
chain_ro = 1 << 0,
chain_rw = 1 << 1,
db_size = 1 << 2,
net_ro = 1 << 3,
net_rw = 1 << 4,
producer_ro = 1 << 5,
producer_rw = 1 << 6,
snapshot = 1 << 7,
trace_api = 1 << 8,
prometheus = 1 << 9,
test_control = 1 << 10,
node = UINT32_MAX
};


class api_category_set {
uint32_t data = {};
public:
constexpr api_category_set() = default;
constexpr explicit api_category_set(api_category c) : data(static_cast<uint32_t>(c)){}
constexpr bool contains(api_category category) const { return (data & static_cast<uint32_t>(category)) != 0; }
constexpr void insert(api_category category) { data |= static_cast<uint32_t>(category); }
heifner marked this conversation as resolved.
Show resolved Hide resolved

constexpr static api_category_set all() {
return api_category_set(api_category::node);
}
};

}
Expand Up @@ -15,21 +15,19 @@ std::string get_endpoint_path(const T& endpt) { return {}; }
std::string get_endpoint_path(const stream_protocol::endpoint& endpt) { return endpt.path(); }

// Accepts incoming connections and launches the sessions
// session_type should be a subclass of beast_http_session
// protocol type must have sub types acceptor and endpoint, e.g. boost::asio::ip::tcp;
// socket type must be the socket e.g, boost::asio::ip::tcp::socket
template<typename session_type, typename protocol_type, typename socket_type>
class beast_http_listener : public std::enable_shared_from_this<beast_http_listener<session_type, protocol_type, socket_type>> {
template<typename socket_type>
class beast_http_listener : public std::enable_shared_from_this<beast_http_listener<socket_type>> {
private:
bool is_listening_ = false;

std::shared_ptr<http_plugin_state> plugin_state_;

using protocol_type = typename socket_type::protocol_type;
typename protocol_type::acceptor acceptor_;
socket_type socket_;
std::string local_address_;

boost::asio::deadline_timer accept_error_timer_;

api_category_set categories_ = {};
public:
beast_http_listener() = default;
beast_http_listener(const beast_http_listener&) = delete;
Expand All @@ -38,73 +36,16 @@ class beast_http_listener : public std::enable_shared_from_this<beast_http_liste
beast_http_listener& operator=(const beast_http_listener&) = delete;
beast_http_listener& operator=(beast_http_listener&&) = delete;

beast_http_listener(std::shared_ptr<http_plugin_state> plugin_state) : is_listening_(false), plugin_state_(std::move(plugin_state)), acceptor_(plugin_state_->thread_pool.get_executor()), socket_(plugin_state_->thread_pool.get_executor()), accept_error_timer_(plugin_state_->thread_pool.get_executor()) {}

virtual ~beast_http_listener() {
try {
stop_listening();
} catch(...) {}
};

void listen(typename protocol_type::endpoint endpoint) {
if(is_listening_) return;

// for unix sockets we should delete the old socket
if(std::is_same<socket_type, stream_protocol::socket>::value) {
::unlink(get_endpoint_path(endpoint).c_str());
}

beast::error_code ec;
// Open the acceptor
acceptor_.open(endpoint.protocol(), ec);
if(ec) {
fail(ec, "open", plugin_state_->logger, "closing port");
return;
}

// Allow address reuse
acceptor_.set_option(asio::socket_base::reuse_address(true), ec);
if(ec) {
fail(ec, "set_option", plugin_state_->logger, "closing port");
return;
}

// Bind to the server address
acceptor_.bind(endpoint, ec);
if(ec) {
fail(ec, "bind", plugin_state_->logger, "closing port");
return;
}

// Start listening for connections
auto max_connections = asio::socket_base::max_listen_connections;
fc_ilog(plugin_state_->logger, "acceptor_.listen()");
acceptor_.listen(max_connections, ec);
if(ec) {
fail(ec, "listen", plugin_state_->logger, "closing port");
return;
}
is_listening_ = true;
beast_http_listener(std::shared_ptr<http_plugin_state> plugin_state, api_category_set categories,
typename protocol_type::endpoint endpoint,
const std::string& local_address="")
: plugin_state_(std::move(plugin_state)), acceptor_(plugin_state_->thread_pool.get_executor(), endpoint),
socket_(plugin_state_->thread_pool.get_executor()), local_address_(local_address),
accept_error_timer_(plugin_state_->thread_pool.get_executor()), categories_(categories) {
}

// Start accepting incoming connections
void start_accept() {
if(!is_listening_) return;
do_accept();
}

bool is_listening() {
return is_listening_;
}

void stop_listening() {
if(is_listening_) {
plugin_state_->thread_pool.stop();
is_listening_ = false;
}
}
virtual ~beast_http_listener() {};

private:
void do_accept() {
auto self = this->shared_from_this();
acceptor_.async_accept(socket_, [self](beast::error_code ec) {
Expand All @@ -122,10 +63,12 @@ class beast_http_listener : public std::enable_shared_from_this<beast_http_liste
} else {
// Create the session object and run it
std::string remote_endpoint = boost::lexical_cast<std::string>(self->socket_.remote_endpoint());
std::make_shared<session_type>(
std::make_shared<beast_http_session<socket_type>>(
std::move(self->socket_),
self->plugin_state_,
std::move(remote_endpoint))
std::move(remote_endpoint),
self->categories_,
self->local_address_)
->run_session();
}

Expand All @@ -134,5 +77,11 @@ class beast_http_listener : public std::enable_shared_from_this<beast_http_liste
}
});
}

bool is_ip_v6_only() const {
boost::asio::ip::v6_only option;
acceptor_.get_option(option);
return option.value();
}
};// end class beast_http_Listener
}// namespace eosio