-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Add UNIX socket HTTP server to keosd #5425
Changes from 6 commits
568d82d
66f586c
1b1c307
d269cd4
aa8a90d
bcfbdfd
da096b2
9a3a0c1
8951cfa
77d4ea1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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> | ||
|
@@ -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 { | ||
|
||
|
@@ -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>; | ||
|
||
|
@@ -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(); | ||
} | ||
|
@@ -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 ); | ||
|
@@ -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 ); | ||
|
@@ -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())); | ||
|
@@ -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), | ||
"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."); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That works for me.
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>(), | ||
|
@@ -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()); | ||
|
@@ -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()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does boost do something sane with permissions on the socket file? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" ); | ||
|
@@ -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()); | ||
|
@@ -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); | ||
|
There was a problem hiding this comment.
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.)