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 プロシージャも簡易対応してみた。