Json-rpc.js

Материал из support.qbpro.ru

оригинал https://gist.github.com/869907

КОД

var sys = require('sys');
var http = require('http');

//===----------------------------------------------------------------------===//
// Server Client
//===----------------------------------------------------------------------===//
var Client = function(port, host, user, password) {
  this.port = port;
  this.host = host;
  this.user = user;
  this.password = password;
  
  this.call = function(method, params, callback, errback, path) {
    var client = http.createClient(port, host);
    
    // First we encode the request into JSON
    var requestJSON = JSON.stringify({
      'jsonrpc': '2.0',
      'id': '' + (new Date()).getTime(),
      'method': method,
      'params': params
    });
    
    var headers = {};

    if (user && password) {
      var buff = new Buffer(this.user + ":" + this.password)
                           .toString('base64');
      var auth = 'Basic ' + buff;
      headers['Authorization'] = auth;
    }

    // Then we build some basic headers.
    headers['Host'] = host;
    headers['Content-Length'] = requestJSON.length;

    // Now we'll make a request to the server
    var request = client.request('POST', path || '/', headers);
    request.write(requestJSON);
    request.on('response', function(response) {
      // We need to buffer the response chunks in a nonblocking way.
      var buffer = '';
      response.on('data', function(chunk) {
        buffer = buffer + chunk;
      });
      // When all the responses are finished, we decode the JSON and
      // depending on whether it's got a result or an error, we call
      // emitSuccess or emitError on the promise.
      response.on('end', function() {
        var decoded = JSON.parse(buffer); // TODO: Check for invalid response from server
        if(decoded.hasOwnProperty('result')) {
          if (callback) 
            callback(null, decoded.result);
          
        } else {
          // Call error handler if it is set, otherwise call callback with error parameters
          if (errback) {
          	errback(decoded.error);
          } else if(callback) {
          	callback(decoded.error, null);
          }
       }
      });
    });
  };
}

//===----------------------------------------------------------------------===//
// Server
//===----------------------------------------------------------------------===//
function Server() {
  var self = this;
  this.functions = {};
  this.scopes = {};
  this.defaultScope = this;
  this.server = http.createServer(function(req, res) {
    Server.trace('<--', 'accepted request');
    if(req.method === 'POST') {
      self.handlePOST(req, res);
    }
    else {
      Server.handleNonPOST(req, res);
    }
  });
}


//===----------------------------------------------------------------------===//
// exposeModule
//===----------------------------------------------------------------------===//
Server.prototype.exposeModule = function(mod, object, scope) {
  var funcs = [];
  for(var funcName in object) {
    var funcObj = object[funcName];
    if(typeof(funcObj) == 'function') {
      this.functions[mod + '.' + funcName] = funcObj;
      funcs.push(funcName);

      if (scope) {
        this.scopes[mod + '.' + funcName] = scope;
      }
    }
  }
  Server.trace('***', 'exposing module: ' + mod + ' [funs: ' + funcs.join(', ') 
                + ']');
  return object;
}


//===----------------------------------------------------------------------===//
// expose
//===----------------------------------------------------------------------===//
Server.prototype.expose = function(name, func, scope) {
  Server.trace('***', 'exposing: ' + name);
  this.functions[name] = func;

  if (scope) {
    this.scopes[name] = scope;
  }
}


//===----------------------------------------------------------------------===//
// trace
//===----------------------------------------------------------------------===//
Server.trace = function(direction, message) {
  sys.puts('   ' + direction + '   ' + message);
}


//===----------------------------------------------------------------------===//
// listen
//===----------------------------------------------------------------------===//
Server.prototype.listen = function(port, host) { 
  this.server.listen(port, host);
  Server.trace('***', 'Server listening on http://' + (host || '127.0.0.1') + 
                ':' + port + '/'); 
}


