【劉文彬】RPC的基礎(chǔ):調(diào)研EOS插件http_plugin

原文鏈接:https://www.cnblogs.com/Evsward/p/httpPlugin.html

區(qū)塊鏈的應(yīng)用是基于http服務(wù)吁津,這種能力在EOS中是依靠http_plugin插件賦予的碍脏。
關(guān)鍵字:通訊模式糊探,add_api褥紫,http server,https server儡炼,unix server,io_service,socket秆麸,connection

通訊模式

EOS中,一個插件的使用要先獲取其實例,例如http_plugin獲取實例的語句是:

auto& _http_plugin = app().get_plugin<http_plugin>();

其他插件的獲取方式與此相同。目前為止凌蔬,包括前文介紹到的method、channel、信號槽译暂、信號量,跨模塊的交互方式可以總結(jié)為五種:

  • method,插件之間的調(diào)用啦撮,一個插件A將其函數(shù)按key注冊到method池中劫乱,其他任意數(shù)量的插件B狭吼、C、D均可通過key去method池中找到該函數(shù)并調(diào)用。這種通訊模式是一個由調(diào)用者主動發(fā)起的過程摘悴。
  • channel,插件之間的調(diào)用,一個插件A按key找到頻道并向頻道publish一個動作,其他任意數(shù)量的插件B粪小、C、D,甚至在不同節(jié)點上的插件B、C姿骏、D,只要是按key訂閱了該channel并綁定了他們各自本地的一個notify function,就會被觸發(fā)執(zhí)行。這種通訊模式是基于發(fā)布訂閱模式,或者說是更高級的觀察者模式旺坠,是由發(fā)布者的行為交由channel來觸發(fā)所有訂閱者綁定的本地通知函數(shù)的過程出刷。
  • 信號槽,插件與controller的交互過程改抡。controller下啟基于chainbase的狀態(tài)數(shù)據(jù)庫,上承信號的管理,通過信號來與外部進(jìn)行交互藐窄,controller會根據(jù)鏈的行為emit一個對應(yīng)的信號出來诺凡,其他插件如果有處理該信號的需求會連接connect該信號并綁定函數(shù)實現(xiàn)。有時候一個信號會被多個插件所連接,例如accepted_block_header信號,是承認(rèn)區(qū)塊頭的信號,會被net_plugin捕捉并處理,同時該信號也會被chain_plugin所捕捉害幅,觸發(fā)廣播。
  • 信號量,一般是應(yīng)用程序與操作系統(tǒng)發(fā)生的交互枢里,在EOS中栏豺,應(yīng)用程序的實例是application,它與操作系統(tǒng)發(fā)生的交互都是通過信號量來完成,首先聲明一個信號,然后通過async_wait觸發(fā)信號完成與操作系統(tǒng)的交互。
  • 實例調(diào)用谭贪,對比以上四種松散的方式,這種模式是強(qiáng)關(guān)聯(lián),正如我們剛剛學(xué)習(xí)編程時喜歡使用new/create而不考慮對象的垃圾處理以及實例管理,后來會采用解耦的松散的統(tǒng)一實例管理框架宙橱,或者采用單例而不是每次都要new/create环葵。但這種方式并不是完全不被推薦的菊卷,當(dāng)實例的某個成員直接被需要時,可以直接通過該方式獲取到扑眉,而不是通過以上四種方式來使用雪营。

目前總結(jié)出來的五種跨模塊交互方式,前四種更注重通訊,最后一種更注重其他模塊的內(nèi)容扶歪。更注重通訊的前四種是基于同一底層通訊機(jī)制(socket)年枕,但適用于不同場景的設(shè)計實現(xiàn)树姨。

add_api函數(shù)

從chain_api_plugin過來,http_plugin的使用方式是:

_http_plugin.add_api({
      CHAIN_RO_CALL(get_info, 200l),
      ...
   });

那么,就從add_api入手研究http_plugin。add_api函數(shù)聲明在http_plugin頭文件中裙盾,說明該函數(shù)的內(nèi)容很少或很具備通用性钢属。

void add_api(const api_description& api) {
   for (const auto& call : api)
      add_handler(call.first, call.second);
}

