Bitshares 开发者系列(2)—— Bitsharesjs-ws 库源码解读

官方接口访问库 GitHub : https://github.com/bitshares/bitsharesjs-ws
在阅读源码之前,最好先浏览一遍 Bitshares 官方公开的开发文档,虽然文档中对开发细节描述的不是很清楚,但也能对系统的通讯方式有个详细的了解。

项目库介绍


bitsharesjs-ws 是比特股的 Github 官方账号创建的一个项目,主要功能是提供一个访问比特股 API 节点的工具,与 API 节点通讯访问方式采用的是 WebSocket 方式,代码采用JS编写,可以同时运行于浏览器端与Node环境下。

项目代码结构如下:

1
2
3
4
5
+ build         * 编译版本的项目文件
+ examples * 项目库的使用案例
+ lib * 项目库源码文件
+ test * 项目单元测试
+ tools * 编译工具

直接进入 lib 目录,打开 index.js 文件阅读源码,你会发现项目库共导出三个如下模块:

1
2
3
4
5
export {
Apis, // 工具库的主要接口,对外公开了 API 的访问方式,与节点服务器的通讯全部依赖此模块的内部实现
ChainConfig, // Bitshares 区块链公开的参数配置项
Manager // 与节点服务器之间的连接管理器,负责管理与检查和节点服务器之间的连接状态
}

Apis 模块实现逻辑


Apis 模块对应的实现文件是 ApisInstances.js,该模块引入了 ChainWebSocket.jsGrapheneApi.js 等内部模块,这些内部子模块的作用稍后再作描述,先看Apis模块导出的供外部系统调用的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
export default {
/**
* String cs 代表要连接的完整钱包节点地址。
* Boolean connect 创建成功之后是否直接执行创建的对象的 connect 方法。
* int connectTimeout 连接节点时,响应超时的时间。
* Boolean enableCrypto 是否进行加密通讯。
*
* @return 该方法会返回一个 ApiInstance 的实例对象,全局共享此单例对象。
*/
instance (cs = "ws://localhost:8090", connect, connectTimeout = 4000, enableCrypto)

/**
* Function callback 设置一个当网络连接状态发生改变的时候的回调函数。
*/
setRpcConnectionStatusCallback (callback)

/**
* 设定当网络连接失败的时候,是否自动重连,会重新设置 autoReconnect 的值。
*/
setAutoReconnect (auto)

/**
* 重置当前的连接,假如当前是连接状态,会直接
*/
reset (cs = "ws://localhost:8090", connect, connectTimeout = 4000)

/**
* 获取当前连接的服务器节点的区块链 ID
*/
chainId ()

/**
* 关闭当前的连接
*/
close ()
}

查看上面导出的方法实现,可以发现 instance 方法会构建一个 ApisInstance 对象的实例,并让外部系统该通过此实例调用对应类别的API接口,关于 ApisInstance 对象的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Class ApiInstance {
chain_id // 当前连接的区块链 id
String url // 当前连接的完整钱包节点地址。
statusCb
ChainWebSocket ws_rpc // 创建的 ChainWebSocket 对象实例。
Promise init_promise
GrapheneApi _db
GrapheneApi _net
GrapheneApi _hist
GrapheneApi _crypt


/**
* 调用 instance() 的时候,第二个参数传递 true,会自动调用此函数连接区块节点,旨在初始化网络连接。
* 并且初始化 inst 内部的各个关键属性,init_promise, _db, ws_rpc ...
*/
connect (cs, connectTimeout, enableCrypto = false)
close()
db_api()
network_api()
history_api()
crypto_api()
setRpcConnectionStatusCallback()
}

