Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Add UNIX socket HTTP server to keosd #5425

Merged
merged 10 commits into from
Sep 1, 2018
145 changes: 126 additions & 19 deletions plugins/http_plugin/http_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* @copyright defined in eos/LICENSE.txt
*/
#include <eosio/http_plugin/http_plugin.hpp>
#include <eosio/http_plugin/local_endpoint.hpp>
#include <eosio/chain/exceptions.hpp>

#include <fc/network/ip.hpp>
Expand Down Expand Up @@ -43,6 +44,11 @@ namespace eosio {
using std::shared_ptr;
using websocketpp::connection_hdl;

static http_plugin_defaults current_http_plugin_defaults;

void http_plugin::set_defaults(const http_plugin_defaults config) {
current_http_plugin_defaults = config;
}

namespace detail {

Expand Down Expand Up @@ -79,9 +85,42 @@ namespace eosio {

static const long timeout_open_handshake = 0;
};

struct asio_local_with_stub_log : public websocketpp::config::asio {
typedef asio_local_with_stub_log type;
typedef asio base;

typedef base::concurrency_type concurrency_type;

typedef base::request_type request_type;
typedef base::response_type response_type;

typedef base::message_type message_type;
typedef base::con_msg_manager_type con_msg_manager_type;
typedef base::endpoint_msg_manager_type endpoint_msg_manager_type;

typedef websocketpp::log::stub elog_type;
typedef websocketpp::log::stub alog_type;

typedef base::rng_type rng_type;

struct transport_config : public base::transport_config {
typedef type::concurrency_type concurrency_type;
typedef type::alog_type alog_type;
typedef type::elog_type elog_type;
typedef type::request_type request_type;
typedef type::response_type response_type;
typedef websocketpp::transport::asio::basic_socket::local_endpoint socket_type;
};

typedef websocketpp::transport::asio::local_endpoint<transport_config> transport_type;

static const long timeout_open_handshake = 0;
};
}

using websocket_server_type = websocketpp::server<detail::asio_with_stub_log<websocketpp::transport::asio::basic_socket::endpoint>>;
using websocket_local_server_type = websocketpp::server<detail::asio_local_with_stub_log>;
using websocket_server_tls_type = websocketpp::server<detail::asio_with_stub_log<websocketpp::transport::asio::tls_socket::endpoint>>;
using ssl_context_ptr = websocketpp::lib::shared_ptr<websocketpp::lib::asio::ssl::context>;

Expand All @@ -105,9 +144,16 @@ namespace eosio {

websocket_server_tls_type https_server;

optional<asio::local::stream_protocol::endpoint> unix_endpoint;
websocket_local_server_type unix_server;

bool validate_host;
set<string> valid_hosts;

string unix_socket_path_option_name = "unix-socket-path";
string http_server_address_option_name = "http-server-address";
string https_server_address_option_name = "https-server-address";

bool host_port_is_valid( const std::string& header_host_port, const string& endpoint_local_host_port ) {
return !validate_host || header_host_port == endpoint_local_host_port || valid_hosts.find(header_host_port) != valid_hosts.end();
}
Expand Down Expand Up @@ -163,7 +209,7 @@ namespace eosio {
}

template<class T>
static void handle_exception(typename websocketpp::server<detail::asio_with_stub_log<T>>::connection_ptr con) {
static void handle_exception(typename websocketpp::server<T>::connection_ptr con) {
string err = "Internal Service error, http: ";
try {
con->set_status( websocketpp::http::status_code::internal_server_error );
Expand Down Expand Up @@ -195,18 +241,26 @@ namespace eosio {
}

template<class T>
void handle_http_request(typename websocketpp::server<detail::asio_with_stub_log<T>>::connection_ptr con) {
try {
bool is_secure = con->get_uri()->get_secure();
const auto& local_endpoint = con->get_socket().lowest_layer().local_endpoint();
auto local_socket_host_port = local_endpoint.address().to_string() + ":" + std::to_string(local_endpoint.port());
bool allow_host(const typename T::request_type& req, typename websocketpp::server<T>::connection_ptr con) {
bool is_secure = con->get_uri()->get_secure();
const auto& local_endpoint = con->get_socket().lowest_layer().local_endpoint();
auto local_socket_host_port = local_endpoint.address().to_string() + ":" + std::to_string(local_endpoint.port());

const auto& host_str = req.get_header("Host");
if (host_str.empty() || !host_is_valid(host_str, local_socket_host_port, is_secure)) {
con->set_status(websocketpp::http::status_code::bad_request);
return false;
}
return true;
}

template<class T>
void handle_http_request(typename websocketpp::server<T>::connection_ptr con) {
try {
auto& req = con->get_request();
const auto& host_str = req.get_header("Host");
if (host_str.empty() || !host_is_valid(host_str, local_socket_host_port, is_secure)) {
con->set_status(websocketpp::http::status_code::bad_request);

if(!allow_host<T>(req, con))
return;
}

if( !access_control_allow_origin.empty()) {
con->append_header( "Access-Control-Allow-Origin", access_control_allow_origin );
Expand Down Expand Up @@ -258,7 +312,7 @@ namespace eosio {
ws.set_reuse_addr(true);
ws.set_max_http_body_size(max_body_size);
ws.set_http_handler([&](connection_hdl hdl) {
handle_http_request<T>(ws.get_con_from_hdl(hdl));
handle_http_request<detail::asio_with_stub_log<T>>(ws.get_con_from_hdl(hdl));
});
} catch ( const fc::exception& e ){
elog( "http: ${e}", ("e",e.to_detail_string()));
Expand All @@ -275,17 +329,41 @@ namespace eosio {
valid_hosts.emplace(host + ":" + resolved_port_str);
}

void mangle_option_names() {
if(current_http_plugin_defaults.address_config_prefix.empty())
return;
unix_socket_path_option_name.insert(0, current_http_plugin_defaults.address_config_prefix+"-");
http_server_address_option_name.insert(0, current_http_plugin_defaults.address_config_prefix+"-");
https_server_address_option_name.insert(0, current_http_plugin_defaults.address_config_prefix+"-");
}
};

template<>
bool http_plugin_impl::allow_host<detail::asio_local_with_stub_log>(const detail::asio_local_with_stub_log::request_type& req, websocketpp::server<detail::asio_local_with_stub_log>::connection_ptr con) {
return true;
}

http_plugin::http_plugin():my(new http_plugin_impl()){}
http_plugin::~http_plugin(){}

void http_plugin::set_program_options(options_description&, options_description& cfg) {
cfg.add_options()
("http-server-address", bpo::value<string>()->default_value("127.0.0.1:8888"),
"The local IP and port to listen for incoming http connections; set blank to disable.")
my->mangle_option_names();
if(current_http_plugin_defaults.default_unix_socket_path.length())
cfg.add_options()
(my->unix_socket_path_option_name.c_str(), bpo::value<boost::filesystem::path>()->default_value(current_http_plugin_defaults.default_unix_socket_path),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to be std::string with manual parsing into bfs path, or paths with spaces are spuriously rejected. (Ancient boost defect.)

"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()
(my->http_server_address_option_name.c_str(), 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.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving blank doesn't actually seem to be possible. It generates a parse error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That works for me.

$ programs/nodeos/nodeos 2>&1 | grep listen
2018-08-29T23:42:50.438 thread-0   http_plugin.cpp:344           plugin_initialize    ] configured http to listen on 127.0.0.1:8888
2018-08-29T23:42:50.440 thread-0   http_plugin.cpp:401           plugin_startup       ] start listening for http requests
2018-08-29T23:42:50.440 thread-0   net_plugin.cpp:3010           plugin_startup       ] starting listener, max clients is 25
$ programs/nodeos/nodeos --http-server-address '' 2>&1 | grep listen
2018-08-29T23:43:46.528 thread-0   net_plugin.cpp:3010           plugin_startup       ] starting listener, max clients is 25

I also double checked and the config.ini works being blank too.

else
cfg.add_options()
(my->http_server_address_option_name.c_str(), bpo::value<string>(),
"The local IP and port to listen for incoming http connections; leave blank to disable.");

("https-server-address", bpo::value<string>(),
cfg.add_options()
(my->https_server_address_option_name.c_str(), bpo::value<string>(),
"The local IP and port to listen for incoming https connections; leave blank to disable.")

("https-certificate-chain-file", bpo::value<string>(),
Expand Down Expand Up @@ -334,8 +412,8 @@ namespace eosio {
}

tcp::resolver resolver( app().get_io_service());
if( options.count( "http-server-address" ) && options.at( "http-server-address" ).as<string>().length()) {
string lipstr = options.at( "http-server-address" ).as<string>();
if( options.count( my->http_server_address_option_name ) && options.at( my->http_server_address_option_name ).as<string>().length()) {
string lipstr = options.at( my->http_server_address_option_name ).as<string>();
string host = lipstr.substr( 0, lipstr.find( ':' ));
string port = lipstr.substr( host.size() + 1, lipstr.size());
tcp::resolver::query query( tcp::v4(), host.c_str(), port.c_str());
Expand All @@ -353,7 +431,14 @@ namespace eosio {
}
}

if( options.count( "https-server-address" ) && options.at( "https-server-address" ).as<string>().length()) {
if( options.count( my->unix_socket_path_option_name ) && !options.at( my->unix_socket_path_option_name ).as<boost::filesystem::path>().empty()) {
boost::filesystem::path sock_path = options.at(my->unix_socket_path_option_name).as<boost::filesystem::path>();
if (sock_path.is_relative())
sock_path = app().data_dir() / sock_path;
my->unix_endpoint = asio::local::stream_protocol::endpoint(sock_path.string());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does boost do something sane with permissions on the socket file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just will get the default via whatever current umask is. You'll find in the local_endpoint.hpp file I monkey with the umask to ensure the file is created 0660

}

if( options.count( my->https_server_address_option_name ) && options.at( my->https_server_address_option_name ).as<string>().length()) {
if( !options.count( "https-certificate-chain-file" ) ||
options.at( "https-certificate-chain-file" ).as<string>().empty()) {
elog( "https-certificate-chain-file is required for HTTPS" );
Expand All @@ -365,7 +450,7 @@ namespace eosio {
return;
}

string lipstr = options.at( "https-server-address" ).as<string>();
string lipstr = options.at( my->https_server_address_option_name ).as<string>();
string host = lipstr.substr( 0, lipstr.find( ':' ));
string port = lipstr.substr( host.size() + 1, lipstr.size());
tcp::resolver::query query( tcp::v4(), host.c_str(), port.c_str());
Expand Down Expand Up @@ -413,6 +498,28 @@ namespace eosio {
}
}

if(my->unix_endpoint) {
try {
my->unix_server.clear_access_channels(websocketpp::log::alevel::all);
my->unix_server.init_asio(&app().get_io_service());
my->unix_server.set_max_http_body_size(my->max_body_size);
my->unix_server.listen(*my->unix_endpoint);
my->unix_server.set_http_handler([&](connection_hdl hdl) {
my->handle_http_request<detail::asio_local_with_stub_log>( my->unix_server.get_con_from_hdl(hdl));
});
my->unix_server.start_accept();
} catch ( const fc::exception& e ){
elog( "unix socket service failed to start: ${e}", ("e",e.to_detail_string()));
throw;
} catch ( const std::exception& e ){
elog( "unix socket service failed to start: ${e}", ("e",e.what()));
throw;
} catch (...) {
elog("error thrown from unix socket io service");
throw;
}
}

if(my->https_listen_endpoint) {
try {
my->create_server_for_endpoint(*my->https_listen_endpoint, my->https_server);
Expand Down
16 changes: 16 additions & 0 deletions plugins/http_plugin/include/eosio/http_plugin/http_plugin.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,19 @@ namespace eosio {
*/
using api_description = std::map<string, url_handler>;

struct http_plugin_defaults {
//If not empty, this string is prepended on to the various configuration
// items for setting listen addresses
string address_config_prefix;
//If empty, unix socket support will be completely disabled. If not empty,
// unix socket support is enabled with the given default path (treated relative
// to the datadir)
string default_unix_socket_path;
//If non 0, HTTP will be enabled by default on the given port number. If
// 0, HTTP will not be enabled by default
uint16_t default_http_port{0};
};

/**
* This plugin starts an HTTP server and dispatches queries to
* registered handles based upon URL. The handler is passed the
Expand All @@ -60,6 +73,9 @@ namespace eosio {
http_plugin();
virtual ~http_plugin();

//must be called before initialize
static void set_defaults(const http_plugin_defaults config);

APPBASE_PLUGIN_REQUIRES()
virtual void set_program_options(options_description&, options_description& cfg) override;

Expand Down
Loading