從前面的調(diào)用代碼可以看出,add_api函數(shù)的參數(shù)是一個對象集合,它們總體是一個api_description類型的常量引用台颠。

using api_description = std::map<string, url_handler>;

api_description根據(jù)源碼可知是一個map实蔽,key為string類型的url路徑地址漆腌,值為url_handler是具體實現(xiàn)API功能的處理函數(shù)。在add_api的調(diào)用部分,宏CHAIN_RO_CALL調(diào)用了另一個宏CALL劳景,CALL組裝了map的這兩個數(shù):

#define CALL(api_name, api_handle, api_namespace, call_name) \
{std::string("/v1/" #api_name "/" #call_name), \
   [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(200, fc::json::to_string(result)); \
          } catch (...) { \
             http_plugin::handle_exception(#api_name, #call_name, body, cb); \
          } \
       }}

CALL宏體包含兩個數(shù)據(jù),以逗號隔開,前面部分為url路徑地址,后面部分為api_handler,此處實際上是一個匿名內(nèi)部函數(shù)茎杂』壤回到add_api函數(shù)的聲明悼粮,遍歷整個api曾棕,逐一執(zhí)行add_handler為url和api處理函數(shù)添加相互綁定的關(guān)系翘地。

add_handler函數(shù)

直接進(jìn)入函數(shù)實現(xiàn)的代碼:

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));
  });
}

app()前文講到了,是用來獲取application實例的勺远,其包含一個public權(quán)限的成員函數(shù)get_io_service:

boost::asio::io_service& get_io_service() { return *io_serv; }

返回的是基于boost::asio::io_service庫的共享指針類型胶逢,application的私有成員io_serv的指針饰潜。

io_service是asio框架中的調(diào)度器彭雾,用來調(diào)度異步事件蜜托,application實例要保存一個io_service對象嗓化,用于保存當(dāng)前實例的所有待調(diào)度的異步事件驳糯。

io_service的兩個重要方法:

  • post,用于發(fā)布一個異步事件古胆,依賴asio庫進(jìn)行自動調(diào)度陨帆,不需要顯式調(diào)用函數(shù)亥鸠。
  • run,顯式調(diào)用哨坪,同步執(zhí)行回調(diào)函數(shù)拧篮。

當(dāng)appbase.exec()執(zhí)行時慧妄,io_service會同步啟動运挫,如果一個插件需要IO或其他異步操作关划,可以通過以下方式進(jìn)行分發(fā):

app().get_io_service().post( lambda )

那么,這種分發(fā)方式伦籍,除了在http_plugin的add_handler函數(shù)中使用到,EOSIO/eos中在bnet_plugin插件中有大量使用到垒迂,緣于bnet_plugin對異步事件發(fā)布的需求唯竹〔希回到add_handler函數(shù),post后面跟隨的是lambda表達(dá)式窍霞,[=]代表捕獲所有以值訪問的局部名字。lambda體是將url和handler作為二元組插入到http_plugin_impl對象的唯一指針my的共有成員url_handlers集合中,數(shù)據(jù)類型與上面的api_description一致攀唯。

url_handlers集合

url_handlers集合的數(shù)據(jù)源是其他插件通過add_api函數(shù)傳入組裝好的url和handler的對象。該集合作為api的異步處理器集合玉掸,在http_plugin中消費該集合數(shù)據(jù)的是handle_http_request函數(shù)窟却。該函數(shù)處理外部請求茬腿,根據(jù)請求url在url_handlers集合中查找數(shù)據(jù)弓候,找到handler以后,傳入外部參數(shù)數(shù)據(jù)并執(zhí)行handler對應(yīng)的處理函數(shù)。

handle_http_request函數(shù)

/**
 * 處理一個http請求(http_plugin)
 * @tparam T socket type
 * @param con 連接對象
 */
