EOS節點初始化和路由系統的建立(環境Centos7.4)
nodeos是負責跟鏈打交道的模組,該模組採用外掛化的設計,其中內建了chain_plugin,http_plugin,net_plugin,producer_plugin幾個外掛,這些外掛是一定會載入的;另外一些外掛我們可以引數選擇載入,如chain_api_plugin。nodeos除了對外跟鏈打交道,對內提供http介面供命令解析模組cleos或其他自定義模組呼叫,這個工作主要是由可選外掛chain_api_plugin完成的。那現在看下介面(路由)是如何建立的。
首先在eosio\programs\nodeos\main.cpp的main函式:
int main(int argc, char** argv) { try { app().set_version(eosio::nodeos::config::version); //註冊外掛 app().register_plugin<history_plugin>(); auto root = fc::app_path(); app().set_default_data_dir(root / "eosio/nodeos/data" ); app().set_default_config_dir(root / "eosio/nodeos/config" ); //初始化外掛 if(!app().initialize<chain_plugin, http_plugin, net_plugin, producer_plugin>(argc, argv)) return INITIALIZE_FAIL; initialize_logging(); ilog("nodeos version ${ver}", ("ver", eosio::utilities::common::itoh(static_cast<uint32_t>(app().version())))); ilog("eosio root is ${root}", ("root", root.string())); //執行plugin->startup()函式 app().startup(); //設定訊號處理函式等,執行io_service的run函式,啟動任務處理 app().exec(); } catch( const extract_genesis_state_exception& e ) { return EXTRACTED_GENESIS; } catch( const fixed_reversible_db_exception& e ) { return FIXED_REVERSIBLE; } catch( const fc::exception& e ) { elog("${e}", ("e",e.to_detail_string())); ...
1 執行初始化函式initialize,執行每個外掛的初始化函式initialize
2 執行startup函式,執行每個外掛的startup函式
3 執行exec函式,完成訊號回撥函式設定,和訊息迴圈
initialize函式主要是設定一些配置檔案路徑等,執行完之後會執行外掛的initialize函式,實質還是呼叫initialize_impl
template<typename... Plugin> bool initialize(int argc, char** argv) { return initialize_impl(argc, argv, {find_plugin<Plugin>()...}); }
我們看下
bool application::initialize_impl(int argc, char** argv, vector<abstract_plugin*> autostart_plugins) { set_program_options(); bpo::variables_map options; bpo::store(bpo::parse_command_line(argc, argv, my->_app_options), options); if( options.count( "help" ) ) { cout << my->_app_options << std::endl; return false; } if( options.count( "version" ) ) { cout << my->_version << std::endl; return false; } if( options.count( "print-default-config" ) ) { print_default_config(cout); return false; } if( options.count( "data-dir" ) ) { // Workaround for 10+ year old Boost defect // See https://svn.boost.org/trac10/ticket/8535 // Should be .as<bfs::path>() but paths with escaped spaces break bpo e.g. // std::exception::what: the argument ('/path/with/white\ space') for option '--data-dir' is invalid auto workaround = options["data-dir"].as<std::string>(); bfs::path data_dir = workaround; if( data_dir.is_relative() ) data_dir = bfs::current_path() / data_dir; my->_data_dir = data_dir; } if( options.count( "config-dir" ) ) { auto workaround = options["config-dir"].as<std::string>(); bfs::path config_dir = workaround; if( config_dir.is_relative() ) config_dir = bfs::current_path() / config_dir; my->_config_dir = config_dir; } auto workaround = options["logconf"].as<std::string>(); bfs::path logconf = workaround; if( logconf.is_relative() ) logconf = my->_config_dir / logconf; my->_logging_conf = logconf; workaround = options["config"].as<std::string>(); bfs::path config_file_name = workaround; if( config_file_name.is_relative() ) config_file_name = my->_config_dir / config_file_name; if(!bfs::exists(config_file_name)) { if(config_file_name.compare(my->_config_dir / "config.ini") != 0) { cout << "Config file " << config_file_name << " missing." << std::endl; return false; } write_default_config(config_file_name); } bpo::store(bpo::parse_config_file<char>(config_file_name.make_preferred().string().c_str(), my->_cfg_options, true), options); if(options.count("plugin") > 0) { auto plugins = options.at("plugin").as<std::vector<std::string>>(); for(auto& arg : plugins) { vector<string> names; boost::split(names, arg, boost::is_any_of(" \t,")); for(const std::string& name : names) get_plugin(name).initialize(options); } } try { //這些外掛是一定會執行初始化的 for (auto plugin : autostart_plugins) if (plugin != nullptr && plugin->get_state() == abstract_plugin::registered) plugin->initialize(options); bpo::notify(options); } catch (...) { std::cerr << "Failed to initialize\n"; return false; } return true; }
下面是我們的重點,startup函式
void application::startup() {
try {
for (auto plugin : initialized_plugins)
plugin->startup();
} catch(...) {
shutdown();
throw;
}
}
實質是執行每個外掛的startup函式,我們以chain_api_plugin為例,看下路由系統的建立:
virtual void startup() override {
if(_state == initialized) {
_state = started;
static_cast<Impl*>(this)->plugin_requires([&](auto& plug){ plug.startup(); });
static_cast<Impl*>(this)->plugin_startup();
app().plugin_started(*this);
}
assert(_state == started); // if initial state was not initialized, final state cannot be started
}
首先會執行依賴外掛的startup函式,接著執行plugin_startup:
void chain_api_plugin::plugin_startup() {
ilog( "starting chain_api_plugin" );
my.reset(new chain_api_plugin_impl(app().get_plugin<chain_plugin>().chain()));
auto ro_api = app().get_plugin<chain_plugin>().get_read_only_api();
auto rw_api = app().get_plugin<chain_plugin>().get_read_write_api();
app().get_plugin<http_plugin>().add_api({
CHAIN_RO_CALL(get_info, 200l),
CHAIN_RO_CALL(get_block, 200),
CHAIN_RO_CALL(get_account, 200),
CHAIN_RO_CALL(get_code, 200),
CHAIN_RO_CALL(get_table_rows, 200),
CHAIN_RO_CALL(get_currency_balance, 200),
CHAIN_RO_CALL(get_currency_stats, 200),
CHAIN_RO_CALL(get_producers, 200),
CHAIN_RO_CALL(abi_json_to_bin, 200),
CHAIN_RO_CALL(abi_bin_to_json, 200),
CHAIN_RO_CALL(get_required_keys, 200),
CHAIN_RW_CALL_ASYNC(push_block, chain_apis::read_write::push_block_results, 202),
CHAIN_RW_CALL_ASYNC(push_transaction, chain_apis::read_write::push_transaction_results, 202),
CHAIN_RW_CALL_ASYNC(push_transactions, chain_apis::read_write::push_transactions_results, 202)
});
}
CHAIN_RO_CALL和CHAIN_RW_CALL_ASYNC是個什麼東西呢?
#define CALL(api_name, api_handle, api_namespace, call_name, http_response_code) \
{std::string("/v1/" #api_name "/" #call_name), \
[this, api_handle](string, string body, url_response_callback cb) mutable { \
try { \
if (body.empty()) body = "{}"; \
auto result = api_handle.call_name(fc::json::from_string(body).as<api_namespace::call_name ## _params>()); \
cb(http_response_code, fc::json::to_string(result)); \
} catch (...) { \
http_plugin::handle_exception(#api_name, #call_name, body, cb); \
} \
}}
#define CALL_ASYNC(api_name, api_handle, api_namespace, call_name, call_result, http_response_code) \
{std::string("/v1/" #api_name "/" #call_name), \
[this, api_handle](string, string body, url_response_callback cb) mutable { \
if (body.empty()) body = "{}"; \
api_handle.call_name(fc::json::from_string(body).as<api_namespace::call_name ## _params>(),\
[cb, body](const fc::static_variant<fc::exception_ptr, call_result>& result){\
if (result.contains<fc::exception_ptr>()) {\
try {\
result.get<fc::exception_ptr>()->dynamic_rethrow_exception();\
} catch (...) {\
http_plugin::handle_exception(#api_name, #call_name, body, cb);\
}\
} else {\
cb(http_response_code, result.visit(async_result_visitor()));\
}\
});\
}\
}
實質是一個string物件和一個匿名函式的組合結構{string,lamada func}物件,我們在看下add_api是個啥,在檔案eosio\plugins\http_plugin\include\eosio\http_plugin\http_plugin.hpp:
using api_description = std::map<string, url_handler>;
/**
* This plugin starts an HTTP server and dispatches queries to
* registered handles based upon URL. The handler is passed the
* URL that was requested and a callback method that should be
* called with the response code and body.
*
* The handler will be called from the appbase application io_service
* thread. The callback can be called from any thread and will
* automatically propagate the call to the http thread.
*
* The HTTP service will run in its own thread with its own io_service to
* make sure that HTTP request processing does not interfer with other
* plugins.
*/
class http_plugin : public appbase::plugin<http_plugin>
{
public:
http_plugin();
virtual ~http_plugin();
APPBASE_PLUGIN_REQUIRES()
virtual void set_program_options(options_description&, options_description& cfg) override;
void plugin_initialize(const variables_map& options);
void plugin_startup();
void plugin_shutdown();
void add_handler(const string& url, const url_handler&);
void add_api(const api_description& api) {
for (const auto& call : api)
add_handler(call.first, call.second);
}
// standard exception handling for api handlers
static void handle_exception( const char *api_name, const char *call_name, const string& body, url_response_callback cb );
private:
std::unique_ptr<class http_plugin_impl> my;
};
可以看到add_api需要的引數是std::map<string, url_handler>型別,結合void chain_api_plugin::plugin_startup()函式,我們容易知道用一系列組合結構{string,lamada func}物件初始化std::map<string, url_handler>,作為add_api函式的引數,在函式中,列舉std::map<string, url_handler>物件,執行add_handler:
void http_plugin::add_handler(const string& url, const url_handler& handler) {
ilog( "add api url: ${c}", ("c",url) );
app().get_io_service().post([=](){
my->url_handlers.insert(std::make_pair(url,handler));
});
}
看下my:
std::unique_ptr<class http_plugin_impl> my;
再看下http_plugin_impl的url_handlers:
class http_plugin_impl {
public:
map<string,url_handler> url_handlers;
所以容易知道所謂路由系統其實質是一張簡單的map表,key為string物件,value為匿名函式