Graphene 源码阅读 - RPC 篇 - API 注册机制

in bitshares •  5 months ago

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_apidatabase 方法来开启 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_stateadd_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 的方法的.

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!