template<class T>
void handle_http_request(typename websocketpp::server<T>::connection_ptr con) {
    try {
       auto& req = con->get_request(); // 獲得請求對象req恬惯。
       if(!allow_host<T>(req, con))// 檢查host地址是否有效
          return;
       // 根據(jù)config.ini中http_plugin相關(guān)的連接配置項進(jìn)行設(shè)置。
       if( !access_control_allow_origin.empty()) {
          con->append_header( "Access-Control-Allow-Origin", access_control_allow_origin );
       }
       if( !access_control_allow_headers.empty()) {
          con->append_header( "Access-Control-Allow-Headers", access_control_allow_headers );
       }
       if( !access_control_max_age.empty()) {
          con->append_header( "Access-Control-Max-Age", access_control_max_age );
       }
       if( access_control_allow_credentials ) {
          con->append_header( "Access-Control-Allow-Credentials", "true" );
       }
       if(req.get_method() == "OPTIONS") { // HTTP method包含:`GET` `HEAD` `POST` `OPTIONS` `PUT` `DELETE` `TRACE` `CONNECT`
          con->set_status(websocketpp::http::status_code::ok);
          return;// OPTIONS不能緩存竟终,未能獲取到請求的資源切蟋。
       }
    
       con->append_header( "Content-type", "application/json" );// 增加請求頭柄粹。
       auto body = con->get_request_body(); // 獲得請求體(請求參數(shù))
       auto resource = con->get_uri()->get_resource(); // 獲得請求的路徑(url)
       auto handler_itr = url_handlers.find( resource ); // 在url_handlers集合中找到對應(yīng)的handler
       if( handler_itr != url_handlers.end()) {
          con->defer_http_response();// 延時響應(yīng)
          // 調(diào)用handler什黑,傳入?yún)?shù)堪夭、url,回調(diào)函數(shù)是lambda表達(dá)式,用于將接收到的結(jié)果code和響應(yīng)body賦值給連接圣絮。
          handler_itr->second( resource, body, [con]( auto code, auto&& body ) {
             con->set_body( std::move( body )); // 接收到的響應(yīng)body賦值給連接扮匠。
             con->set_status( websocketpp::http::status_code::value( code )); // 接收到的code賦值給連接棒搜。
             con->send_http_response();// 發(fā)送http響應(yīng)
          } );
       } else {
          dlog( "404 - not found: ${ep}", ("ep", resource)); // 未在url_handlers集合中找到
          // 針對失敗的情況力麸,設(shè)置http的響應(yīng)對象數(shù)據(jù)克蚂。
          error_results results{websocketpp::http::status_code::not_found,
                                "Not Found", error_results::error_info(fc::exception( FC_LOG_MESSAGE( error, "Unknown Endpoint" )), verbose_http_errors )};
          con->set_body( fc::json::to_string( results ));
          con->set_status( websocketpp::http::status_code::not_found );
       }
    } catch( ... ) {
       handle_exception<T>( con );
    }
}

下面來看該函數(shù)handle_http_request的使用位置埃叭。有兩處赤屋,均在http_plugin內(nèi)部:

  • create_server_for_endpoint函數(shù)类早,為websocket對象ws設(shè)置http處理函數(shù)莺奔,是一個lambda表達(dá)式令哟,lambda體為handle_http_request函數(shù)的調(diào)用屏富,傳入連接對象con蛙卤,由hdl轉(zhuǎn)換而來。另外已维,create_server_for_endpoint函數(shù)在http_plugin::plugin_startup中也有兩處調(diào)用垛耳。
  • http_plugin::plugin_startup堂鲜,插件的啟動階段缔莲,下面將分析該插件的生命周期痴奏。

http_plugin的生命周期

正如研究其他的插件一樣抛虫,學(xué)習(xí)路線離不開插件的生命周期建椰。

插件一般都是在程序入口(例如nodeos棉姐,keosd)進(jìn)行生命周期的控制的伞矩,一般不做區(qū)分乃坤,由于插件有共同基類湿诊,程序入口做統(tǒng)一控制厅须。

下面依次介紹http_plugin的生命周期朗和。

http_plugin::set_defaults

僅屬于http_plugin插件的生命周期眶拉。設(shè)置默認(rèn)值镀层,默認(rèn)值僅包含三項:

struct http_plugin_defaults {
  // 如果不為空,該項的值將在被監(jiān)聽的地址生效唱逢。作為不同配置項的前綴。
  string address_config_prefix;
  // 如果為空屋休,unix socket支持將被完全禁用坞古。如果不為空,值為data目錄的相對路徑劫樟,作為默認(rèn)路徑啟用unix socket支持痪枫。
  string default_unix_socket_path;
  // 如果不是0,HTTP將被啟用于默認(rèn)給出的端口號叠艳。如果是0,HTTP將不被默認(rèn)啟用附较。
  uint16_t default_http_port{0};
};

