EOS中基于http-json的RPC机制

eos中客户端跟区块链的通信采用基于http-json作为传输的RPC(Remote Procedure Call ),下面简述一下客户端通过RPC调用callA这个过程:
1)client:callA(arg1, arg2...) // client stub 存放server的地址消息,再将client的callA请求参数序列化打包成json数据格式然后通过http发送给server
2)server:callA(arg1, arg2...) // server stub 接收client发送过来的json格式参数,然后将参数反序列化解包成二进制格式,并调用本地的方法callA最终再将结果序列化成json数据格式通过http返回给client

下面以push_transaction为例:
cleos --print-request --print-response -v push action eosio.token transfer '[ "eosio", "hellohello22", "100.0000 SYS", "m" ]' -p eosio@active

客户端发出的http请求: post参数为json数据格式

POST /v1/chain/push_transaction HTTP/1.0
Host: 127.0.0.1:8888
content-length: 380
Accept: */*
Connection: close

{
  "signatures": [
    "SIG_K1_KXikhe2K5KSbuvwc38EahnxwzGA1gFcsy6AoN9tdThxbdxob3kVYQoUZGrsKC7PMraEjEBKKaFfUtoBmoEHvUntF2bDxzh"
  ],
  "compression": "none",
  "packed_context_free_data": "",
  "packed_trx": "5023d05b2c3d5d2c778e000000000100a6823403ea3055000000572d3ccdcd010000000000ea305500000000a8ed3232220000000000ea305520048d51351aa36a40420f00000000000453595300000000016d00"
}

客户端收到的响应:返回的响应为json格式

{
  "transaction_id": "c6dece9a8c2abf83d5b83e8c9c8f74179ab35c7bfeedcc0e41b1bc90681ea331",
  "processed": {
    "id": "c6dece9a8c2abf83d5b83e8c9c8f74179ab35c7bfeedcc0e41b1bc90681ea331",
    "receipt": {
      "status": "executed",
      "cpu_usage_us": 2889,
      "net_usage_words": 16
    },
    "elapsed": 76187,
    "net_usage": 128,
    "scheduled": false,
    "action_traces": [{
        "receipt": {
          "receiver": "eosio.token",
          "act_digest": "ff3f09bbc56a0c9a7a06871c2f66c723c8befa90b837e22985ab65bfc0bf7032",
          "global_sequence": 278021,
          "recv_sequence": 51,
          "auth_sequence": [[
              "eosio",
              277915
            ]
          ],
          "code_sequence": 1,
          "abi_sequence": 1
        },
        "act": {
          "account": "eosio.token",
          "name": "transfer",
          "authorization": [{
              "actor": "eosio",
              "permission": "active"
            }
          ],
          "data": {
            "from": "eosio",
            "to": "hellohello22",
            "quantity": "100.0000 SYS",
            "memo": "m"
          },
...
  }
}
  • url handler的注册
    区块链中所有需要提供http api的插件都依赖于http_plugin,http_plugin实现了一个简单http服务器(websocketpp),要提供一个url接口只需要调用http_plugin中的add_handler函数添加一个处理该url的handler
// 在handler 中需要调用的回调函数
using url_response_callback = std::function;
// 相应url的handler,http_plugin会调用
using url_handler = std::function;

void add_handler(const string& url, const url_handler&);

下面以chain_api_plugin.cpp中的push_transaction handler为例

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_RW_CALL_ASYNC(push_transaction, chain_apis::read_write::push_transaction_results, 202),
...
   });
}

push_transaction handler对应的uri为/v1/chain/push_transaction,下面push_transaction handler代码为上述CHAIN_RW_CALL_ASYNC宏展开

[this, rw_api](string, string body, url_response_callback cb) mutable { 
if (body.empty()) body = "{}"; 
// 调用本地RPC函数push_transaction
rw_api.push_transaction(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("chain", "push_transaction", body, cb); 
                } 
            } else { 
                // 没有异常的情况下相应码为202,第二个参数为返回的序列化结果
                cb(202, result.visit(async_result_visitor())); 
            } 
        }
    );
}

上述调用本地RPC函数push_transaction,第一个参数把接收到的json数据格式反序列化为push_transaction_params参数,第二个参数为一个匿名函数,push_transaction处理完之后会调用该匿名函数

  • http请求的处理
    http_plugin处理http请求,找到相应uri对于的handler然后调用,最终handler处理完成之后会调用url_response_callback返回相应码跟body
template
void handle_http_request(typename websocketpp::server>::connection_ptr con) {
   try {
...
      auto body = con->get_request_body();
      auto resource = con->get_uri()->get_resource();
     // 是否有该uri的handler
      auto handler_itr = url_handlers.find( resource );
      if( handler_itr != url_handlers.end()) {
         con->defer_http_response();
         // 调用该uri的handler,这个handler就是我们上面通过add_handler添加的handler,第三个参数url_response_callback最终处理完成之后调用(响应码,body)     
         handler_itr->second( resource, body, [con]( auto code, auto&& body ) {
            con->set_body( std::move( body ));
            con->set_status( websocketpp::http::status_code::value( code ));
            con->send_http_response();
         } );

      } 
...
   } catch( ... ) {
      handle_exception( con );
   }
}

关于eos中的参数的序列化与反序列化,eos fc中实现了反射机制,上层应用只需要为某一类型定义一个FC_REFLECT/FC_REFLECT_DERIVED就能实现该类型的序列化跟反序列化

你可能感兴趣的:(EOS中基于http-json的RPC机制)