Json-RPC2 ネタは以前も書いたのだけど、ブラウザから何度もリクエストする方法じゃなく、必要なプロシージャを一括要求して、全ての処理結果を得たいよね。
で、こういうのを バッチリクエストというんだけど、紹介したモジュールは バッチ処理プロシージャを自分で書いて対応しないといけない。
最初に想定していた要求&応答のJSON
requestJson: {
jsonrpc: '2.0',
method: 'batch'
params: [
// バッチ処理したい リクエストJSONの配列
{ jsonrpc: '2.0', id:1, method: 'add', params: [32, 24] },
{ jsonrpc: '2.0', id:1, method: 'sub', params: [32, 24] },
{ jsonrpc: '2.0', id:1, method: 'mul', params: [32, 24] },
{ jsonrpc: '2.0', id:1, method: 'div', params: [32, 24] }
],
id: id
}
responseJson: {
"jsonrpc":"2.0",
"result":[
{"jsonrpc":"2.0","result":{"method":"add","result":56},"id":1365840840287},
{"jsonrpc":"2.0","result":{"method":"sub","result":8},"id":1365840840287},
{"jsonrpc":"2.0","result":{"method":"mul","result":768},"id":1365840840287},
{"jsonrpc":"2.0","result":{"method":"div","result":1.3333333333333333},"id":1365840840287}
],
"error":null,
"id":1
}
※フロー制御のない試作コードは削除しました
caolan/async でフロー制御する場合
JsonRPCのコールバック仕様を変更する。
プロシージャのコールバック仕様は変わるが、フロー制御モジュールと合わせた方が扱いやすい。
- 成功時: callback(null, 'successResult')
- エラー時: callback('errorMessage')
[node-jsonrpc2/src/jsonrpc.js]
193 // Try to call the method, but intercept errors and call our
194 // onFailure handler.
195 var method = self.functions[decoded.method];
196- var callback = function(result, errormessage) {
196+ var callback = function(errormessage, result) { // caolan/async の実装に合わせる。
197 if (errormessage) {
198 Server.handleError(-32602, errormessage, decoded.id, req, res);
199 } else {
200 onSuccess(result);
201 }
202 };
追記)上記修正箇所は、caolan/async の実装というか「Nodeのコールバックに関する習慣」となりつつある様子。Fork されたコードを眺めていると、どれも コールバック関数の引数は err, rslt の順に。
で、async を使ったバッチリクエスト用プロシージャ・モジュールがこれ。
/*
jsonrpc-batch.js
Usage:
var jsonrpc = require('jsonrpc'); // 上記のカスタマイズをしたモジュール
var server = new jsonrpc.Server();
server.expose( 'batch', require('path/to/jsonrpc-batch.js') );
*/
var async = require('async');
module.exports = function(batch, conn, callback) {
var req, svr, procs, tasks, createTask, cmplTasks;
if( !batch.length || !Array.isArray(batch) ) {
// batch プロシージャにエラー通知&クライアントへ応答
return callback('cant running the batch request.');
}
svr = conn.endpoint || conn.server; // bitcoinjs/jsonrpc2 対応
procs = svr.functions;
// async モジュール用にタスクの作成
tasks = [];
createTask = function(j) {
return function doProcedure(cb){
setTimeout(function(){
var scope;
if(!(j.method && j.params && j.id)) {
return cb('request json error'); // abort this task
}
if(j.method === 'batch') {
return cb('cant call `batch` in batch procedure.');
}
if(!procs.hasOwnProperty(j.method)) {
return cb('method not found'); // abort this task
}
scope = svr.scopes[j.method] || svr.defaultScope;
try {
// バッチリクエストされたプロシージャコール
procs[j.method].call(scope, j.params, conn, cb);
} catch (err) {
cb(err.message);
}
},0);
};
};
batch.forEach(function(reqJson){
tasks.push( createTask(reqJson) );
});
// async モジュールで全タスク実行。
cmplTasks = function(err, resp){
if(err){
return callback(err); // batch プロシージャのエラー終了&クライアントへの応答
}
var rslt = {};
batch.forEach(function(p, i){
rslt[p.method] = resp[i];
});
return callback(null, rslt); // batch プロシージャの成功終了&クライアントへの応答
}
// parallel を使い、並行処理する
async.parallel(tasks, cmplTasks);
}
async.parallel には tasks = { 'hoge': taskFunction } を与えても良いのだけど、要求されたプロシージャ名がサーバーに実装されているかどうか定かでは無いので、タスク関数でチェックする方向。やむなく配列(tasks = [] )を使うことに。
クライアントへ応答するresultは、チョット変えてる。
requestJson: {
jsonrpc: '2.0',
method: 'batch'
params: [
// バッチ処理したい リクエストJSONの配列
{ jsonrpc: '2.0', id:1, method: 'add', params: [32, 24] },
{ jsonrpc: '2.0', id:1, method: 'sub', params: [32, 24] },
{ jsonrpc: '2.0', id:1, method: 'mul', params: [32, 24] },
{ jsonrpc: '2.0', id:1, method: 'div', params: [32, 24] }
],
id: id
}
// ブラウザなど、クライアントへの応答はこう変えた。
responseJson: {
"jsonrpc":"2.0",
"result":{
"add":56,
"sub":8,
"mul":768,
"div":1.3333333333333333
},
"error":null,
"id":1
}
追記)
JSON-RPC2をソケット接続でも使いたいなら、冒頭で紹介するモジュールからForkされた bitcoinjs/jsonrpc2 になるかも。
Pure JavaScript なストリーム処理できる JsonParser に依存するけど、以下の特徴がある。
- このエントリでカスタマイズを施した仕様に合わせてプロシージャを書けば、そのまま活用できる
- HTTP, Socket 両対応なハイブリッドなサーバーも可能
- authorization ヘッダーを用いた認証機能に対応した管理専用RPCサーバーにも
エントリの batch プロシージャも簡易対応してみた。

コメント