nodeos的set_defaults語句為:

http_plugin::set_defaults({
    .address_config_prefix = "",
    .default_unix_socket_path = "",
    .default_http_port = 8888
});

keosd的set_defaults語句為:

http_plugin::set_defaults({
    .address_config_prefix = "",
    // key_store_executable_name = "keosd";
    .default_unix_socket_path = keosd::config::key_store_executable_name + ".sock", // 默認(rèn)unix socket路徑為keosd.sock
    .default_http_port = 0
});

http_plugin::set_program_options

設(shè)置http_plugin插件的參數(shù)吃粒,構(gòu)建屬于http_plugin的配置選項,將與其他插件的配置共同組成配置文件config.ini拒课,在此基礎(chǔ)上添加--help等參數(shù)構(gòu)建程序(例如nodeos)的CLI命令行參數(shù)徐勃。同時設(shè)置參數(shù)被設(shè)置以后的處理方案。

/**
 * 生命周期 http_plugin::set_program_options
 * @param cfg 命令行和配置文件的手動配置項的并集早像,交集以命令行配置為準(zhǔn)的配置對象僻肖。
 */
void http_plugin::set_program_options(options_description&, options_description& cfg) {
   // 處理默認(rèn)set_defaults配置項。
  my->mangle_option_names();
  if(current_http_plugin_defaults.default_unix_socket_path.length())// 默認(rèn)unix socket 路徑
     cfg.add_options()
        (my->unix_socket_path_option_name.c_str(), bpo::value<string>()->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)// 設(shè)置默認(rèn)http端口
     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.");
  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.");// 端口配置為空的話禁用http
  // 根據(jù)手動配置項來設(shè)置
  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.")// 端口配置為空的話禁用http
        ("https-certificate-chain-file", bpo::value<string>(),// https的配置卢鹦,證書鏈文件
         "Filename with the certificate chain to present on https connections. PEM format. Required for https.")
        ("https-private-key-file", bpo::value<string>(),// https的配置臀脏,私鑰文件
         "Filename with https private key in PEM format. Required for https")
        ("access-control-allow-origin", bpo::value<string>()->notifier([this](const string& v) {// 跨域問題,控制訪問源
            my->access_control_allow_origin = v;
            ilog("configured http with Access-Control-Allow-Origin: ${o}", ("o", my->access_control_allow_origin));
         }),
         "Specify the Access-Control-Allow-Origin to be returned on each request.")
        ("access-control-allow-headers", bpo::value<string>()->notifier([this](const string& v) {// 控制允許訪問的http頭
            my->access_control_allow_headers = v;
            ilog("configured http with Access-Control-Allow-Headers : ${o}", ("o", my->access_control_allow_headers));
         }),
         "Specify the Access-Control-Allow-Headers to be returned on each request.")
        ("access-control-max-age", bpo::value<string>()->notifier([this](const string& v) {// 控制訪問的最大緩存age
            my->access_control_max_age = v;
            ilog("configured http with Access-Control-Max-Age : ${o}", ("o", my->access_control_max_age));
         }),
         "Specify the Access-Control-Max-Age to be returned on each request.")
        ("access-control-allow-credentials",
         bpo::bool_switch()->notifier([this](bool v) {
            my->access_control_allow_credentials = v;
            if (v) ilog("configured http with Access-Control-Allow-Credentials: true");
         })->default_value(false), // 控制訪問允許的證書
         "Specify if Access-Control-Allow-Credentials: true should be returned on each request.")
         // 最大請求體的大小冀自,默認(rèn)為1MB揉稚。
        ("max-body-size", bpo::value<uint32_t>()->default_value(1024*1024), "The maximum body size in bytes allowed for incoming RPC requests")
        // 打印http詳細(xì)的錯誤信息到日志,默認(rèn)為false凡纳,不打印窃植。
        ("verbose-http-errors", bpo::bool_switch()->default_value(false), "Append the error log to HTTP responses")
        // 校驗host,如果設(shè)置為false荐糜,任意host均為有效巷怜。默認(rèn)為true葛超,要校驗host。
        ("http-validate-host", boost::program_options::value<bool>()->default_value(true), "If set to false, then any incoming \"Host\" header is considered valid")
        // 別名延塑。另外可接受的host頭
        ("http-alias", bpo::value<std::vector<string>>()->composing(), "Additionaly acceptable values for the \"Host\" header of incoming HTTP requests, can be specified multiple times.  Includes http/s_server_address by default.");
}

