UNPKG

core

Version:

Core Libs

585 lines (503 loc) 14.4 kB
/** * @overview Loader * @copyright Dreamlab Onet.pl sp. z. o o 2012 */ var http = require('http'), https = require('https'), util = require('util'); var Request = require('./Request.js').Request, Response = require('./Response.js').Response, Event = require('../event/Event.js').Event, ErrorEvent = require('../event/ErrorEvent.js').ErrorEvent, EventDispatcher = require('../event/EventDispatcher.js').EventDispatcher, Types = require('../common/Types.js'); /** * Http Loader * @class Loader * @classdesc Processes given requests and returns the result * @extends EventDispatcher * @param {Request} request request to process */ var Loader = function (request) { EventDispatcher.call(this); this._buffer = null; this._request = null; this._timeout = 8000; this._timer = null; this._followRedirects = true; this._maxRedirectsCount = 10; this._currentRedirectsCount = 0; this._dataBuffering = true; this._agent = false; this._errorEmited = false; this._requestStart = null; this._requestLength = null; this._logStatus = true; this.setRequest(request); }; Loader.prototype = Object.create(EventDispatcher.prototype); Loader.prototype.setLogStatus = function (value) { this._logStatus = !!value; return this; } /** * enables/disables data buffering * @method * @param {Boolean} buffering * @returns {Loader} */ Loader.prototype.setDataBuffering = function (buffering) { this._dataBuffering = buffering; return this; }; /** * callback responsible for gathering response chunks * @method * @private * @param {Server.HttpResponse} serverResponse * @fires Loader.Event.HEADERS * @fires Loader.Event.PROGRESS */ Loader.prototype._handleServerResponse = function (serverResponse) { var dataLength = 0, buffers = [], self = this, response = new Response(); response.setStatusCode(serverResponse.statusCode); response.setHeaders(serverResponse.headers); response.setHttpVersion(serverResponse.httpVersion); //console.log(">>>>> LOADER headers"); this.dispatchEvent(new Event(Loader.Event.HEADERS, { "statusCode": serverResponse.statusCode, "headers": serverResponse.headers, "httpVersion": serverResponse.httpVersion, "body": null, "response": response, "stream": serverResponse })); serverResponse.addListener('data', function (buffer) { //console.log(">>>>> LOADER data"); if (self._dataBuffering) { buffers.push(buffer); dataLength += buffer.length; } response.write(buffer); self.dispatchEvent(new Event(Loader.Event.PROGRESS, buffer)); }); serverResponse.addListener('end', function () { //console.log(">>>>> LOADER end"); self._buffer = new Buffer(dataLength); var bufferPosition = 0; for (var i = 0, l = buffers.length; i < l; i++) { buffers[i].copy(self._buffer, bufferPosition); bufferPosition += buffers[i].length; } response.end(); self._onEnd(serverResponse); }); }; /** * sets timer which is responsible for timeout detection * @method * @private * @param {Server.HttpRequest} request * @fires Loader.Event.ERROR */ Loader.prototype._setTimer = function (request) { var self = this; this._timer = setTimeout(function () { request.removeAllListeners('response'); request.abort(); self._log(); if (!self._errorEmited) { self.dispatchEvent(new ErrorEvent(Loader.Event.ERROR, null, -3, 'Http response timeout')); self._errorEmited = true; } }, this.getTimeout()); }; /** * clears timer handle which is responsible for timeout detection * @method * @private */ Loader.prototype._clearTimer = function () { clearTimeout(this._timer); }; /** * clears internal chunks array * @method * @private */ Loader.prototype._clearChunks = function () { this._buffer = []; }; /** * check for 3xx following * @method * @private * @param {Server.HttpResponse} response * @fires Loader.Event.ERROR * @returns {Boolean} */ Loader.prototype._checkForRedirect = function (response) { var requestMethod = this._request.getMethod(); if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.hasOwnProperty('location') && this._followRedirects && requestMethod != Request.PUT && requestMethod != Request.POST) { response.removeAllListeners('data'); response.removeAllListeners('end'); if (this._currentRedirectsCount >= this._maxRedirectsCount) { if (!this._errorEmited) { this.dispatchEvent(new ErrorEvent(Loader.Event.ERROR, null, -4, 'maxRedirectsCount reached, stopping')); this._errorEmited = true; } return true; } this._currentRedirectsCount++; var host = this._request.getHost(); var connectionHost = this._request.getConnectionHost(); this._request.setUrl(response.headers.location); /* redirect with no host */ if (!this._request.getHost() && host) { this._request.setHost(host); } if (!this._request.getConnectionHost() && connectionHost) { this._request.setConnectionHost(connectionHost); } this._clearChunks(); this.load(); return true; } return false; }; Loader.prototype._log = function (res, type) { var req = this.getRequest(); var code = 0; var len = 0; if (res) { code = res.statusCode; len = this._buffer.length; } var upstream = req.getHeader('host') || '-'; var gateway = req.getConnectionHost() || '-'; var tt = Date.now() - this._requestStart; var rel = this._requestLength || 0; type = type || 'http'; if (this._logStatus) { console.info('Loader/Monitoring: upstream=%s code=%s tt=%s len=%s method=%s gateway=%s type=%s reqlen=%s', upstream, code, tt, len, req.getMethod(), gateway, type, rel); } else { console.log('Loader/Monitoring: upstream=%s code=%s tt=%s len=%s method=%s gateway=%s type=%s reqlen=%s', upstream, code, tt, len, req.getMethod(), gateway, type, rel); } }; /** * called on request end, when whole data has been retreived from server * @method * @private * @param {http.IncomingMessage} response * @fires Loader.Event.ERROR * @fires Loader.Event.LOADED */ Loader.prototype._onEnd = function (response) { var requestMethod = this._request.getMethod(); this._log(response); if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.hasOwnProperty('location') && this._followRedirects && requestMethod != Request.PUT && requestMethod != Request.POST) { if (this._currentRedirectsCount >= this._maxRedirectsCount) { if (!this._errorEmited) { this.dispatchEvent(new ErrorEvent(Loader.Event.ERROR, null, -4, 'maxRedirectsCount reached, stopping')); this._errorEmited = true; } return; } this._currentRedirectsCount++; this._request.setUrl(response.headers.location); this._clearChunks(); this.load(); return; } var data = this._parseResponse(response); if (data == false) { //tutaj zakladamy ze _parseResponse obsluzyl blad return; } this.dispatchEvent(new Event(Loader.Event.LOADED, data)); }; /** * creates Response from node http.IncomingMessage and received data chunks * @method * @private * @param {http.IncomingMessage} response */ Loader.prototype._parseResponse = function (response) { return new Response({ "statusCode": response.statusCode, "headers": response.headers, "httpVersion": response.httpVersion, "body": this._buffer }); }; /** * creates node http.request compatible object based on given request * @method * @private * @param {Request} request * @throws Loader.Exception.INCOMPLETE_REQUEST */ Loader.prototype._createRequest = function (request) { var ua = request.getHeader('user-agent'); if (!ua) { request.setHeader('user-agent', 'nodejs ' + process.env.OPAL_IDENTITY || '', true); } var host = request.getConnectionHost() || request.getHost(); //FIXME to pasuje rozwiazac w klasie Request gdy klonuje inny request host = host.replace(':'+request.getPort(), ''); var port = request.getPort(); var path = request.getPath(); var method = request.getMethod(); var headers = request.getHeaders(); if (!host || !port || !path || !method || !headers) { this._log(); throw Loader.Exception.INCOMPLETE_REQUEST; } var retValue = { host: host, port: +port, path: path + request.getQueryString(), method: method, headers: headers }; if (!this._agent) { retValue.agent = false; } return retValue; }; /** * sets timeout in miliseconds * @method * @param {Number} ms * @returns {Loader} */ Loader.prototype.setTimeout = function (ms) { this._timeout = ms; return this; }; /** * gets timeout in miliseconds * @method * @returns {Number} */ Loader.prototype.getTimeout = function () { return this._timeout; }; /** sets followRedirects * @method * @param {Boolean} follow * @throws Loader.Exception.FOLLOW_REDIRECTS_TYPE_ERROR * @returns {Loader} */ Loader.prototype.setFollowRedirects = function (follow) { if (typeof follow == 'boolean') { this._followRedirects = follow; } else { throw Loader.Exception.FOLLOW_REDIRECTS_TYPE_ERROR; } return this; }; /** * gets followRedirects * @method * @returns {Boolean} */ Loader.prototype.getFollowRedirects = function () { return this._followRedirects; }; /** * sets maxRedirects after which processing request will be aborted * @method * @param {Number} count * @returns {Loader} */ Loader.prototype.setMaxRedirectsCount = function (count) { this._maxRedirectsCount = count; return this; }; /** * gets maxRedirects count * @method * @returns {Number} */ Loader.prototype.getMaxRedirectsCount = function () { return this._maxRedirectsCount; }; /** * sets request object to process, * request to process is a copy from a original request * @method * @param {Request} request * @throws Loader.Exception.WRONG_REQUEST_INSTANCE */ Loader.prototype.setRequest = function (request) { if (request && !(request instanceof Request)) { throw Loader.Exception.WRONG_REQUEST_INSTANCE; } else if (request) { this._request = new Request(request); this._currentRedirectCount = 0; this._clearChunks(); } }; /** * returns request (a copy of it) which is going to be processed */ Loader.prototype.getRequest = function () { return this._request; }; /** * aborts a request, clears timer, dispatches ABORTED event * @method * @fires Loader.Event.ABORTED */ Loader.prototype.abort = function () { if (this._clientRequest) { this._clientRequest.abort(); } this._clearTimer(); this.dispatchEvent(new Event(Loader.Event.ABORTED, 'Aborted')); }; /** * set agent value * @param {Boolean} value */ Loader.prototype.setAgent = function (value) { if (!Types.isBoolean(value)) throw new Error(Loader.Exception.AGENT_VALUE); this._agent = value; }; /** * makes request to the backend system based on the request object * @method * @param {Request} request * @fires Loader.Event.ERROR * @throws Loader.Exception.REQUEST_NOT_SET */ Loader.prototype.load = function (request) { this.setRequest(request); this._errorEmited = false; if (this._request == null) { throw Loader.Exception.REQUEST_NOT_SET; } var that = this; this._requestStart = Date.now(); var options = this._createRequest(this._request); var httpClient = http; if(this._request.getProto() == 'https:') { httpClient = https; } this._clientRequest = httpClient.request(options, function (serverResponse) { that._clearTimer(); if (that._checkForRedirect(serverResponse)) { return; } else { that._handleServerResponse(serverResponse); } }); this._clientRequest.addListener('error', function (e) { options.agent = null; console.error('LOADER ERROR NODE REQUEST: ' + util.inspect(e, true, 20) + ' Options: ' + util.inspect(options, true, 20)); that._clearTimer(); that._log(); if (!that._errorEmited) { that.dispatchEvent(new ErrorEvent(Loader.Event.ERROR, e, -1, 'Http request error: ' + e.message)); that._errorEmited = true; } }); // jezeli sa dane np POST to tez je wpisuje if (this._request.getBody().length() > 0) { var buf = this._request.getBody().toBuffer(); this._requestLength = buf.length; this._clientRequest.write(buf); } this._clientRequest.end(); this._setTimer(this._clientRequest); }; /** * @static * @namespace * @constant */ Loader.Event = {}; /** * @static * @constant * @type {String} * @default */ Loader.Event.HEADERS = "Loader_HEADERS"; /** * @static * @constant * @type {String} * @default */ Loader.Event.PROGRESS = "Loader_PROGRESS"; /** * @static * @constant * @type {String} * @default */ Loader.Event.LOADED = "Loader_LOADED"; /** * @static * @constant * @type {String} * @default */ Loader.Event.ABORTED = "Loader_ABORTED"; /** * @static * @constant * @type {String} * @default */ Loader.Event.ERROR = "Loader_ERROR"; /** * @static * @namespace * @constant */ Loader.Exception = {}; /** * @static * @constant * @type {String} * @default */ Loader.Exception.INCOMPLETE_REQUEST = 'Given request misses some key values'; /** * @static * @constant * @type {String} * @default */ Loader.Exception.REQUEST_NOT_SET = 'Request has not been set'; /** * @static * @constant * @type {String} * @default */ Loader.Exception.WRONG_REQUEST_INSTANCE = 'Given request is not a instance of Request'; /** * @static * @constant * @type {String} * @default */ Loader.Exception.FOLLOW_REDIRECTS_TYPE_ERROR = 'setFollowRedirects requires an argument to be a boolean'; /** * @static * @constant * @type {String} * @default */ Loader.Exception.AGENT_VALUE = 'Agent param value must be a type of Boolean'; exports.Loader = Loader;