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();
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(argc, argv))
return INITIALIZE_FAIL;
initialize_logging();
ilog("nodeos version ${ver}", ("ver", eosio::utilities::common::itoh(static_cast(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
bool initialize(int argc, char** argv) {
return initialize_impl(argc, argv, {find_plugin()...});
}
我们看下
bool application::initialize_impl(int argc, char** argv, vector 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() 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();
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();
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();
bfs::path logconf = workaround;
if( logconf.is_relative() )
logconf = my->_config_dir / logconf;
my->_logging_conf = logconf;
workaround = options["config"].as();
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(config_file_name.make_preferred().string().c_str(),
my->_cfg_options, true), options);
if(options.count("plugin") > 0)
{
auto plugins = options.at("plugin").as>();
for(auto& arg : plugins)
{
vector 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(this)->plugin_requires([&](auto& plug){ plug.startup(); });
static_cast(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()));
auto ro_api = app().get_plugin().get_read_only_api();
auto rw_api = app().get_plugin().get_read_write_api();
app().get_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()); \
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(),\
[cb, body](const fc::static_variant& result){\
if (result.contains()) {\
try {\
result.get()->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;
/**
* 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
{
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 my;
};
可以看到add_api需要的参数是std::map
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 my;
再看下http_plugin_impl的url_handlers:
class http_plugin_impl {
public:
map url_handlers;
所以容易知道所谓路由系统其实质是一张简单的map表,key为string对象,value为匿名函数