http_plugin::plugin_initialize

插件初始化的操作绣张。讀取配置并做出處理。

實際上关带,在set_option_program階段也做了對配置值的讀取及轉(zhuǎn)儲處理侥涵。原因是一些默認(rèn)參數(shù),即用戶不經(jīng)常配置的選項宋雏,就不需要讀取用戶配置的選項芜飘,可以在set_option_program階段做出處理,而那些需要用戶來配置的選項則需要在初始化階段讀入并處理磨总。

初始化階段讀入的配置項包含:

  • validate_host嗦明,是否校驗host,bool類型的值蚪燕。

  • valid_hosts娶牌,添加alias別名作為有效host。

  • listen_endpoint馆纳,根據(jù)在set_option_program階段賦值的my成員http_server_address_option_name诗良,重組處理得到監(jiān)聽點,同時添加至valid_hosts鲁驶。

  • unix_endpoint鉴裹,同樣根據(jù)my成員unix_socket_path_option_name處理,得到絕對路徑賦值給unix_endpoint灵嫌。

  • 對set_option_program階段賦值的my成員https_server_address_option_name的值的處理壹罚,https的兩個配置的處理,最終重組處理寿羞,分別賦值給my成員https_listen_endpoint猖凛,https_cert_chain,https_key绪穆,以及valid_hosts辨泳。

  • max_body_size,直接賦值玖院。

    當(dāng)然在初始化階段仍舊可以配置set_option_program階段已做出處理的配置項菠红,以用戶配置為準(zhǔn)。

http_plugin::plugin_startup

在插件中难菌,啟動階段都是非常重要的生命周期试溯。它往往代碼很簡單甚至簡略,但功能性很強(qiáng)郊酒。下面來看http_plugin的啟動階段的內(nèi)容遇绞,g共分為三部分:

  • listen_endpoint键袱,本地節(jié)點的http監(jiān)聽路徑,例如127.0.0.1:8888摹闽。
  • unix_endpoint蹄咖,如果為空,unix socket支持將被完全禁用付鹿。如果不為空澜汤,值為data目錄的相對路徑,作為默認(rèn)路徑啟用unix socket支持舵匾。
  • https_listen_endpoint俊抵,https版本的本地節(jié)點http監(jiān)聽路徑,一般不設(shè)置坐梯,對應(yīng)的是配置中的https_server_address選項务蝠。

對于以上三種情況,啟動階段分別做了三種對應(yīng)的處理烛缔,首先來看最標(biāo)準(zhǔn)最常見的情況,就是基于http的本地監(jiān)聽路徑listen_endpoint:

if(my->listen_endpoint) {
    try {
        my->create_server_for_endpoint(*my->listen_endpoint, my->server); // 創(chuàng)建http服務(wù)(上面介紹到的函數(shù))轩拨。內(nèi)部調(diào)用了http請求處理函數(shù)践瓷。
        ilog("start listening for http requests");
        my->server.listen(*my->listen_endpoint);// 手動監(jiān)聽設(shè)置端點。使用設(shè)置綁定內(nèi)部接收器亡蓉。
        my->server.start_accept();// 啟動服務(wù)器的異步連接晕翠,開始監(jiān)聽:無限循環(huán)接收器。啟動服務(wù)器連接無限循環(huán)接收器砍濒。監(jiān)聽后必須調(diào)用淋肾。在底層io_service開始運行之前,此方法不會有任何效果爸邢。它可以在io_service已經(jīng)運行之后被調(diào)用樊卓。有關(guān)如何停止此驗收循環(huán)的說明,請參閱傳輸策略的文檔杠河。
    } catch ( const fc::exception& e ){
        elog( "http service failed to start: ${e}", ("e",e.to_detail_string()));
        throw;
    } catch ( const std::exception& e ){
        elog( "http service failed to start: ${e}", ("e",e.what()));
        throw;
    } catch (...) {
        elog("error thrown from http io service");
        throw;
    }
}

