Graphene 源码阅读 - RPC 篇 - API 注册机制
API 注册这部分感觉又是 BM 炫技的部分, 之前数据库索引篇的各种泛型, 奇异模板, 递归模板已经让我抓狂了一次, 没想到 api 注册本以为很直观的东西, 竟然也搞的那么复杂, 真是累觉不爱, 本篇不做详解, 只陈述一下概念和流程.
Graphene 的 api 被分为 login_api
, database_api
, network_broadcast_api
, history_api
, asset_api
等几类, 除了 database_api
之外都定义在 <app>/api.hpp
中, database_api
可能会因为 api 数太多所以单独放在 <app>/database_api.hpp
中.
下面自顶向下, 看一看 api 注册经过了哪些模块, 是如何被注册的.
websocket_server 与 websocket_connection
websocket_server 的作用显而易见, 它负责监听在 RPC 服务端口上, 接受客户端连接并响应客户端请求. 每当新的连接到来就会创建一个 websocket_connection 实例, 这个实例用来后续与对应的客户端通信, 这和我们所了解的原生 socket 编程中 accept
返回与客户端通信的 socket 是一样的.
websocket_server
|
|
| on_connection (创建 websocket_connection 用于和客户端通信)
|
| websocket_connection 包含 websocket_api_connection
V
websocket_api_connection (register login_api, database_api)
websocket_api_connection
上面的 websocket_server, websocket_connection 是对 websocketapp 的直接封装, 而 websocket_api_connection 则是 fc 嫌 websocket_connection 的 "状态" 表现不丰富而定义的一个类, 它作为 websocket_connection 的数据成员, 扩充了每个连接的 "状态" 信息, 在 bitshares 的 RPC 通信过程中客户端后面所能够调用的 api 可能随前面所调的 api 影响, 这些状态当然是业务相关的, 而不是 websocket 协议相关的, 所以需要 websocket_api_connection 这个扩展类来做.
一个比较典型的例子就是, 连接建立后客户端首先调用 login_api
的 database
方法来开启 database_api
的访问 (注意这里说的不是 api 访问权限, 那是另一个问题), 其次才能调用 database_api
下的各个方法.
>>> {"id":1,"method":"call","params":[2,"get_chain_id",[]]}
{"id":1, "result":"error!"}
>>> {"id":1,"method":"call","params":[1,"database",[]]}
{"id":1, "result":2}
>>> {"id":1,"method":"call","params":[2,"get_chain_id",[]]}
{"id":1, "result":"correct chain id"}
websocket_api_connection 的核心成员如下:
fc::http::websocket_connection& _connection; // 指向包含它的 websocket_connection 对象
fc::rpc::state _rpc_state; // 通信状态, 包括 request, response, 递增请求 id
/// 下面这俩继承自父类 api_connection
std::vector< std::unique_ptr<generic_api> > _local_apis; // 存储所有注册进来的 api 们
std::map< uint64_t, api_id_type > _handle_to_id; // api 的 handle 实际上是 api 实例的指针, api 的 id 就是它注册进 _local_apis 的序号. 目前只在 register_api 时查重用, 不必做太多关注
fc::rpc::state
websocket_api_connection 的成员 _rpc_state
维护了与客户端通信的 request/response 队列, 以及消息 id 的自增. websocket_api_connection 在构建时就会调用 _rpc_state
的 add_method
方法, 添加三个方法, 分别是 "call", "notice", "callback". 这三个字段就是我们在抓包时看到的 {"id":1,"method":"call","params":[2,"get_chain_id",[]]}
中的 method
字段的值.
这三个方法的 handlers 分别是三个 lambda 定义的回调函数, 在 "call" 的回调函数中, 会解析 rpc json 消息中的 params
字段, 取出 api_id, 方法名和参数去掉用实际的 api, 这是一个复杂的反射过程, 后面会介绍.
fc::api 与 generic_api
websocket_api_connection 除了调用 _rpc_state
添加那三个方法外, 还会负责注册一下 login_api
--- 因为总得让客户端有最初可调用的 api 不是嘛!
注册由 websocket_api_connection::register_api
方法负责, 但是在注册之前, login_api
, database_api
等 api 需要用 fc::api
包装一下, fc::api
中定义了一些宏, 为每个 api 定义了 vtable
类型, vtable
里定义了每个 api 的 visit 函数, visit 函数会将 api 中的方法们用传入的 visitor 问候个遍. fc::api
还重载了 ->
操作符, 使得对 fc::api
的调用都会变成对对应的 vtable 的调用. 被包装过的 login_api
记做 fc::api<login_api>
.
register_api
会将 fc::api<login_api>
push 到上面说的 _local_apis
字段中, 但是, 你也看到了 _local_apis
是个向量, 成员类型是 generic_api
. 是的, 这里还有一层转换, 就是 fc::api<login_api>
到 generic_api
的转换.
我们先来看 generic_api 的核心成员们:
fc::any _api; // 指向实际的 api, 比如 login_api
std::vector< std::function<variant(const variants&)> > _methods; // api 中的方法们
std::map< std::string, uint32_t > _by_name; // 记录方法名 => 方法 id 的映射, 方法 id 实际上就是
generic_api::api_visitor 子类型 // 子类中包含反指向 generic_api 的成员
然后再来看这步转换, 转换在 generic_api
的构造函数中可以窥知一二, 在这里 fc::api<login_api>
的 visitor 接口被调用, 传入的 visitor 是 generic_api::api_visitor
这个访问者, 这个访问者会将 fc::api<login_api>
中的方法们塞入 _methods
字段, 但是, 你又看到了, _methods
的元素类型是 std::function<variant(const variants&)>
, 这里又涉及到一步转换, 就是讲 fc::api<login_api>
的方法们通过 to_generic
转换成 "通用方法", 而 to_generic
是个模板函数, 其模板参数也很复杂, 看代码时要特别留意 api 下的各个方法对应哪个 to_generic
.
比如说 login_api::database()
这个方法, 这个方法的签名是:
fc::api<database_api, Transform = identity_member> login_api::database()const
而在 fc::api
的宏作用下, 这个方法的签名会变成:
std::function<fc::api<database_api, Transform = identity_member>(Args...)> login_api::database()const
以这个函数签名做参数调用 to_generic
的话, 匹配的会是如下这个变种. 这里特别注意一下这个方法的最后一句是一个 register_api
调用, 这一句不是每个 to_generic
变种都有的, 只有 login_api
下那些返回 fc::api<xxxx>
的方法才会匹配到下面这个 to_generic
方法. 这一点很重要, 这体现了客户端通过调用 login_api
的各个方法来打开对其它 api 访问通道 (再提醒一句这里指的不是 api 的访问权限, api 访问权限由另外的逻辑保证).
393 template<typename Interface, typename Adaptor, typename ... Args>
394 std::function<variant(const fc::variants&)> generic_api::api_visitor::to_generic(
395 const std::function<fc::api<Interface,Adaptor>(Args...)>& f )const
396 {
397 auto api_con = _api_con;
398 auto gapi = &_api;
399 return [=]( const variants& args ) {
400 auto con = api_con.lock();
401 FC_ASSERT( con, "not connected" );
402
403 auto api_result = gapi->call_generic( f, args.begin(), args.end(), con->_max_conversion_depth );
404 return con->register_api( api_result );
405 };
406 }
结语
至此, 我们看到了, api 的注册实际上就是注册进了 websocket_api_connection 的 _local_apis
字段, 进而每个方法注册进了 generic_api 的 _methods
字段.
本文到此为止, 下文将介绍 websocket_server 是如何将收到的调用请求翻译成相应 api 的方法的.