此处方法的功能是与 Apis 模块提供的那些同名方法是相同的,因为 Apis 模块公开的那些方法,也是最终的中转到了此处的方法实现。重点需要注意的就是此对象的内部属性,主要有ws_rpcinit_promise_db_net_hist_crypt等。

  • ws_rpc 此属性的值是一个 ChainWebSocket 对象的实例,内部包装了 ReconnectWebSocket 对象,并定义了一套API接口的调用流程。最终由此对象向服务器节点发起 API 调用,并处理返回结果,这样一个完整的访问流程,就是此内部模块定义的。

  • init_promise 一个Promise对象,因为网络接口调用、WebSocket连接等操作都是异步响应的,所以此处定义了一个 Promise 对象来给外部系统使用,当与服务器节点之间的连接全部初始化完成之后,此异步对象就会变为完成状态。

  • _db _net _hist _crypt 这些属性都分别是一个 GrapheneApi 对象的实例,用于区分不同类别的API调用,因为 Bitshares 系统为不同的 API 接口设置有不同的 API 访问令牌,所以单独为每个 API 类型都创建了一个实例,以便外部系统调用不同类型的 API 的时候,不用反复设置 API 令牌标识。

关于 ChainWebSocket 与 ReconnectWebSocket 应该进一步解释下它们的作用,首先说一下 ReconnectWebSocket, ReconnectWebSocket 实现了在浏览器环境下的断线重连机制,因为整个模块是使用 WbeSocket 与服务节点进行通信连接的,所以可能会偶尔的发生 WebSocket 断开连接的问题。

因为当断开连接以后,可能就会导致订阅通知与API调用接口不能正常使用,这就需要外部系统自己再定义一套复杂的连接状态检查规则,来进行断线之后的连接与初始化操作。所以索性就在模块内部实现了一个 ReconnectWebSocket 模块,实现一套 Bitshares 系统专用的断线重连的制度,来保证与服务器节点连接的稳定性,减少外部系统的复杂程度,让外部系统能专心于业务逻辑的开发。

具体是如何定义重连制度的,可以自己查看 ReconnectWebSocket 模块的源码,这里不再详细介绍,但其实官方引用的 ReconnectWebSocket 在实现的时候也有一个问题,ReconnectWebSocket 仅仅是包装了 WebSocket 对象,并在内部依赖 document 与 window 对象,所以导致断线重连的制度在Nodejs的环境中是不可用的,只有在浏览器的环境下才会有断线重连保护。

ChainWebSocket 是建立在可靠的 ReconnectWebSocket 连接之上的,然后针对 Bitshares 系统的特性,构建了一个API的访问流程,和订阅通知的处理流程。外部系统的所有API操作操作都会经由 ChainWebSocket 处理并通过 WebSocket 发送到服务器节点,然后在响应结果发生以后,再由 ChainWebSocket 解析并转交给外外部系统处理。

由于在 Nodejs 的环境中是不存在 WebSocket 对象的,所以引入了一个开源的第三方 WebSocket 的实现 —— ws:

1
2
3
4
5
6
7
if (typeof WebSocket === "undefined" && !process.env.browser) {
WebSocketClient = require("ws");
} else if (typeof(WebSocket) !== "undefined" && typeof document !== "undefined") {
WebSocketClient = require("ReconnectingWebSocket")
} else {
WebSocketClient = WebSocket;
}

那么 Apis 模块的功能就已经全部介绍完了,梳理一下你的 API 访问流程应该是这样的:

通过 Apis.instance 连接操作,告诉模块要到的服务器节点,并且配置是否开启断线重连制度。

然后通过返回的 instance 对象属性 init_promise 来指定连接成功之后的下一步操作。

假如你在连接成功之后通过 instance.__db 属性调用了 get_account API,那么你的请求会经由 GrapheneApi 打上对应的API令牌标识,再投递给 ChainWebSocket 发送请求,当响应结果到来的时候,又会回调你的处理函数来完成一个完整的API请求流程。

Manager 模块实现逻辑


辅助 Apis 模块实现 WebSocket 连接的管理,更详细的功能不再描述,可以自行查看源码。

Manager 模块实现逻辑


定义了一些 Bitshares 链上的一些属性配置,我目前对 Bitshares 系统的其他细节还不了解,就不作解读说明了,等以后学习了更多的知识,再补充这些内容供大家参考。

总结


bitsharesjs-ws 针对 Bitshares 系统,定义了一套 API 调用的流程,并拓展了 WebSocket 的功能来维持一个稳定的连接通道,让外部系统能够专心于业务 API 的开发,但在其模块职责领域上,还应该有很多的可拓展空间,在此感谢此模块开发者为我们提供了一个好用的工具库,并节省了我们宝贵的时间。