主要是啟動http服務(wù)的流程碌尔,包括客戶端和服務(wù)端,endpoint和server_endpoint兩個角色的啟動券敌。下面來看基于unix socket的情況unix_endpoint:

if(my->unix_endpoint) {
    try {
        my->unix_server.clear_access_channels(websocketpp::log::alevel::all);// 清除所有登陸的頻道
        my->unix_server.init_asio(&app().get_io_service());// 初始化io_service對象唾戚,io_service就是上面分析過的application的io_service對象,傳入asio初始化函數(shù)初始化asio傳輸策略待诅。在使用asio transport之前必須要init asio叹坦。
        my->unix_server.set_max_http_body_size(my->max_body_size); // 設(shè)置HTTP消息體大小的最大值,該值決定了如果超過這個值的消息體將導(dǎo)致連接斷開卑雁。
        my->unix_server.listen(*my->unix_endpoint); // 手動設(shè)置本地socket監(jiān)聽路徑募书。
        my->unix_server.set_http_handler([&](connection_hdl hdl) {// 設(shè)置http請求處理函數(shù)(注意此處不再通過create_server_for_endpoint函數(shù)來調(diào)用绪囱,因為不再需要websocket的包裝)。
           my->handle_http_request<detail::asio_local_with_stub_log>( my->unix_server.get_con_from_hdl(hdl));
        });
        my->unix_server.start_accept();// 同上锐膜,啟動server端的無限循環(huán)接收器毕箍。
    } 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;
    }
}

下面來看基于https的本地監(jiān)聽路徑https_listen_endpointd的處理:

if(my->https_listen_endpoint) {
    try {
        my->create_server_for_endpoint(*my->https_listen_endpoint, my->https_server); // 同上http的原理,只是參數(shù)換為https的值道盏。
        // 設(shè)置TLS初始化處理器而柑。當(dāng)請求一個TLS上下文使用時,將調(diào)用該TLS初始化處理器荷逞。該處理器必須返回一個有效TLS上下文媒咳,以支持當(dāng)前端點能夠初始化TLS連接。
        // connection_hdl种远,一個連接的唯一標(biāo)識涩澡。它是實現(xiàn)了一個弱引用智能指針weak_ptr指向連接對象。線程安全坠敷。通過函數(shù)endpoint::get_con_from_hdl()可以轉(zhuǎn)化為一個完整的共享指針妙同。
        my->https_server.set_tls_init_handler([this](websocketpp::connection_hdl hdl) -> ssl_context_ptr{
           return my->on_tls_init(hdl); 
        });
        ilog("start listening for https requests");
        my->https_server.listen(*my->https_listen_endpoint);// 同上http的原理,監(jiān)聽地址膝迎。
        my->https_server.start_accept();// 同上http的原理粥帚,啟動服務(wù)。
    } catch ( const fc::exception& e ){
        elog( "https service failed to start: ${e}", ("e",e.to_detail_string()));
        throw;
    } catch ( const std::exception& e ){
        elog( "https service failed to start: ${e}", ("e",e.what()));
        throw;
    } catch (...) {
        elog("error thrown from https io service");
        throw;
    }
}

unix server與server的底層實現(xiàn)是一致的限次,只是外部的包裹處理不同芒涡,https_server的類型再加上這個ssl上下文的類型指針ssl_context_ptr。他們的聲明分別是:

using websocket_server_type = websocketpp::server<detail::asio_with_stub_log<websocketpp::transport::asio::basic_socket::endpoint>>; // http server
using websocket_local_server_type = websocketpp::server<detail::asio_local_with_stub_log>; // unix server
using websocket_server_tls_type =  websocketpp::server<detail::asio_with_stub_log<websocketpp::transport::asio::tls_socket::endpoint>>; // https server
using ssl_context_ptr =  websocketpp::lib::shared_ptr<websocketpp::lib::asio::ssl::context>; // https ssl_context_ptr
HTTPS = HTTP over TLS卖漫。TLS的前身是SSL费尽。

