bitshares研究系列【bitshares-ui代码分析之api】已经分析过api websocket调用方式,并用wscat测试了基本调用,本篇从bitshares-ui和bitshares-core看看api的调用流程。
bitsharesjs-ws
安装方法:
npm install bitsharesjs-ws
ApiInstances.js
实现了api实例,instance()创建这样一个对象。
this.ws_rpc = new ChainWebSocket(cs, this.statusCb, connectTimeout, autoReconnect, function () {
if (_this2._db) {
_this2._db.exec('get_objects', [['2.1.0']]).catch(function (e) {});
}
});
生成一个ChainWebSocket对象,并调用database的一个命令:get_objects,实际上这条命令会取到什么数据呢?用wscat看一下:
> {"method": "call", "params": [2, "get_objects", [["2.1.0"]]], "id": 4}
< {"id":4,"jsonrpc":"2.0","result":[{"id":"2.1.0","head_block_number":26046408,"head_block_id":"018d6fc89631b618da75fcbf6519bd72a250927d","time":"2018-04-12T07:33:12","current_witness":"1.6.110","next_maintenance_time":"2018-04-12T08:00:00","last_budget_time":"2018-04-12T07:00:00","witness_budget":53900000,"accounts_registered_this_interval":36,"recently_missed_count":0,"current_aslot":26193445,"recent_slots_filled":"340282366920938463463374607431768211455","dynamic_flags":0,"last_irreversible_block_num":26046386}]}
"2.1.0" 取到的是当前区块链数据,特殊帐户对应什么数据参见这:http://docs.bitshares.org/development/blockchain/objects.html
同时也会取到其它api对象,如下:
_this2._db = new GrapheneApi(_this2.ws_rpc, "database");
_this2._net = new GrapheneApi(_this2.ws_rpc, "network_broadcast");
_this2._hist = new GrapheneApi(_this2.ws_rpc, "history");
if (enableCrypto) _this2._crypt = new GrapheneApi(_this2.ws_rpc, "crypto");
ChainWebSocket.js
文件实现了websocket连接,call函数实现了call调用,如下:
ChainWebSocket.prototype.call = function call(params) {
var _this2 = this;
if (this.ws.readyState !== 1) {
return Promise.reject(new Error('websocket state error:' + this.ws.readyState));
}
var method = params[1];
if (SOCKET_DEBUG) console.log("[ChainWebSocket] >---- call -----> \"id\":" + (this.cbId + 1), JSON.stringify(params));
this.cbId += 1;
if (method === "set_subscribe_callback" || method === "subscribe_to_market" || method === "broadcast_transaction_with_callback" || method === "set_pending_transaction_callback") {
// Store callback in subs map
this.subs[this.cbId] = {
callback: params[2][0]
};
// Replace callback with the callback id
params[2][0] = this.cbId;
}
if (method === "unsubscribe_from_market" || method === "unsubscribe_from_accounts") {
if (typeof params[2][0] !== "function") {
throw new Error("First parameter of unsub must be the original callback");
}
var unSubCb = params[2].splice(0, 1)[0];
// Find the corresponding subscription
for (var id in this.subs) {
if (this.subs[id].callback === unSubCb) {
this.unsub[this.cbId] = id;
break;
}
}
}
var request = {
method: "call",
params: params
};
request.id = this.cbId;
this.send_life = max_send_life;
return new Promise(function (resolve, reject) {
_this2.cbs[_this2.cbId] = {
time: new Date(),
resolve: resolve,
reject: reject
};
_this2.ws.send(JSON.stringify(request));
});
};
params[1] 就是method,如"set_subscribe_callback"等,对照ws命令看一下:
{"method": "call", "params": [2, "set_subscribe_callback", [5, false]], "id": 6}
this.cbId 记录了id,如果是"set_subscribe_callback"等method,会把传进来回调函数保存到this.subs的map中,并用this.cbId替换,这样websocket有订阅消息到达时,可以根据this.cbId找到对应的回调函数。代码如下:
if (method === "set_subscribe_callback" || method === "subscribe_to_market" || method === "broadcast_transaction_with_callback" || method === "set_pending_transaction_callback") {
// Store callback in subs map
this.subs[this.cbId] = {
callback: params[2][0]
};
// Replace callback with the callback id
params[2][0] = this.cbId;
}
函数最后就是通过 _this2.ws.send(JSON.stringify(request)); 发送消息了。
listener函数实现了接收消息,如下:
ChainWebSocket.prototype.listener = function listener(response) {
if (SOCKET_DEBUG) console.log("[ChainWebSocket] <---- reply ----<", JSON.stringify(response));
var sub = false,
callback = null;
if (response.method === "notice") {
sub = true;
response.id = response.params[0];
}
if (!sub) {
callback = this.cbs[response.id];
this.responseCbId = response.id;
} else {
callback = this.subs[response.id].callback;
}
if (callback && !sub) {
if (response.error) {
callback.reject(response.error);
} else {
callback.resolve(response.result);
}
delete this.cbs[response.id];
if (this.unsub[response.id]) {
delete this.subs[this.unsub[response.id]];
delete this.unsub[response.id];
}
} else if (callback && sub) {
callback(response.params[1]);
} else {
console.log("Warning: unknown websocket response: ", response);
}
};
如果response.method === "notice"就是订阅消息,会找到call时保留的回调函数 this.subs[response.id].callback 并调用。
bitsharesjs
安装方法:
npm install bitsharesjs
ChainStore.js
这个文件里有更上一层的调用,封装了很多函数,简单看下init函数中的部分代码:
if (delta < 60) {
Apis.instance().db_api().exec("set_subscribe_callback", [_this.onUpdate.bind(_this), subscribe_to_new]).then(function () {
console.log("synced and subscribed, chainstore ready");
_this.subscribed = true;
_this.subError = null;
_this.notifySubscribers();
resolve();
}).catch(function (error) {
_this.subscribed = false;
_this.subError = error;
_this.notifySubscribers();
reject(error);
console.log("Error: ", error);
});
在init中自动做了订阅的处理,Apis.instance().db_api().exec 这个函数实现,实际就是调用ChainWebSocket中的call函数,在GrapheneApi.js中,如下:
GrapheneApi.prototype.exec = function exec(method, params) {
return this.ws_rpc.call([this.api_id, method, params]).catch(function (error) {
console.log("!!! GrapheneApi error: ", method, params, error, JSON.stringify(error));
throw error;
});
};
再来看订阅消息到达时的回调函数,也就是Apis.instance().db_api().exec()的参数 _this.onUpdate.bind(_this),实际就是ChainStore.js中的onUpdate()函数,如下:
ChainStore.prototype.onUpdate = function onUpdate(updated_objects /// map from account id to objects
) {
var cancelledOrders = [];
var closedCallOrders = [];
for (var a = 0; a < updated_objects.length; ++a) {
for (var i = 0; i < updated_objects[a].length; ++i) {
var _obj = updated_objects[a][i];
if (_ChainValidation2.default.is_object_id(_obj)) {
...
} else {
this._updateObject(_obj);
}
}
}
...
this.notifySubscribers();
};
继续看一下上篇中取得的订阅消息格式:
< {"method":"notice","params":[5,[[{"id":"2.1.0","head_block_number":25995300,"head_block_id":"018ca824af5b469d8fd78dfd790b1d248e78f986","time":"2018-04-10T12:46:45","current_witness":"1.6.69","next_maintenance_time":"2018-04-10T13:00:00","last_budget_time":"2018-04-10T12:00:00","witness_budget":26900000,"accounts_registered_this_interval":51,"recently_missed_count":0,"current_aslot":26142245,"recent_slots_filled":"340282366920938463463374607431768211455","dynamic_flags":0,"last_irreversible_block_num":25995281}]]]}
params中[[{"id":"2.1.0"是一个列表的列表,对应以上代码中的两个for循环处理,如果订阅消息到达时,会执行到this._updateObject(_obj),也就是_updateObject()函数,这个函数基本只要是异步消息处理都会调用到,如下:
ChainStore.prototype._updateObject = function _updateObject(object) {
/*
* A lot of objects get spammed by the API that we don't care about, filter these out here
*/
// Transaction object
if (object.id.substring(0, transaction_prefix.length) == transaction_prefix) {
return; // console.log("not interested in transaction:", object);
} else if (object.id.substring(0, account_transaction_history_prefix.length) == account_transaction_history_prefix) {
// transaction_history object
}
......
if (notify_subscribers) {
this.notifySubscribers();
}
return current;
};
这个函数会用object.id.substring对id进行判断,就是判断"x.x.x"这个id是哪种类型,这个前级定义在文件头,看其中交易数据的定义如下:
var transaction_prefix = "2." + parseInt(impl_object_type.transaction, 10) + ".";
而这个impl_object_type在上篇文章中也已经谈到了,在ChainType.js中也有定义。
onUpdate函数最后调用的this.notifySubscribers();,实现了界面层订阅的消息通知,如下:
ChainStore.prototype.notifySubscribers = function notifySubscribers() {
var _this2 = this;
// Dispatch at most only once every x milliseconds
if (!this.dispatched) {
this.dispatched = true;
this.timeout = setTimeout(function () {
_this2.dispatched = false;
_this2.subscribers.forEach(function (callback) {
callback();
});
}, this.dispatchFrequency);
}
};
有一个subscribers列表,subscribe()函数添加订阅,unsubscribe()函数移除订阅,界面层调用方式,看BindToChainState.jsx中的调用:
componentWillMount() {
ChainStore.subscribe(this.update);
this.update();
}
componentWillUnmount() {
ChainStore.unsubscribe(this.update);
}
这篇文章分析不到bitshares-core的实现了,bitshares-ui这块api调用流程简单分析完成了。
感谢您阅读 @chaimyu 的帖子,期待您能留言交流!