From a39d1a19838df846c5a98e3733bf43361f788e0e Mon Sep 17 00:00:00 2001 From: Carlos Justiniano Date: Sat, 6 May 2017 02:41:22 -0400 Subject: [PATCH] Refactor to use Node HTTP (#116) --- .eslintrc | 3 +- index.js | 71 +++++--------------------------- lib/config.js | 42 +------------------ lib/server-request.js | 70 ++++++++++++++++++++++++++++++++ lib/server-response.js | 92 +++++++++++++++++++----------------------- lib/utils.js | 4 +- package.json | 4 +- 7 files changed, 128 insertions(+), 158 deletions(-) create mode 100644 lib/server-request.js diff --git a/.eslintrc b/.eslintrc index 4d7d1e1..7646512 100644 --- a/.eslintrc +++ b/.eslintrc @@ -100,6 +100,7 @@ "-": 0, "define": true, "expect": true, - "it": true + "it": true, + "require": true } } diff --git a/index.js b/index.js index 77b51cf..fcd85b2 100755 --- a/index.js +++ b/index.js @@ -9,13 +9,14 @@ Promise.series = (iterable, action) => { const EventEmitter = require('events'); const util = require('util'); const Route = require('route-parser'); -const fetch = require('node-fetch'); const Utils = require('./lib/utils'); const UMFMessage = require('./lib/umfmessage'); const RedisConnection = require('./lib/redis-connection'); const ServerResponse = require('./lib/server-response'); let serverResponse = new ServerResponse(); +const ServerRequest = require('./lib/server-request'); +let serverRequest = new ServerRequest(); let HYDRA_REDIS_DB = 0; const redisPreKey = 'hydra:service'; @@ -1059,60 +1060,23 @@ class Hydra extends EventEmitter { reject(err); } else { instance = Utils.safeJSONParse(result); - let url = `http://${instance.ip}:${instance.port}${parsedRoute.apiRoute}`; let options = { - method: parsedRoute.httpMethod.toUpperCase(), - timeout: 0 + host: instance.ip, + port: instance.port, + path: parsedRoute.apiRoute, + method: parsedRoute.httpMethod.toUpperCase() }; options.headers = Object.assign({}, umfmsg.headers); if (umfmsg.authorization) { options.headers.Authorization = umfmsg.authorization; } - if (umfmsg.body) { - let httpMethod = parsedRoute.httpMethod.toUpperCase(); - if (httpMethod === 'POST' || httpMethod === 'PUT') { - options.body = Utils.safeJSONStringify(umfmsg.body); - } - } - - let status = 0; - let ct; - let isJSON = false; - let headers = {}; - fetch(url, options) + options.body = Utils.safeJSONStringify(umfmsg.body); + serverRequest.send(options) .then((res) => { - status = res.status; - res.headers.forEach((value, name) => { - headers[name] = value; - }); - ct = res.headers.get('content-type'); - if (ct && ct.indexOf('json') > -1) { - isJSON = true; - return res.json(); - } else { - isJSON = false; - return res.buffer(); - } - }) - .then((body) => { - if (body.statusCode) { - resolve(body); - } else { - let resObject; - if (isJSON) { - resObject = serverResponse.createResponseObject(status, { - result: body - }); - } else { - resObject = { - headers, - body - }; - } - resolve(resObject); - } + resolve(serverResponse.createResponseObject(res.statusCode, res)); }) .catch((_err) => { + this.emit('metric', `service:unavailable|${instance.serviceName}|${instance.instanceID}`); instanceList.shift(); if (instanceList.length === 0) { resolve(this._createServerResponseWithReason(ServerResponse.HTTP_SERVICE_UNAVAILABLE, `An instance of ${instance.serviceName} is unavailable`)); @@ -1508,21 +1472,6 @@ class Hydra extends EventEmitter { * Hydra private utility functions. * ***************************************************************/ - /** - * @name _createServerResponseWithReason - * @summary Create a server response using an HTTP code and reason - * @param {number} httpCode - code using ServerResponse.HTTP_XXX - * @param {string} reason - reason description - * @return {object} response - response object for use with promise resolve and reject calls - */ - _createServerResponse(httpCode, reason) { - return serverResponse.createResponseObject(httpCode, { - result: { - reason: reason - } - }); - } - /** * @name _createServerResponseWithReason * @summary Create a server response using an HTTP code and reason diff --git a/lib/config.js b/lib/config.js index 550aaa8..fb4fa5d 100644 --- a/lib/config.js +++ b/lib/config.js @@ -2,7 +2,6 @@ const fs = require('fs'); const Promise = require('bluebird'); -const fetch = require('node-fetch'); const Utils = require('./utils'); /** @@ -43,13 +42,7 @@ class Config { } else { try { if (typeof cfg === 'string') { - if (cfg.substring(0, 4) === 'http') { - // network based - this._doInitViaNetwork(cfg, resolve, reject); - } else { - // file based - this._doInitViaFile(cfg, resolve, reject); - } + this._doInitViaFile(cfg, resolve, reject); } else { this.config = Object.assign({}, cfg); resolve(); @@ -88,39 +81,6 @@ class Config { }); } - /** - * @name _doInitViaNetwork - * @summary Perform initialization using remote request. - * @param {string} configFilePath - URL to configuration JSON data - * @param {function} resolve - resolve function - * @param {function} reject - reject function - * @return {undefined} - */ - _doInitViaNetwork(configFilePath, resolve, reject) { - let options = { - headers: { - 'content-type': 'application/json', - 'Accept': 'application/json; charset=UTF-8' - }, - method: 'GET' - }; - fetch(configFilePath, options) - .then((response) => { - return response.json(); - }) - .then((config) => { - if (config.location) { - this._doInit(config.location, resolve, reject); - } else { - this.config = config; - resolve(); - } - }) - .catch((_err) => { - reject(new Error('config file contents is not valid JSON')); - }); - } - /** * @name init * @summary Initializes config object with JSON file data. diff --git a/lib/server-request.js b/lib/server-request.js new file mode 100644 index 0000000..7b67012 --- /dev/null +++ b/lib/server-request.js @@ -0,0 +1,70 @@ +const http = require('http'); +const REQUEST_TIMEOUT = 5 * 1000; // 5-seconds + +/** + * @name ServerRequest + * @summary Class for handling server requests + */ +class ServerRequest { + /** + * @name constructor + * @summary Class constructor + * @return {undefined} + */ + constructor() { + } + + /** + * @name send + * @summary sends an HTTP Request + * @param {object} options - request options + * @return {object} promise + */ + send(options) { + return new Promise((resolve, reject) => { + if (options.method === 'POST' || options.method === 'PUT') { + options.headers['content-length'] = options.body.length; + } else { + delete options.body; + } + + let req = http.request(options, (res) => { + let response = []; + res.on('data', (data) => { + response.push(data); + }); + res.on('end', () => { + let buffer = Buffer.concat(response); + let data = { + statusCode: res.statusCode, + headers: res.headers + }; + data.headers['content-length'] = Buffer.byteLength(buffer); + data.payLoad = buffer; + resolve(data); + }); + res.on('error', (err) => { + reject(err); + }); + }); + + req.on('socket', (socket) => { + socket.setNoDelay(true); + socket.setTimeout(options.timeout * 1000 || REQUEST_TIMEOUT, () => { + req.abort(); + }); + }); + + req.on('error', (err) => { + reject(err); + }); + + if (options.body) { + req.write(options.body); + } + req.end(); + }); + } +} + +module.exports = ServerRequest; diff --git a/lib/server-response.js b/lib/server-response.js index 822024c..538b40e 100644 --- a/lib/server-response.js +++ b/lib/server-response.js @@ -60,14 +60,11 @@ class ServerResponse { * @return {object} res - Returns the (res) response object when in test mode, else undefined */ sendResponse(code, res, data) { - let response = Object.assign(this.createResponseObject(code), data || {}); let headers = { 'Content-Type': 'application/json' }; - if (response.headers) { - headers = Object.assign(headers, response.headers); - delete response.headers; - } + + let response = Object.assign(this.createResponseObject(code), data || {}); if (this.corsEnabled) { headers['Access-Control-Allow-Origin'] = '*'; } @@ -75,18 +72,9 @@ class ServerResponse { let responseString = JSON.stringify(response); headers['Content-Length'] = Buffer.byteLength(responseString); - if (this.testMode) { - res = { - headers, - response - }; - return res; - } else { - res.writeHead(code, headers); - res.write(responseString); - res.end(); - return; - } + res.writeHead(code, headers); + res.write(responseString); + res.end(); } /** @@ -94,10 +82,10 @@ class ServerResponse { * @summary Send an HTTP_OK server response to caller. * @param {object} res - Node HTTP response object * @param {object} data - An object to send - * @return {object} res - Returns the (res) response object when in test mode, else undefined + * @return {undefined} */ sendOk(res, data) { - return this.sendResponse(ServerResponse.HTTP_OK, res, data); + this.sendResponse(ServerResponse.HTTP_OK, res, data); } /** @@ -105,10 +93,10 @@ class ServerResponse { * @summary Send an HTTP_CREATED server response to caller. * @param {object} res - Node HTTP response object * @param {object} data - An object to send - * @return {object} res - Returns the (res) response object when in test mode, else undefined + * @return {undefined} */ sendCreated(res, data) { - return this.sendResponse(ServerResponse.HTTP_CREATED, res, data); + this.sendResponse(ServerResponse.HTTP_CREATED, res, data); } /** @@ -116,10 +104,10 @@ class ServerResponse { * @summary Send an HTTP_MOVED_PERMANENTLY server response to caller. * @param {object} res - Node HTTP response object * @param {object} data - An object to send - * @return {object} res - Returns the (res) response object when in test mode, else undefined + * @return {undefined} */ sendMovedPermanently(res, data) { - return this.sendResponse(ServerResponse.HTTP_MOVED_PERMANENTLY, res, data); + this.sendResponse(ServerResponse.HTTP_MOVED_PERMANENTLY, res, data); } /** @@ -127,10 +115,10 @@ class ServerResponse { * @summary Send an HTTP_BAD_REQUEST server response to caller. * @param {object} res - Node HTTP response object * @param {object} data - An object to send - * @return {object} res - Returns the (res) response object when in test mode, else undefined + * @return {undefined} */ sendInvalidRequest(res, data) { - return this.sendResponse(ServerResponse.HTTP_BAD_REQUEST, res, data); + this.sendResponse(ServerResponse.HTTP_BAD_REQUEST, res, data); } /** @@ -138,10 +126,10 @@ class ServerResponse { * @summary Send an HTTP_UNAUTHORIZED server response to caller. * @param {object} res - Node HTTP response object * @param {object} data - An object to send - * @return {object} res - Returns the (res) response object when in test mode, else undefined + * @return {undefined} */ sendInvalidUserCredentials(res, data) { - return this.sendResponse(ServerResponse.HTTP_UNAUTHORIZED, res, data); + this.sendResponse(ServerResponse.HTTP_UNAUTHORIZED, res, data); } /** @@ -149,10 +137,10 @@ class ServerResponse { * @summary Send an HTTP_PAYMENT_REQUIRED server response to caller. * @param {object} res - Node HTTP response object * @param {object} data - An object to send - * @return {object} res - Returns the (res) response object when in test mode, else undefined + * @return {undefined} */ sendPaymentRequired(res, data) { - return this.sendResponse(ServerResponse.HTTP_PAYMENT_REQUIRED, res, data); + this.sendResponse(ServerResponse.HTTP_PAYMENT_REQUIRED, res, data); } /** @@ -160,10 +148,10 @@ class ServerResponse { * @summary Send an HTTP_NOT_FOUND server response to caller. * @param {object} res - Node HTTP response object * @param {object} data - An object to send - * @return {object} res - Returns the (res) response object when in test mode, else undefined + * @return {undefined} */ sendNotFound(res, data) { - return this.sendResponse(ServerResponse.HTTP_NOT_FOUND, res, data); + this.sendResponse(ServerResponse.HTTP_NOT_FOUND, res, data); } /** @@ -171,10 +159,10 @@ class ServerResponse { * @summary Send an HTTP_BAD_REQUEST server response to caller. * @param {object} res - Node HTTP response object * @param {object} data - An object to send - * @return {object} res - Returns the (res) response object when in test mode, else undefined + * @return {undefined} */ sendInvalidSession(res, data) { - return this.sendResponse(ServerResponse.HTTP_BAD_REQUEST, res, data); + this.sendResponse(ServerResponse.HTTP_BAD_REQUEST, res, data); } /** @@ -182,10 +170,10 @@ class ServerResponse { * @summary Send an HTTP_SERVER_ERROR server response to caller. * @param {object} res - Node HTTP response object * @param {object} data - An object to send - * @return {object} res - Returns the (res) response object when in test mode, else undefined + * @return {undefined} */ sendRequestFailed(res, data) { - return this.sendResponse(ServerResponse.HTTP_SERVER_ERROR, res, data); + this.sendResponse(ServerResponse.HTTP_SERVER_ERROR, res, data); } /** @@ -193,10 +181,10 @@ class ServerResponse { * @summary Send an HTTP_CONFLICT server response to caller. * @param {object} res - Node HTTP response object * @param {object} data - An object to send - * @return {object} res - Returns the (res) response object when in test mode, else undefined + * @return {undefined} */ sendDataConflict(res, data) { - return this.sendResponse(ServerResponse.HTTP_CONFLICT, res, data); + this.sendResponse(ServerResponse.HTTP_CONFLICT, res, data); } /** @@ -204,10 +192,10 @@ class ServerResponse { * @summary Send an HTTP_TOO_LARGE server response to caller. * @param {object} res - Node HTTP response object * @param {object} data - An object to send - * @return {object} res - Returns the (res) response object when in test mode, else undefined + * @return {undefined} */ sendTooLarge(res, data) { - return this.sendResponse(ServerResponse.HTTP_TOO_LARGE, res, data); + this.sendResponse(ServerResponse.HTTP_TOO_LARGE, res, data); } /** @@ -215,10 +203,10 @@ class ServerResponse { * @summary Send an HTTP_TOO_MANY_REQUEST server response to caller. * @param {object} res - Node HTTP response object * @param {object} data - An object to send - * @return {object} res - Returns the (res) response object when in test mode, else undefined + * @return {undefined} */ sendTooManyRequests(res, data) { - return this.sendResponse(ServerResponse.HTTP_TOO_MANY_REQUEST, res, data); + this.sendResponse(ServerResponse.HTTP_TOO_MANY_REQUEST, res, data); } /** @@ -226,10 +214,10 @@ class ServerResponse { * @summary Send an HTTP_SERVER_ERROR server response to caller. * @param {object} res - Node HTTP response object * @param {object} data - An object to send - * @return {object} res - Returns the (res) response object when in test mode, else undefined + * @return {undefined} */ sendServerError(res, data) { - return this.sendResponse(ServerResponse.HTTP_SERVER_ERROR, res, data); + this.sendResponse(ServerResponse.HTTP_SERVER_ERROR, res, data); } /** @@ -237,10 +225,10 @@ class ServerResponse { * @summary Alias for sendServerError * @param {object} res - Node HTTP response object * @param {object} data - An object to send - * @return {object} res - Returns the (res) response object when in test mode, else undefined + * @return {undefined} */ sendInternalError(res, data) { - return this.sendServerError(res, data); + this.sendServerError(res, data); } /** @@ -248,10 +236,10 @@ class ServerResponse { * @summary Send an HTTP_METHOD_NOT_IMPLEMENTED server response to caller. * @param {object} res - Node HTTP response object * @param {object} data - An object to send - * @return {object} res - Returns the (res) response object when in test mode, else undefined + * @return {undefined} */ sendMethodNotImplemented(res, data) { - return this.sendResponse(ServerResponse.HTTP_METHOD_NOT_IMPLEMENTED, res, data); + this.sendResponse(ServerResponse.HTTP_METHOD_NOT_IMPLEMENTED, res, data); } /** @@ -259,10 +247,10 @@ class ServerResponse { * @summary Send an HTTP_CONNECTION_REFUSED server response to caller. * @param {object} res - Node HTTP response object * @param {object} data - An object to send - * @return {object} res - Returns the (res) response object when in test mode, else undefined + * @return {undefined} */ sendUnavailableError(res, data) { - return this.sendResponse(ServerResponse.HTTP_CONNECTION_REFUSED, res, data); + this.sendResponse(ServerResponse.HTTP_CONNECTION_REFUSED, res, data); } } @@ -272,6 +260,8 @@ class ServerResponse { ServerResponse.HTTP_OK = 200; ServerResponse.HTTP_CREATED = 201; ServerResponse.HTTP_MOVED_PERMANENTLY = 301; +ServerResponse.HTTP_FOUND = 302; +ServerResponse.HTTP_NOT_MODIFIED = 304; ServerResponse.HTTP_BAD_REQUEST = 400; ServerResponse.HTTP_UNAUTHORIZED = 401; ServerResponse.HTTP_PAYMENT_REQUIRED = 402; @@ -292,6 +282,8 @@ ServerResponse.STATUS = { '200': ['OK', 'Request succeeded without error'], '201': ['Created', 'Resource created'], '301': ['Moved Permanently', 'Resource has been permanently moved'], + '302': ['Found', 'Resource was found under another URI'], + '304': ['Not Modified', 'Resource has not been modified'], '400': ['Bad Request', 'Request is invalid, missing parameters?'], '401': ['Unauthorized', 'User isn\'t authorized to access this resource'], '402': ['Payment Required', 'This code is reserved for future use.'], @@ -304,7 +296,7 @@ ServerResponse.STATUS = { '500': ['Server Error', 'An error occurred on the server'], '501': ['Method Not Implemented', 'The requested method / resource isn\'t implemented on the server'], '502': ['Connection Refused', 'The connection to server was refused'], - '503': ['Service Unavailable', 'The server is currently unable to handle the request due to a temporary overloading or maintenance of the server. The implication is that this is a temporary condition which will be alleviated after some delay'] + '503': ['Service Unavailable', 'The server is currently unable to handle the request due to a temporary overloading or maintenance of the server'] }; module.exports = ServerResponse; diff --git a/lib/utils.js b/lib/utils.js index d9d6bc8..a67146b 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,7 +1,6 @@ 'use strict'; const crypto = require('crypto'); -const jss = require('json-stringify-safe'); /** * @name Utils @@ -31,7 +30,7 @@ class Utils { * Returns undefined if the object isn't a valid object or can't be stringified */ static safeJSONStringify(obj) { - return jss(obj); + return JSON.stringify(obj); } /** @@ -47,6 +46,7 @@ class Utils { try { data = JSON.parse(str); } catch (e) { + data = undefined; } return data; } diff --git a/package.json b/package.json index 6275eec..7720396 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hydra", - "version": "1.3.1", + "version": "1.3.2", "license": "MIT", "author": "Carlos Justiniano", "contributors": [ @@ -32,8 +32,6 @@ "dependencies": { "bluebird": "3.5.0", "ip": "1.1.5", - "json-stringify-safe": "5.0.1", - "node-fetch": "1.6.3", "redis": "2.7.1", "redis-url": "1.2.1", "route-parser": "0.0.5",