從上面的聲明可以看出,http和https最大的不同是羊始,前者是basic_socket旱幼,后者是tls_socket,socket類型不同店枣,http是基礎(chǔ)socket速警,https是包裹了tls的socket。

http_plugin::plugin_shutdown

關(guān)閉是插件的最后一個生命周期鸯两,代碼很少闷旧,主要執(zhí)行的是資源釋放工作。

void http_plugin::plugin_shutdown() {
  if(my->server.is_listening())
     my->server.stop_listening();
  if(my->https_server.is_listening())
     my->https_server.stop_listening();
}

此處沒有unix_server的處理[#6393]钧唐。http和https都是socket忙灼,需要手動停止監(jiān)聽,啟動無限循環(huán)接收器。unix server是通過io_service來異步處理该园,底層實現(xiàn)邏輯相同酸舍,也啟動了無限循環(huán)接收器。

總結(jié)

本文首先以外部使用http_plugin的方式:add_api函數(shù)為研究入口里初,逐層深入分析啃勉。接著從整體上研究了http_plugin的生命周期,進(jìn)一步加深了對http_plugin的http/https/unix三種server的認(rèn)識双妨。


相關(guān)文章和視頻推薦

【劉杰良】使用RPC接口新建EOS賬戶 - 實戰(zhàn)

圓方圓學(xué)院匯集大批區(qū)塊鏈名師淮阐,打造精品的區(qū)塊鏈技術(shù)課程。 在各大平臺都長期有優(yōu)質(zhì)免費公開課刁品,歡迎報名收看泣特。

公開課地址:https://ke.qq.com/course/345101

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市挑随,隨后出現(xiàn)的幾起案子状您,更是在濱河造成了極大的恐慌,老刑警劉巖兜挨,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件膏孟,死亡現(xiàn)場離奇詭異,居然都是意外死亡拌汇,警方通過查閱死者的電腦和手機(jī)骆莹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來担猛,“玉大人,你說我怎么就攤上這事丢氢「盗” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵疚察,是天一觀的道長蒸走。 經(jīng)常有香客問我,道長貌嫡,這世上最難降的妖魔是什么比驻? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮岛抄,結(jié)果婚禮上别惦,老公的妹妹穿的比我還像新娘。我一直安慰自己夫椭,他們只是感情好掸掸,可當(dāng)我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般扰付。 火紅的嫁衣襯著肌膚如雪堤撵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天羽莺,我揣著相機(jī)與錄音实昨,去河邊找鬼。 笑死盐固,一個胖子當(dāng)著我的面吹牛荒给,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播闰挡,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼锐墙,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了长酗?” 一聲冷哼從身側(cè)響起溪北,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎夺脾,沒想到半個月后之拨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡咧叭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年蚀乔,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片菲茬。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡吉挣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出婉弹,到底是詐尸還是另有隱情睬魂,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布镀赌,位于F島的核電站氯哮,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏商佛。R本人自食惡果不足惜喉钢,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望良姆。 院中可真熱鬧肠虽,春花似錦、人聲如沸玛追。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至伯复,卻和暖如春慨代,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背啸如。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工侍匙, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人叮雳。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓想暗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親帘不。 傳聞我的和親對象是個殘疾皇子说莫,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)寞焙,斷路器储狭,智...
    卡卡羅2017閱讀 134,628評論 18 139
  • afinalAfinal是一個android的ioc,orm框架 https://github.com/yangf...
    passiontim閱讀 15,406評論 2 45
  • 我們在考試的時候不是要把每個題都去做捣郊,而是要把自己會做的爭取得分是不是辽狈?考試就是把我會做的都做對。這是最起碼的呛牲。然...
    鏡花水月_1987閱讀 109評論 0 0
  • 來源: 家庭醫(yī)生在線 不少疾病都存在一些飲食禁忌刮萌,氣血不足雖然嚴(yán)格意義上說并不是疾病,但要想改善氣血不足的情況娘扩,相...
    上海資訊健康閱讀 488評論 0 0
  • 今天早晨坐公交車上班着茸,一個打扮過于樸素的四十歲左右婦女拎著很重的一個大尼龍袋子上了車。很沉的緣故琐旁,她把袋子...
    貳十一天閱讀 298評論 0 0