美文网首页比特股教程实时更细
Graphene 源码阅读 - RPC 篇 - API 注册机制

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

作者: 聂怀远 | 来源:发表于2018-05-31 22:24 被阅读27次

    https://steemit.com/bitshares/@cifer/graphene-rpc-api

    API 注册这部分感觉又是 BM 炫技的部分, 之前数据库索引篇的各种泛型, 奇异模板, 递归模板已经让我抓狂了一次, 没想到 api 注册本以为很直观的东西, 竟然也搞的那么复杂, 真是累觉不爱, 本篇不做详解, 只陈述一下概念和流程.

    Graphene 的 api 被分为 login_api, database_api, network_broadcast_api, history_api, asset_api 等几类, 除了 database_api 之外都定义在 /api.hpp 中, database_api 可能会因为 api 数太多所以单独放在 /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 >                      _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.

    register_api 会将 fc::api push 到上面说的 _local_apis 字段中, 但是, 你也看到了 _local_apis 是个向量, 成员类型是 generic_api. 是的, 这里还有一层转换, 就是 fc::api 到 generic_api 的转换.

    我们先来看 generic_api 的核心成员们:

    fc::any                                                _api;                                  // 指向实际的 api, 比如 login_api

    std::vector< std::function >  _methods;    // api 中的方法们

    std::map< std::string, uint32_t >                      _by_name;              // 记录方法名 => 方法 id 的映射, 方法 id 实际上就是

    generic_api::api_visitor 子类型                                                        // 子类中包含反指向 generic_api 的成员

    然后再来看这步转换, 转换在 generic_api 的构造函数中可以窥知一二, 在这里 fc::api 的 visitor 接口被调用, 传入的 visitor 是 generic_api::api_visitor 这个访问者, 这个访问者会将 fc::api中的方法们塞入 _methods 字段, 但是, 你又看到了, _methods 的元素类型是 std::function, 这里又涉及到一步转换, 就是讲 fc::api 的方法们通过 to_generic 转换成 "通用方法", 而 to_generic 是个模板函数, 其模板参数也很复杂, 看代码时要特别留意 api 下的各个方法对应哪个 to_generic.

    比如说 login_api::database() 这个方法, 这个方法的签名是:

    fc::api login_api::database()const

    而在 fc::api 的宏作用下, 这个方法的签名会变成:

    std::function(Args...)> login_api::database()const

    以这个函数签名做参数调用 to_generic 的话, 匹配的会是如下这个变种. 这里特别注意一下这个方法的最后一句是一个 register_api 调用, 这一句不是每个 to_generic 变种都有的, 只有 login_api 下那些返回 fc::api 的方法才会匹配到下面这个 to_generic 方法. 这一点很重要, 这体现了客户端通过调用 login_api 的各个方法来打开对其它 api 访问通道 (再提醒一句这里指的不是 api 的访问权限, api 访问权限由另外的逻辑保证).

    393    template

    394    std::function generic_api::api_visitor::to_generic(

    395                                                const std::function(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 的方法的.

    相关文章

      网友评论

      本文标题:Graphene 源码阅读 - RPC 篇 - API 注册机制

      本文链接:https://www.haomeiwen.com/subject/rhvyjftx.html