core
Version:
Core Libs
367 lines (325 loc) • 11.2 kB
JavaScript
/**
* @overview JsonRpcRequestProcessor
* @copyright Dreamlab Onet.pl sp. z. o o 2012
*/
var RequestProcessor = require('../http/RequestProcessor.js').RequestProcessor;
var JsonRpcAbstract = require('../jsonrpc/JsonRpcAbstract.js').JsonRpcAbstract;
var JsonRpcResponse = require('../jsonrpc/JsonRpcResponse.js').JsonRpcResponse;
var JsonRpcRequest = require('../jsonrpc/JsonRpcRequest.js').JsonRpcRequest;
var JsonRpcMethod = require('../jsonrpc/JsonRpcMethod.js').JsonRpcMethod;
var Types = require('../common/Types.js').Types;
var BinaryData = require('../data/BinaryData.js').BinaryData;
/**
* @class JsonRpcRequestProcessor
* @classdesc JsonRpcRequestProcessor
* @extends RequestProcessor
*
* @requires RequestProcessor
* @requires JsonRpcAbstract
* @requires JsonRpcResponse
* @requires JsonRpcMethod
* @requires Types
* @requires BinaryData
*
* @param {Server} server
* @returns {JsonRpcRequestProcessor}
*/
var JsonRpcRequestProcessor = exports.JsonRpcRequestProcessor = function (server) {
this._request = null;
this._response = null;
this._responseData = [];
this._isBatchRequest = false;
this._methodCount = 0;
RequestProcessor.call(this, server);
};
JsonRpcRequestProcessor.prototype = Object.create(RequestProcessor.prototype);
/**
* Processes request and generates response
* @param {Request} request
* @param {Response} response
* @returns {undefined}
* @method
*/
JsonRpcRequestProcessor.prototype.process = function (request, response) {
RequestProcessor.prototype.process.call(this, request, response);
this._request = request;
this._response = response;
var jsonRpcRequests = this._getRequest();
var methods = [];
this._methodCount = jsonRpcRequests.length;
for (var i = 0, tmpMethodCount = jsonRpcRequests.length; i < tmpMethodCount; i++ ) {
this._processSingle(jsonRpcRequests[i]);
try {
methods.push(jsonRpcRequests[i].getMethod());
} catch (e) {
console.error('JsonRpcRequestProcessor/process: error:', e);
}
}
//XXX: dziala jesli jest process.nextTick
this._requestLine = '"' + this._request.getMethod() + ' ' + this._request.getPath() +
' method:' + methods.join(':') + ' ' + this._request.getHttpVersion() + '"';
};
/**
* Returns Method object for specified request
*
* @param {JsonRpcRequest} request
* @returns {JsonRpcMethod}
* @method
* @private
*/
JsonRpcRequestProcessor.prototype._getMethodClass = function (requestedMethod) {
return this.server.getMethod(requestedMethod, this._request);
};
/**
* Return reposne to caller
* @private
*/
JsonRpcRequestProcessor.prototype._prepareResponse = function () {
var tmp_rep = null;
var that = this;
if (--this._methodCount <= 0) {
// jezeli ktos ustawil explicite statusCode lub body do zwrocenia
// to znaczy ze chce nadpisac cala odpowiedz jsonRpc
// i bierze na siebie odpowiedzialnosc poskladania poprawnie calego response
// jedyne co wtedy robimy to odsylamy odpowiedz w nastepnym ticku
// jezeli nic nie zmieniono: wartosciami sa getStatusCode()==null i getBody()==null
if (!this._response.getStatusCode() || !this._response.getBody()) {
this._response.setStatusCode(200);
this._response.setHeaders({
'Content-Type': 'application/json; charset=utf8',
'Access-Control-Allow-Origin': '*'
}, true);
var responseData;
if (this._isBatchRequest === true) {
responseData = this._responseData;
} else {
responseData = this._responseData.pop();
}
this._response.setBody(new BinaryData(
this._getBody(JSON.stringify(responseData)),
BinaryData.Encoding.TEXT,
BinaryData.CharacterEncoding.UTF8));
}
process.nextTick(function() {
that._response.send();
});
}
};
/**
* Processes single request and generates response
* @param {Request} request
* @param {Response} response
* @param {Number} index
* @returns {undefined}
* @method
*/
JsonRpcRequestProcessor.prototype._processSingle = function(jsonRpcRequest) {
var method;
var methodClass;
var requestedMethod;
var requestId = jsonRpcRequest.getId();
var that = this;
try {
requestedMethod = jsonRpcRequest.getMethod();
methodClass = this._getMethodClass(requestedMethod);
} catch (ex) {
console.error('_processSingle error', ex);
this._throwError(JsonRpcRequestProcessor.Errors.INVALID_METHOD, ex, requestId);
}
if (!methodClass) {
console.warn('JsonRpcRequestProcessor/process/methodNotFound', requestedMethod);
var e = new Error(JsonRpcRequestProcessor.Errors.METHOD_NOT_FOUND);
this._throwError(JsonRpcRequestProcessor.Errors.METHOD_NOT_FOUND, e, requestId);
return;
}
method = new methodClass(this._request, this._response, this.server);
if (!Types.isInstanceOf(method, JsonRpcMethod)) {
var error = new Error(JsonRpcRequestProcessor.Errors.METHOD_NOT_FOUND);
console.error('JsonRpcRequestProcessor/process/methodClassError/' + requestedMethod);
this._throwError(JsonRpcRequestProcessor.Errors.METHOD_NOT_FOUND, error, requestId);
return;
}
//FIXME: tutaj trzebaby sie zastanowic jak to zrobic bo raz przekazujemy Exception a raz ErrorEvent
method.addEventListener(JsonRpcMethod.Event.WRONG_PARAMS, function (e) {
that._handleError(e, requestId);
}, this);
method.addEventListener(JsonRpcMethod.Event.ERROR, function(e) {
that._handleError(e, requestId);
}, this);
method.addEventListener(JsonRpcMethod.Event.OK, function(e) {
that._success(e, requestId);
}, this);
try {
method.execute(jsonRpcRequest.getParams());
} catch (e) {
console.error('JsonRpcRequestProcessor/execute/uncaughtException/method/' +
requestedMethod, e, e.stack);
this._throwError(JsonRpcRequestProcessor.Errors.INTERNAL_ERROR, e, requestId);
}
};
/**
* Returns jsonRpc response to the caller
* @param {Event} e
* @returns {undefined}
* @method
* @private
*/
JsonRpcRequestProcessor.prototype._success = function(e, requestId) {
var data;
if (e.data) {
data = e.data;
if (!Types.isInstanceOf(e.data, JsonRpcResponse)) {
data = new JsonRpcResponse();
data.setResult(e.data);
}
data.setId(requestId);
this._responseData.push(data.toJson());
}
return this._prepareResponse();
};
/**
* Handles errors from executed JsonRpc Method
* @param {Event} e
* @returns {undefined}
* @method
* @private
*/
JsonRpcRequestProcessor.prototype._handleError = function (e, requestId) {
if (e.type == JsonRpcMethod.Event.WRONG_PARAMS) {
this._throwError(JsonRpcRequestProcessor.Errors.INVALID_PARAMS, e, requestId);
} else if (e.type == JsonRpcMethod.Event.ERROR) {
this._throwError(e.code || -1, e, requestId);
} else {
this._throwError(JsonRpcRequestProcessor.Errors.INTERNAL_ERROR, e, requestId);
}
};
/**
* Retrieves jsonrpc request from request body
* @returns [JsonRpcRequest]
* @method
* @private
*/
JsonRpcRequestProcessor.prototype._getRequest = function () {
//tutaj mozna dodac pewnie jakas walidacje czy request jest POSTem
var raw_requests;
var requests = [];
try {
raw_requests = JSON.parse(this._request.getBody().toBuffer().toString());
} catch (e) {
var ex = new Error(JsonRpcAbstract.Exceptions.PARSE_ERROR);
console.error('JsonRpcRequestProcessor/process/createJsonRpcRequest', e);
this._throwError(JsonRpcRequestProcessor.Errors.PARSE_ERROR, ex, 0);
return requests;
}
if (Types.isArray(raw_requests) === true) {
this._isBatchRequest = true;
}
else {
this._isBatchRequest = false;
raw_requests = [raw_requests];
}
for (var i = 0, raw_lenght = raw_requests.length; i < raw_lenght; i++) {
try{
requests.push(new JsonRpcRequest(raw_requests[i]));
} catch(e) {
console.error('JsonRpcRequestProcessor/process/createBathJsonRpcRequest', e);
if (e.message == JsonRpcAbstract.Exceptions.INPROPER_JSON) {
this._throwError(JsonRpcRequestProcessor.Errors.PARSE_ERROR, e);
} else if (e.message == JsonRpcAbstract.Exceptions.INVALID_JSON_RPC) {
this._throwError(JsonRpcRequestProcessor.Errors.INVALID_REQUEST, e);
} else {
this._throwError(JsonRpcRequestProcessor.Errors.INTERNAL_ERROR, e);
}
}
}
return requests;
};
/**
* Retrieves body from given JsonRpcResponse
* @method
* @private
* @param {JsonRpcResponse} response
* @return {String}
*/
JsonRpcRequestProcessor.prototype._getBody = function (response) {
return response.toString();
};
/**
* Returns jsonRpc error to the caller
* @param {Number|ErrorEvent} code
* @param {Exception|ErrorEvent} exception
* @param {Number} id
* @returns {undefined}
* @method
* @private
*/
JsonRpcRequestProcessor.prototype._throwError = function (code, exception, id) {
var errorCode = null;
var errorMsg = null;
var loggingFunc = 'warn';
var jsonRpcResponse = new JsonRpcResponse();
jsonRpcResponse.setId(id ? id : "undefined");
if (Types.isObject(code)) {
errorCode = code.code;
errorMsg = code.message;
} else {
errorCode = code;
errorMsg = exception.message;
}
// Error codes range pre-defined for JSON-RPC
if (errorCode >= -32768 && errorCode <= -32000) {
loggingFunc = 'error';
}
console[loggingFunc]('JsonRpcRequestProcessor/_throwError/ code: ' + errorCode + " msg: " + errorMsg);
jsonRpcResponse.setError(parseInt(errorCode, 10), errorMsg || "", exception.data ? exception.data : null);
this._responseData.push(jsonRpcResponse.toJson());
return this._prepareResponse();
};
/**
* @static
* @constant
* @namespace
*/
JsonRpcRequestProcessor.Errors = {};
/**
* @static
* @constant
* @type {String}
* @default
*/
JsonRpcRequestProcessor.Errors.PARSE_ERROR = { code: -32700, message: 'Parse error' };
/**
* @static
* @constant
* @type {Object}
* @default
*/
JsonRpcRequestProcessor.Errors.INVALID_REQUEST = { code: -32600, message: 'Invalid Request' };
/**
* @static
* @constant
* @type {Object}
* @default
*/
JsonRpcRequestProcessor.Errors.METHOD_NOT_FOUND = { code: -32601, message: 'Method not found' };
/**
* @static
* @constant
* @type {Object}
* @default
*/
JsonRpcRequestProcessor.Errors.INVALID_PARAMS = { code: -32602, message: 'Invalid params' };
/**
* @static
* @constant
* @type {Object}
* @default
*/
JsonRpcRequestProcessor.Errors.INTERNAL_ERROR = { code: -32603, message: 'Internal error' };
/**
* @static
* @constant
* @type {Object}
* @default
*/
JsonRpcRequestProcessor.Errors.INVALID_METHOD = { code: -32604, message: 'Invalid method' };