//===----------------------------------------------------------------------===//
// handlePOST  разбор запроса
//===----------------------------------------------------------------------===//
Server.prototype.handlePOST = function(req, res) {
  var buffer = '';
  var self = this;
  var handle = function (buf) {
    //первоначальный разбор JSON тела запроса, узнать вообще JSON это или нет 
    var decoded = "";
    try { 
    	decoded = JSON.parse(buf);
    } catch (e) {
    	return Server.handleError(-32700, "Parse Error", null, req, res);
    }
    

    // Check for the required fields, and if they aren't there, then
    // dispatch to the handleError function.    
    //Проверка наличия полей запроса (метод, параметры, id задания)
    
    if(!(decoded.method && decoded.params && decoded.id)) {
      
      if (typeof(id) == "undefined") {
   		var id = null;
   	  } 
   	  
      return Server.handleError(-32600, "Invalid Request", decoded.id, req, res);
    }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    if(!self.functions.hasOwnProperty(decoded.method)) {
      return Server.handleError(-32601, "Method not found", decoded.id, req, res);
    }

					// Build our success handler
					var onSuccess = function(funcResp) {
					  Server.trace('-->', 'response (id ' + decoded.id + '): ' + 
									JSON.stringify(funcResp));

					  var encoded = JSON.stringify({
						'jsonrpc': '2.0',
						'result': funcResp,
						'error': null,
						'id': decoded.id
					  });
					  
					  res.writeHead(200, {'Content-Type': 'application/json',
										  'Content-Length': encoded.length});
					  res.write(encoded);
					  res.end();
					};

    Server.trace('<--', 'request (id ' + decoded.id + '): ' + 
                  decoded.method + '(' + decoded.params.join(', ') + ')');

    // Try to call the method, but intercept errors and call our
    // onFailure handler.
    var method = self.functions[decoded.method];
					var callback = function(result, errormessage) {
					  if (errormessage) {
						Server.handleError(-32602, errormessage, decoded.id, req, res);
					  } else {
						onSuccess(result);
					  }
					};
    var scope = self.scopes[decoded.method] || self.defaultScope; //новая область видимости для вызываемого метода

    // Other various information we want to pass in for the handler to be
    // able to access.
    var opt = {
      req: req,
      server: self
    };

    try {
      method.call(scope, decoded.params, opt, callback);
    } catch (err) {
      return Server.handleError(-32603, err, decoded.id, req, res);
    }

  } // function handle(buf)

  req.addListener('data', function(chunk) {
    buffer = buffer + chunk;
  });

  req.addListener('end', function() {
    handle(buffer);
  });
}

//===----------------------------------------------------------------------===//
// handleError
//===----------------------------------------------------------------------===//
Server.handleError = function(code, message, id, req, res) {
  
  var encoded = JSON.stringify({
  	'jsonrpc': '2.0',
    'error': {
    	'code':code,
    	'message':message
    },
    'id': id
  });
  
  res.writeHead(400, {'Content-Type': 'text/plain',
                      'Content-Length': encoded.length,
                      'Allow': 'POST'});
  
  res.write(encoded);
  res.end();
  
  Server.trace('-->', 'Failure: ' + code + ': ' + message);
}


//===----------------------------------------------------------------------===//
// handleNonPOST
//===----------------------------------------------------------------------===//
Server.handleNonPOST = function(req, res) {
  
  var encoded = JSON.stringify({
  	'jsonrpc': '2.0',
    'error': {
    	'code':-32600,
    	'message':"Only POST is allowed."
    },
    'id': null
  });
  
  res.writeHead(405, {'Content-Type': 'text/plain',
                      'Content-Length': encoded.length,
                      'Allow': 'POST'});
  res.write(encoded);
  res.end();
}


module.exports.Server = Server;
module.exports.Client = Client;

Модуль

function jsonrps(buf) {
	var trigger_error=0;
	this.functions = {}; //объект, содержащий все доступные функции, здесь надо читать все методы из БД в functions
	this.scopes = {}; //объект, содержащий все новые области видимости для вызываемых методов
    this.defaultScope = this; //новые область видимости по умолчинию //если нет области видимости в методе - надо сделать другой
	function error(code, message, id){
		var encoded = JSON.stringify({
			'jsonrpc': '2.0',
			'error': {
				'code':code,
			    'message':message
			    },
			'id': id
			});
		return encoded;
		}
	//первая проверка: валидность JSON вообще
	//если JSON не валидный генерируем ошибку -32700 и прекращаем функцию 
	//если JSON валидный дальше обрабатываем переменную is_json
	var is_json = "";
    
	try { 
		is_json = JSON.parse(buf);
    } 
    catch (e) {
    	trigger_error=-32700;
    	return this.error(-32700, "Parse Error", null);
    } 
	//вторая проверка: формат запроса по стандарту
    //Наличие полей запроса (метод, параметры) id задания может отсутствовать в случае уведомления
    //если id отсутствует, подставляем null всесто отсутствующего id
    //При отсутствии полей запроса (метод, параметры) генерируем ошибку -32600
    
    if(!(is_json.method && is_json.params && is_json.id)) {
      
      if (typeof(id) == "undefined") {
   		var id = null;
   	  } 
   	  
      return this.error(-32600, "Invalid Request", is_json.id);
    }    
		
	//третья проверка на наличие в functions указанного в запросе метода	
	//в случае ошибки  генерируем ошибку -32601.
	if(!self.functions.hasOwnProperty(decoded.method)) {
      return Server.handleError(-32601, "Method not found", decoded.id, req, res);
    }