diff --git a/.eslintrc.js b/.eslintrc.js index 79f8658..ec71f90 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,7 +16,9 @@ module.exports = { "indent": ["error", 2], "linebreak-style": [ "error", "unix"], "semi": ["error", "always"], - "eol-last": ["error", "always"] + "eol-last": ["error", "always"], + "keyword-spacing": [1], + "no-trailing-spaces": ["error", { "skipBlankLines": true }] }, "overrides": [{ "files": ["src/requestsDebugger.js", "src/commandLine.js", "test/**/*.test.js"], diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 0000000..b382911 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,32 @@ +name: 'unit-tests' +on: + pull_request: + paths: + - './**' + - '.github/workflows/unit-tests*' + push: + paths: + - './**' + - '.github/workflows/unit-tests*' + +jobs: + unit-tests: + runs-on: ${{ matrix.operating-system }} + strategy: + matrix: + operating-system: [ubuntu-latest, macos-latest] + steps: + - uses: actions/checkout@v2 + + - name: Set Node.js 4.9.1 + uses: actions/setup-node@master + with: + node-version: 4.9.1 + + - name: npm install + working-directory: ./ + run: npm install + + - name: Lint and Unit tests + working-directory: ./ + run: npm run test diff --git a/.gitignore b/.gitignore index 3f8f6b0..b208277 100644 --- a/.gitignore +++ b/.gitignore @@ -108,3 +108,9 @@ dist RequestsDebugger-Mac RequestsDebugger.exe RequestsDebugger-Linux* + +# VS code settings +.vscode/ + +# Others +.DS_Store diff --git a/README.md b/README.md index 12d276f..0fedb34 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,17 @@ ## Requests Debugger + +

+ BrowserStack Logo +

+ +

+ unit-tests build status +

+ ### Tool for debugging client side failure of requests, leading to requests getting dropped or not reaching BrowserStack. ## Features -- Proxy Server to intercept requests fired by client bindings to keep track of their flow. +- Server to intercept requests fired by client bindings to keep track of their flow. - Connectivity Checker : To check for the reachability of BrowserStack components, i.e. Rails & Hub. - Multi-Platform Stats Compatibility : Ability to collect stats of CPU, Network & Memory Stats. - Retry Mechanism in case a request fails at the client side @@ -13,6 +22,8 @@ - Start Requests Debugger with the required arguments: `npm run start -- `. - Supported `args`: - `--port `: Port on which the Requests Debugger Tool's Proxy will run. Default: 9687 + - `--reverse-proxy-port `: Port on which the Requests Debugger Tool's Reverse Proxy will run. Default: 9688 + - `--scheme `: Scheme for requests to browserstack. Default: https - `--proxy-host `: Hostname of the Upstream Proxy - `--proxy-port `: Port of the Upstream Proxy. Default: 3128 (if hostname is provided) - `--proxy-user `: Username for auth of the Upstream Proxy @@ -30,7 +41,8 @@ - Windows: `RequestsDebugger.exe ` ## How to use -- Since the tool acts like a proxy, you will have to set the proxy to be used by your client binding to `localhost:9687`. i.e. +- To use tool as reverse proxy, you will have to replace `hub-cloud.browserstack.com` in hub url with `localhost:9688`. +- To use tool as a proxy, you will have to set the proxy to be used by your client binding to `localhost:9687`. i.e. - For Java: - ``` System.getProperties().put("http.proxyHost", "localhost"); @@ -40,7 +52,7 @@ - Set your system's env variable `http_proxy=localhost:9687` and Ruby's Selenium Client Binding will pick the value. Or, - Run you test by giving the environment variable to your command itself, i.e. `http_proxy=localhost:9687 ruby ` - Similarly, you can also set proxy for other client bindings. - + ## Steps to build the executables - Linux - `npm run build:linux` diff --git a/config/constants.js b/config/constants.js index 346bd9c..fbec60a 100644 --- a/config/constants.js +++ b/config/constants.js @@ -1,6 +1,12 @@ -module.exports.VERSION = '1.0.0'; -module.exports.HUB_STATUS_URL = 'http://hub-cloud.browserstack.com/wd/hub/status'; -module.exports.RAILS_AUTOMATE = 'http://automate.browserstack.com'; +module.exports.VERSION = '1.1.0'; +module.exports.BS_DOMAIN = 'browserstack.com'; +module.exports.HUB_HOST = 'hub-cloud.' + this.BS_DOMAIN; +module.exports.RAILS_HOST = 'automate.' + this.BS_DOMAIN; +module.exports.HUB_STATUS_PATH = '/wd/hub/status'; +module.exports.HUB_STATUS_URL = 'http://' + this.HUB_HOST + this.HUB_STATUS_PATH; +module.exports.HUB_STATUS_URL_HTTPS = 'https://' + this.HUB_HOST + this.HUB_STATUS_PATH; +module.exports.RAILS_AUTOMATE = 'http://' + this.RAILS_HOST; +module.exports.RAILS_AUTOMATE_HTTPS = 'https://' + this.RAILS_HOST; module.exports.CONNECTIVITY_REQ_TIMEOUT = 30000; module.exports.DEFAULT_PROXY_PORT = 3128; module.exports.CUSTOM_ERROR_RESPONSE_CODE = 502; @@ -10,6 +16,8 @@ module.exports.PORTS = { MAX: 65535, MIN: 1 }; + +module.exports.LINE_LENGTH = 70; module.exports.PROTOCOL_REGEX = /(^\w+:|^)\/\//; module.exports.LOGS = Object.freeze({ @@ -23,13 +31,15 @@ module.exports.LOGS = Object.freeze({ module.exports.RdGlobalConfig = { RETRY_DELAY: 1000, // in ms - RD_HANDLER_PORT: process.env.NODE_ENV === 'test' ? 8787 : 9687, + RD_HANDLER_PROXY_PORT: process.env.NODE_ENV === 'test' ? 8787 : 9687, + RD_HANDLER_REVERSE_PROXY_PORT: process.env.NODE_ENV === 'test' ? 8788 : 9688, CLIENT_REQ_TIMEOUT: 260000, // in ms + SCHEME: 'https' }; module.exports.COMMON = Object.freeze({ - PING_HUB: 'ping -c 5 hub-cloud.browserstack.com', - PING_AUTOMATE: 'ping -c 5 automate.browserstack.com' + PING_HUB: 'ping -c 5 ' + this.HUB_HOST, + PING_AUTOMATE: 'ping -c 5 ' + this.RAILS_HOST }); module.exports.MAC = Object.freeze({ @@ -49,8 +59,8 @@ module.exports.WIN = Object.freeze({ NETSTAT_ROUTING_TABLE: 'netstat -r', IPCONFIG_ALL: 'ipconfig /all', SWAP_USAGE: 'pagefile get AllocatedBaseSize, CurrentUsage', // this is a WMIC command. Prefix with WMIC Path - PING_HUB: 'ping -n 5 hub-cloud.browserstack.com', - PING_AUTOMATE: 'ping -n 5 automate.browserstack.com', + PING_HUB: 'ping -n 5 ' + this.HUB_HOST, + PING_AUTOMATE: 'ping -n 5 ' + this.RAILS_HOST, LOAD_PERCENTAGE: 'cpu get loadpercentage', // prefix wmic path }); @@ -78,8 +88,10 @@ module.exports.STATIC_MESSAGES = Object.freeze({ CHECK_NETWORK_STATS: 'Stats : Checking Network Stats', CHECK_MEMORY_STATS: 'Stats : Checking Memory Stats', CHECK_CONNECTIVITY: 'Checks : Checking Connectivity With BrowserStack', - ERR_STARTING_TOOL: 'Error in starting Requests Debugger Tool Proxy: ', - TOOL_STARTED_ON_PORT: 'Requests Debugger Tool Proxy Started on Port: ', + ERR_STARTING_TOOL_PROXY: 'Error in starting Requests Debugger Tool Proxy: ', + ERR_STARTING_TOOL_REVERSE_PROXY: 'Error in starting Requests Debugger Tool Reverse Proxy: ', + TOOL_PROXY_STARTED_ON_PORT: 'Requests Debugger Tool Proxy Server Started on Port: ', + TOOL_REVESE_PROXY_STARTED_ON_PORT: 'Requests Debugger Tool Reverse Proxy Server Started on Port: ', CPU_STATS_COLLECTED: 'Stats : Initial CPU Stats Collected', NETWORK_STATS_COLLECTED: 'Stats : Initial Network Stats Collected', MEMORY_STATS_COLLECTED: 'Stats : Initial Memory Stats Collected', diff --git a/package.json b/package.json index 492ca4d..254a817 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "start": "NODE_ENV=prod node src/requestsDebugger.js", "lint": "./node_modules/.bin/eslint 'src/*' 'test/*' 'config/*.js'", - "test": "npm run lint; NODE_ENV=test nyc --reporter=html ./node_modules/mocha/bin/mocha 'test/**/*.test.js'", + "test": "npm run lint; NODE_ENV=test ./node_modules/nyc/bin/nyc.js --reporter=html ./node_modules/mocha/bin/mocha 'test/**/*.test.js'", "build:mac": "npm install; ./node_modules/pkg/lib-es5/bin.js -t node4-macos-x64 src/requestsDebugger.js; mv requestsDebugger RequestsDebugger-Mac", "build:linux-x86": "npm install; ./node_modules/pkg/lib-es5/bin.js -t node4-linux-x86 src/requestsDebugger.js; mv requestsDebugger RequestsDebugger-Linux-x86", "build:linux-x64": "npm install; ./node_modules/pkg/lib-es5/bin.js -t node4-linux-x64 src/requestsDebugger.js; mv requestsDebugger RequestsDebugger-Linux-x64", @@ -34,6 +34,8 @@ "sinon": "7.5.0" }, "dependencies": { + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.1.0", "uuid": "3.4.0", "winston": "2.4.4" }, diff --git a/src/commandLine.js b/src/commandLine.js index cd1a665..c0850ea 100644 --- a/src/commandLine.js +++ b/src/commandLine.js @@ -2,8 +2,8 @@ * Command Line Manager to parse the command line arguments * and set the necessary fields in RdGlobalConfig. */ - var constants = require('../config/constants'); + var RdGlobalConfig = constants.RdGlobalConfig; var CommandLineManager = { @@ -13,21 +13,25 @@ var CommandLineManager = { + "\n" + "Usage: RequestsDebugger [ARGUMENTS]\n\n" + "ARGUMENTS:\n" - + " --port : Port on which the Requests Debugger Tool's Proxy will run\n" - + " Default: " + RdGlobalConfig.RD_HANDLER_PORT + "\n" - + " --proxy-host : Hostname of the Upstream Proxy\n" - + " --proxy-port : Port of the Upstream Proxy. Default: " + constants.DEFAULT_PROXY_PORT + " (if hostname is provided)\n" - + " --proxy-user : Username for auth of the Upstream Proxy\n" - + " --proxy-pass : Password for auth of the Upstream Proxy\n" - + " --retry-delay : Delay for the retry of a failed request. Default: " + RdGlobalConfig.RETRY_DELAY + "ms\n" - + " --request-timeout : Hard timeout for the requests being fired by the tool before receiving any response\n" - + " Default: " + RdGlobalConfig.CLIENT_REQ_TIMEOUT + "ms\n" - + " --logs-path : Directory where the '" + constants.LOGS_FOLDER + "' folder will be created\n" - + " for storing logs. Default: Current Working Directory\n" - + " --del-logs : Deletes any existing logs from the " + constants.LOGS_FOLDER + "/ directory and initializes\n" - + " new files for logging\n" - + " --help : Help for Requests Debugger Tool\n" - + " --version : Version of the Requests Debugger Tool\n"; + + " --proxy-port : Port on which the Requests Debugger Tool's Proxy will run\n" + + " Default: " + RdGlobalConfig.RD_HANDLER_PROXY_PORT + "\n" + + " --reverse-proxy-port : Port on which the Requests Debugger Tool's Reverse Proxy will run\n" + + " Default: " + RdGlobalConfig.RD_HANDLER_REVERSE_PROXY_PORT + "\n" + + " --scheme : Scheme for requests to browserstack.\n" + + " Default: " + RdGlobalConfig.SCHEME + "\n" + + " --proxy-host : Hostname of the Upstream Proxy\n" + + " --proxy-port : Port of the Upstream Proxy. Default: " + constants.DEFAULT_PROXY_PORT + " (if hostname is provided)\n" + + " --proxy-user : Username for auth of the Upstream Proxy\n" + + " --proxy-pass : Password for auth of the Upstream Proxy\n" + + " --retry-delay : Delay for the retry of a failed request. Default: " + RdGlobalConfig.RETRY_DELAY + "ms\n" + + " --request-timeout : Hard timeout for the requests being fired by the tool before receiving any response\n" + + " Default: " + RdGlobalConfig.CLIENT_REQ_TIMEOUT + "ms\n" + + " --logs-path : Directory where the '" + constants.LOGS_FOLDER + "' folder will be created\n" + + " for storing logs. Default: Current Working Directory\n" + + " --del-logs : Deletes any existing logs from the " + constants.LOGS_FOLDER + "/ directory and initializes\n" + + " new files for logging\n" + + " --help : Help for Requests Debugger Tool\n" + + " --version : Version of the Requests Debugger Tool\n"; console.log(helpOutput); }, @@ -38,7 +42,7 @@ var CommandLineManager = { /** * Processes the args from the given input array and sets the global config. - * @param {Array} argv + * @param {Array} argv */ processArgs: function (argv) { @@ -67,7 +71,7 @@ var CommandLineManager = { if (CommandLineManager.validArgValue(argv[index + 1])) { var probablePort = parseInt(argv[index + 1]); if (!isNaN(probablePort) && (probablePort <= constants.PORTS.MAX) && (probablePort >= constants.PORTS.MIN)) { - RdGlobalConfig.RD_HANDLER_PORT = probablePort; + RdGlobalConfig.RD_HANDLER_PROXY_PORT = probablePort; } else { console.log("\nPort can only range from:", constants.PORTS.MIN, "to:", constants.PORTS.MAX); invalidArgs.add('--port'); @@ -79,6 +83,24 @@ var CommandLineManager = { } } + // port for Requests Debugger Reverse Proxy + index = argv.indexOf('--reverse-proxy-port'); + if (index !== -1) { + if (CommandLineManager.validArgValue(argv[index + 1])) { + var probableReverseProxyPort = parseInt(argv[index + 1]); + if (!isNaN(probableReverseProxyPort) && (probableReverseProxyPort <= constants.PORTS.MAX) && (probableReverseProxyPort >= constants.PORTS.MIN)) { + RdGlobalConfig.RD_HANDLER_REVERSE_PROXY_PORT = probableReverseProxyPort; + } else { + console.log("\nPort can only range from:", constants.PORTS.MIN, "to:", constants.PORTS.MAX); + invalidArgs.add('--reverse-proxy-port'); + } + argv.splice(index, 2); + } else { + invalidArgs.add('--reverse-proxy-port'); + argv.splice(index, 1); + } + } + // delay for retries in case of request failures index = argv.indexOf('--retry-delay'); if (index !== -1) { @@ -115,12 +137,34 @@ var CommandLineManager = { } } + // process proxy host + index = argv.indexOf('--scheme'); + if (index !== -1) { + if (CommandLineManager.validArgValue(argv[index + 1])) { + var scheme = argv[index + 1]; + if (!(scheme === 'http' || scheme === 'https')){ + console.log("\nScheme can only be http/https"); + invalidArgs.add('--scheme'); + argv.splice(index, 2); + } + else { + RdGlobalConfig.SCHEME = scheme; + argv.splice(index, 2); + } + } else { + invalidArgs.add('--scheme'); + argv.splice(index, 1); + } + } + // process proxy host index = argv.indexOf('--proxy-host'); if (index !== -1) { if (CommandLineManager.validArgValue(argv[index + 1])) { var host = argv[index + 1]; - host = host.replace(constants.PROTOCOL_REGEX, ''); + if (host.lastIndexOf("http") !== 0){ + host = 'http://' + host; + } RdGlobalConfig.proxy = RdGlobalConfig.proxy || {}; RdGlobalConfig.proxy.host = host; argv.splice(index, 2); @@ -151,7 +195,7 @@ var CommandLineManager = { argv.splice(index, 1); } } - + // if proxy port value in invalid or doesn't exist and host exists, set the default value if (RdGlobalConfig.proxy && RdGlobalConfig.proxy.host && (invalidArgs.has('--proxy-port') || !RdGlobalConfig.proxy.port)) { console.log('\nSetting Default Proxy Port:', constants.DEFAULT_PROXY_PORT, '\n'); @@ -190,7 +234,7 @@ var CommandLineManager = { argv.splice(index, 1); } } - + // if proxy pass is invalid or doesn't exist and username exists, set the password as empty if (RdGlobalConfig.proxy && RdGlobalConfig.proxy.username && (invalidArgs.has('--proxy-pass') || !RdGlobalConfig.proxy.password)) { console.log('Setting Proxy Password as Empty\n'); diff --git a/src/connectivity.js b/src/connectivity.js index 529fd1a..001a651 100644 --- a/src/connectivity.js +++ b/src/connectivity.js @@ -12,14 +12,16 @@ var constants = require('../config/constants'); var RdGlobalConfig = constants.RdGlobalConfig; var Utils = require('./utils'); var https = require('https'); +var HttpProxyAgent = require('http-proxy-agent'); +var HttpsProxyAgent = require('https-proxy-agent'); /** * Fires the requests to perform connectivity checks - * @param {Object} requestOptions - * @param {'http'|'https'} requestType - * @param {String} description - * @param {Array} successCodes - * @param {Function} callback + * @param {Object} requestOptions + * @param {'http'|'https'} requestType + * @param {String} description + * @param {Array} successCodes + * @param {Function} callback */ var fireRequest = function (requestOptions, requestType, description, successCodes, callback) { var httpOrHttps = requestType === 'http' ? http : https; @@ -66,7 +68,8 @@ var ConnectivityChecker = { connectionChecks: [], reqOpsWithoutProxy: function () {}, - reqOpsWithProxy: function () {}, + reqOpsHttpWithProxy: function () {}, + reqOpsHttpsWithProxy: function () {}, httpToHubWithoutProxy: function (callback) { var requestUrl = constants.HUB_STATUS_URL; @@ -85,7 +88,7 @@ var ConnectivityChecker = { }, httpsToHubWithoutProxy: function (callback) { - var requestUrl = constants.HUB_STATUS_URL; + var requestUrl = constants.HUB_STATUS_URL_HTTPS; var requestOptions = ConnectivityChecker.reqOpsWithoutProxy(requestUrl, 'https'); fireRequest(requestOptions, 'https', 'HTTPS Request To ' + requestUrl + ' Without Proxy', [200], function (response) { callback(response); @@ -93,7 +96,7 @@ var ConnectivityChecker = { }, httpsToRailsWithoutProxy: function (callback) { - var requestUrl = constants.RAILS_AUTOMATE; + var requestUrl = constants.RAILS_AUTOMATE_HTTPS; var requestOptions = ConnectivityChecker.reqOpsWithoutProxy(requestUrl, 'https'); fireRequest(requestOptions, 'https', 'HTTPS Request to ' + requestUrl + ' Without Proxy', [301, 302], function (response) { callback(response); @@ -102,7 +105,7 @@ var ConnectivityChecker = { httpToHubWithProxy: function (callback) { var requestUrl = constants.HUB_STATUS_URL; - var requestOptions = ConnectivityChecker.reqOpsWithProxy(requestUrl, 'http'); + var requestOptions = ConnectivityChecker.reqOpsHttpWithProxy(requestUrl, 'http'); fireRequest(requestOptions, 'http', 'HTTP Request To ' + requestUrl + ' With Proxy', [200], function (response) { callback(response); }); @@ -110,12 +113,27 @@ var ConnectivityChecker = { httpToRailsWithProxy: function (callback) { var requestUrl = constants.RAILS_AUTOMATE; - var requestOptions = ConnectivityChecker.reqOpsWithProxy(requestUrl, 'http'); - fireRequest(requestOptions, 'http', 'HTTP Request To ' + requestUrl + ' With Proxy', [301], function (response) { + var requestOptions = ConnectivityChecker.reqOpsHttpWithProxy(requestUrl, 'http'); + fireRequest(requestOptions, 'http', 'HTTP Request To ' + requestUrl + ' With Proxy', [301, 302], function (response) { callback(response); }); }, + httpsToHubWithProxy: function (callback) { + var requestUrl = constants.HUB_STATUS_URL_HTTPS; + var requestOptions = ConnectivityChecker.reqOpsHttpsWithProxy(requestUrl, 'https'); + fireRequest(requestOptions, 'https', 'HTTPS Request To ' + requestUrl + ' With Proxy', [200], function (response) { + callback(response); + }); + }, + + httpsToRailsWithProxy: function (callback) { + var requestUrl = constants.RAILS_AUTOMATE_HTTPS; + var requestOptions = ConnectivityChecker.reqOpsHttpsWithProxy(requestUrl, 'https'); + fireRequest(requestOptions, 'https', 'HTTPS Request To ' + requestUrl + ' With Proxy', [301, 302], function (response) { + callback(response); + }); + }, /** * Decides the checks to perform based on whether any proxy is provided by the @@ -137,21 +155,40 @@ var ConnectivityChecker = { return reqOptions; }; + if (RdGlobalConfig.proxy) { + var proxyOpts = url.parse(RdGlobalConfig.proxy.host + ":" +RdGlobalConfig.proxy.port); + if (RdGlobalConfig.proxy.username && RdGlobalConfig.proxy.password) { + proxyOpts.auth = RdGlobalConfig.proxy.username + ":" + RdGlobalConfig.proxy.password; + } + ConnectivityChecker.connectionChecks.push(this.httpToHubWithProxy, this.httpToRailsWithProxy); /* eslint-disable-next-line no-unused-vars */ - ConnectivityChecker.reqOpsWithProxy = function (reqUrl, reqType) { + ConnectivityChecker.reqOpsHttpWithProxy = function (reqUrl, reqType) { + var parsedUrl = url.parse(reqUrl); + var reqOptions = { + method: 'GET', + headers: {}, + host: parsedUrl.hostname, + port: parsedUrl.port || 80, + path: parsedUrl.path, + agent: new HttpProxyAgent(proxyOpts) + }; + return reqOptions; + }; + + ConnectivityChecker.connectionChecks.push(this.httpsToHubWithProxy, this.httpsToRailsWithProxy); + /* eslint-disable-next-line no-unused-vars */ + ConnectivityChecker.reqOpsHttpsWithProxy = function (reqUrl, reqType) { var parsedUrl = url.parse(reqUrl); var reqOptions = { method: 'GET', headers: {}, - host: RdGlobalConfig.proxy.host, - port: RdGlobalConfig.proxy.port, - path: parsedUrl.href + host: parsedUrl.hostname, + port: parsedUrl.port || 443, + path: parsedUrl.path, + agent: new HttpsProxyAgent(proxyOpts) }; - if (RdGlobalConfig.proxy.username && RdGlobalConfig.proxy.password) { - reqOptions.headers['Proxy-Authorization'] = Utils.proxyAuthToBase64(RdGlobalConfig.proxy); - } return reqOptions; }; } @@ -160,9 +197,9 @@ var ConnectivityChecker = { /** * Fires the Connectivity Checks in Async Manner - * @param {String} topic - * @param {Number|String} uuid - * @param {Function} callback + * @param {String} topic + * @param {Number|String} uuid + * @param {Function} callback */ fireChecks: function (topic, uuid, callback) { ConnectivityChecker.decideConnectionChecks(); diff --git a/src/logger.js b/src/logger.js index 6bbdc43..efdccb0 100644 --- a/src/logger.js +++ b/src/logger.js @@ -19,7 +19,7 @@ var LogManager = { * Initializes a logger to the given file and returns the methods to * interact with the logger. * Currently, 'info' and 'error' are defined. THe rest can be taken up later if required. - * @param {String} filename + * @param {String} filename */ initializeLogger: function (filename) { var newLogger = LogManager.getLogger(filename); @@ -36,7 +36,7 @@ var LogManager = { newLogger.transports.file.json = false; newLogger.info("************* LOGGER INITIALIZED **************\r\n"); - + return { info: function (topic, message, stringify, data, uuid) { stringify = stringify || false; diff --git a/src/proxy.js b/src/proxy.js new file mode 100644 index 0000000..bf18c67 --- /dev/null +++ b/src/proxy.js @@ -0,0 +1,177 @@ +/** + * Proxy Server to Intercept the client's requests and handle them on their behalf. + * Initiates stats and connectivity checks when a requests fails. + * It also responds in selenium understandable error when a request fails + * at tool. + */ + +var http = require('http'); +var url = require('url'); +var uuidv4 = require('uuid/v4'); +var Utils = require('./utils'); +var constants = require('../config/constants'); +var ReqLib = require('./requestLib'); + +var RdGlobalConfig = constants.RdGlobalConfig; + +var RdHandler = { + + _requestCounter: 0, + + /** + * Generates the request options for firing requests + * @param {http.IncomingMessage} clientRequest + * @returns {Object} + */ + _generateRequestOptions: function (clientRequest) { + var requestOptions = { + headers: {} + }; + var parsedClientUrl = url.parse(clientRequest.url); + requestOptions.host = parsedClientUrl.host; + requestOptions.port = RdGlobalConfig.SCHEME == 'http' ? 80 : 443; + requestOptions.path = parsedClientUrl.path; + requestOptions.method = clientRequest.method; + requestOptions.headers = clientRequest.headers; + requestOptions.headers['X-Requests-Debugger'] = clientRequest.id; + if (parsedClientUrl.auth) { + requestOptions.headers['Authorization'] = Utils.proxyAuthToBase64(parsedClientUrl.auth); + } + return requestOptions; + }, + + /** + * Frames the error response based on the type of request. + * i.e., if its a request originating for Hub, the response + * is in the format which the client binding would understand. + * @param {Object} parsedRequest + * @param {String} errorMessage + */ + _frameErrorResponse: function (parsedRequest, errorMessage) { + errorMessage += '. ' + constants.STATIC_MESSAGES.REQ_FAILED_MSG; + var parseSessionId = parsedRequest.path.match(/\/wd\/hub\/session\/([a-z0-9]+)\/*/); + if (parseSessionId) { + var sessionId = parseSessionId[1]; + return { + data: { + sessionId: sessionId, + status: 13, + value: { + message: errorMessage, + error: constants.STATIC_MESSAGES.REQ_FAILED_MSG + }, + state: 'error' + }, + statusCode: constants.CUSTOM_ERROR_RESPONSE_CODE + }; + } else { + return { + data: { + message: errorMessage, + error: constants.STATIC_MESSAGES.REQ_FAILED_MSG + }, + statusCode: constants.CUSTOM_ERROR_RESPONSE_CODE + }; + } + }, + + /** + * Handler for incoming requests to Requests Debugger Tool server. + * @param {http.IncomingMessage} clientRequest + * @param {http.ServerResponse} clientResponse + */ + requestHandler: function (clientRequest, clientResponse) { + var parsedClientUrl = url.parse(clientRequest.url); + var metaData = { + clientRequestUrl: clientRequest.url, + toolRequestUrl: RdGlobalConfig.SCHEME + "://" + parsedClientUrl.host + parsedClientUrl.path + }; + clientRequest.id = ++RdHandler._requestCounter + '::' + uuidv4(); + var request = { + method: clientRequest.method, + url: clientRequest.url, + headers: clientRequest.headers, + data: [] + }; + RdGlobalConfig.reqLogger.info(constants.TOPICS.CLIENT_REQUEST_START, clientRequest.method + ' ' + metaData.clientRequestUrl, + false, { + headers: request.headers + }, + clientRequest.id); + + var furtherRequestOptions = RdHandler._generateRequestOptions(clientRequest); + + var paramsForRequest = { + request: request, + furtherRequestOptions: furtherRequestOptions + }; + + ReqLib.call(paramsForRequest, clientRequest, metaData) + .then(function (response) { + RdGlobalConfig.reqLogger.info(constants.TOPICS.CLIENT_RESPONSE_END, clientRequest.method + ' ' + metaData.clientRequestUrl + ', Status Code: ' + response.statusCode, + false, { + data: response.data, + headers: response.headers, + }, + clientRequest.id); + + clientResponse.writeHead(response.statusCode, response.headers); + clientResponse.end(response.data); + }) + .catch(function (err) { + RdGlobalConfig.reqLogger.error(err.customTopic || constants.TOPICS.UNEXPECTED_ERROR, clientRequest.method + ' ' + metaData.toolRequestUrl, + false, { + errorMessage: err.message.toString() + }, + clientRequest.id); + + var errorResponse = RdHandler._frameErrorResponse(furtherRequestOptions, err.message.toString()); + RdGlobalConfig.reqLogger.error(constants.TOPICS.CLIENT_RESPONSE_END, clientRequest.method + ' ' + metaData.clientRequestUrl + ', Status Code: ' + errorResponse.statusCode, + false, + errorResponse.data, + clientRequest.id); + + clientResponse.writeHead(errorResponse.statusCode); + clientResponse.end(JSON.stringify(errorResponse.data)); + }); + }, + + /** + * Starts the proxy server on the given port + * @param {String|Number} port + * @param {Function} callback + */ + startProxyServer: function (port, callback) { + try { + RdHandler.proxyServer = http.createServer(RdHandler.requestHandler); + RdHandler.proxyServer.listen(port); + RdHandler.proxyServer.on('listening', function () { + callback(null, port); + }); + RdHandler.proxyServer.on('error', function (err) { + callback(err.toString(), null); + }); + } catch (e) { + callback(e.toString(), null); + } + }, + + /** + * Stops the currently running proxy server + * @param {Function} callback + */ + stopProxyServer: function (callback) { + try { + if (RdHandler.proxyServer) { + RdHandler.proxyServer.close(); + RdHandler.proxyServer = null; + } + callback(null, true); + } catch (e) { + callback(e.toString(), null); + } + }, + +}; + +module.exports = RdHandler; diff --git a/src/requestLib.js b/src/requestLib.js index 4e5e318..159663b 100644 --- a/src/requestLib.js +++ b/src/requestLib.js @@ -1,29 +1,41 @@ var http = require('http'); +var https = require('https'); var constants = require('../config/constants'); var Utils = require('./utils'); -var keepAliveAgent = new http.Agent({ - keepAlive: true -}); -var RdGlobalConfig = constants.RdGlobalConfig; +var url = require('url'); +var HttpProxyAgent = require('http-proxy-agent'); +var HttpsProxyAgent = require('https-proxy-agent'); +var RdGlobalConfig = constants.RdGlobalConfig; +var httpKeepAliveAgent = new http.Agent({keepAlive: true}); +var httpsKeepAliveAgent = new https.Agent({keepAlive: true}); +var httpProxyAgent = null; +var httpsProxyAgent = null; var RequestLib = { + /** * Method to perform the request on behalf of the client - * @param {{request: Object, furtherRequestOptions: Object}} params - * @param {http.IncomingMessage} clientRequest - * @param {Number} retries + * @param {schemeObj: Object} schemeObj + * @param {{request: Object, furtherRequestOptions: Object}} params + * @param {http.IncomingMessage} clientRequest + * @param {Number} retries */ - _makeRequest: function (params, clientRequest, retries) { + _makeRequest: function (schemeObj, params, clientRequest, metaData, retries) { return new Promise(function (resolve, reject) { - var requestOptions = Object.assign({}, params.furtherRequestOptions, { - agent: keepAliveAgent - }); - - // Adding a custom header for usage and debugging purpose at BrowserStack - requestOptions.headers['X-Requests-Debugger'] = clientRequest.id; - - // Initialize the request to be fired on behalf of the client - var request = http.request(requestOptions, function (response) { + var requestOptions = Object.assign({}, params.furtherRequestOptions); + requestOptions.agent = RdGlobalConfig.SCHEME === 'http' ? httpKeepAliveAgent : httpsKeepAliveAgent; + if (RdGlobalConfig.proxy) { + if (!httpProxyAgent && !httpsProxyAgent) { + var proxyOpts = url.parse(RdGlobalConfig.proxy.host + ":" +RdGlobalConfig.proxy.port); + if (RdGlobalConfig.proxy.username && RdGlobalConfig.proxy.password) { + proxyOpts.auth = RdGlobalConfig.proxy.username + ":" + RdGlobalConfig.proxy.password; + } + httpProxyAgent = new HttpProxyAgent(proxyOpts); + httpsProxyAgent = new HttpsProxyAgent(proxyOpts); + } + requestOptions.agent = RdGlobalConfig.SCHEME === 'http' ? httpProxyAgent : httpsProxyAgent; + } + var request = schemeObj.request(requestOptions, function (response) { var responseToSend = { statusCode: response.statusCode, headers: response.headers, @@ -49,9 +61,8 @@ var RequestLib = { // Log the request that will be initiated on behalf of the client request.on('finish', function () { - RdGlobalConfig.reqLogger.info(constants.TOPICS.TOOL_REQUEST_WITH_RETRIES + retries, clientRequest.method + ' ' + clientRequest.url, - false, - Object.assign({}, params.furtherRequestOptions, { + RdGlobalConfig.reqLogger.info(constants.TOPICS.TOOL_REQUEST_WITH_RETRIES + retries, + clientRequest.method + ' ' + metaData.toolRequestUrl, false, Object.assign({}, params.furtherRequestOptions, { data: Buffer.concat(params.request.data).toString() }), clientRequest.id); @@ -86,7 +97,7 @@ var RequestLib = { }); } }); - + clientRequest.on('error', function (err) { request.end(); reject({ @@ -94,9 +105,9 @@ var RequestLib = { customTopic: constants.TOPICS.CLIENT_REQUEST_WITH_RETRIES + retries }); }); - + clientRequest.on('end', function () { - RdGlobalConfig.reqLogger.info(constants.TOPICS.CLIENT_REQUEST_END, params.request.method + ' ' + params.request.url, false, { + RdGlobalConfig.reqLogger.info(constants.TOPICS.CLIENT_REQUEST_END, params.request.method + ' ' + metaData.clientRequestUrl, false, { data: Buffer.concat(params.request.data).toString() }, clientRequest.id); @@ -111,13 +122,14 @@ var RequestLib = { /** * Handler for performing request. Includes the retry mechanism when request fails. - * @param {{request: Object, furtherRequestOptions: Object}} params - * @param {http.IncomingMessage} clientRequest - * @param {Number} retries + * @param {{request: Object, furtherRequestOptions: Object}} params + * @param {http.IncomingMessage} clientRequest + * @param {Number} retries */ - call: function (params, clientRequest, retries) { + call: function (params, clientRequest, metaData, retries) { retries = (typeof retries === 'number') ? Math.min(constants.MAX_RETRIES, Math.max(retries, 0)) : constants.MAX_RETRIES; - return RequestLib._makeRequest(params, clientRequest, retries) + var schemeObj = RdGlobalConfig.SCHEME === "http" ? http : https; + return RequestLib._makeRequest(schemeObj, params, clientRequest, metaData, retries) .catch(function (err) { var errTopic = err.customTopic || constants.TOPICS.UNEXPECTED_ERROR; // Collect Network & Connectivity Logs whenever a request fails @@ -125,7 +137,7 @@ var RequestLib = { RdGlobalConfig.connHandler(errTopic, clientRequest.id); if (retries > 0) { - RdGlobalConfig.reqLogger.error(errTopic, clientRequest.method + ' ' + clientRequest.url, + RdGlobalConfig.reqLogger.error(errTopic, clientRequest.method + ' ' + metaData.toolRequestUrl, false, { errorMessage: err.message.toString() }, @@ -133,7 +145,7 @@ var RequestLib = { return Utils.delay(RdGlobalConfig.RETRY_DELAY) .then(function () { - return RequestLib.call(params, clientRequest, retries - 1, false); + return RequestLib.call(params, clientRequest, metaData, retries - 1, false); }); } else { throw err; @@ -143,4 +155,3 @@ var RequestLib = { }; module.exports = RequestLib; - diff --git a/src/requestsDebugger.js b/src/requestsDebugger.js index 765e76e..b35f718 100644 --- a/src/requestsDebugger.js +++ b/src/requestsDebugger.js @@ -11,7 +11,8 @@ var RdGlobalConfig = constants.RdGlobalConfig; var STATIC_MESSAGES = constants.STATIC_MESSAGES; var CommandLineManager = require('./commandLine'); var ConnectivityChecker = require('./connectivity'); -var RdHandler = require('./server'); +var proxy = require('./proxy'); +var reverseProxy = require('./reverseProxy'); var StatsFactory = require('./stats/statsFactory'); var LogManager = require('./logger'); var fs = require('fs'); @@ -111,42 +112,51 @@ var RdTool = { */ start: function () { CommandLineManager.processArgs(process.argv); - console.log(Utils.formatAndBeautifyLine(STATIC_MESSAGES.STARTING_TOOL, '-', '-', 60, true)); + console.log(Utils.formatAndBeautifyLine(STATIC_MESSAGES.STARTING_TOOL, '-', '-', constants.LINE_LENGTH, true)); RdTool.initLoggersAndHandlers(); /* eslint-disable indent */ console.log(Utils.formatAndBeautifyLine("Refer '" + RdGlobalConfig.LOGS_DIRECTORY + "' folder for CPU/Network/Memory" + " Stats and Connectivity Checks with BrowserStack components", '', '-', 60, true)); /*eslint-enable indent*/ - - console.log(Utils.formatAndBeautifyLine(STATIC_MESSAGES.CHECK_CPU_STATS, '', '-', 60, true)); + console.log(Utils.formatAndBeautifyLine(STATIC_MESSAGES.CHECK_CPU_STATS, '', '-', constants.LINE_LENGTH, true)); RdGlobalConfig.cpuLogHandler('Initial CPU', null, function () { - console.log(Utils.formatAndBeautifyLine(STATIC_MESSAGES.CPU_STATS_COLLECTED, '', '-', 60, true)); + console.log(Utils.formatAndBeautifyLine(STATIC_MESSAGES.CPU_STATS_COLLECTED, '', '-', constants.LINE_LENGTH, true)); }); - console.log(Utils.formatAndBeautifyLine(STATIC_MESSAGES.CHECK_NETWORK_STATS, '', '-', 60, true)); + console.log(Utils.formatAndBeautifyLine(STATIC_MESSAGES.CHECK_NETWORK_STATS, '', '-', constants.LINE_LENGTH, true)); RdGlobalConfig.networkLogHandler('Initial Network', null, function () { - console.log(Utils.formatAndBeautifyLine(STATIC_MESSAGES.NETWORK_STATS_COLLECTED, '', '-', 60, true)); + console.log(Utils.formatAndBeautifyLine(STATIC_MESSAGES.NETWORK_STATS_COLLECTED, '', '-', constants.LINE_LENGTH, true)); }); - console.log(Utils.formatAndBeautifyLine(STATIC_MESSAGES.CHECK_MEMORY_STATS, '', '-', 60, true)); + console.log(Utils.formatAndBeautifyLine(STATIC_MESSAGES.CHECK_MEMORY_STATS, '', '-', constants.LINE_LENGTH, true)); RdGlobalConfig.memLogHandler('Initial Memory', null, function () { - console.log(Utils.formatAndBeautifyLine(STATIC_MESSAGES.MEMORY_STATS_COLLECTED, '', '-', 60, true)); + console.log(Utils.formatAndBeautifyLine(STATIC_MESSAGES.MEMORY_STATS_COLLECTED, '', '-', constants.LINE_LENGTH, true)); }); - console.log(Utils.formatAndBeautifyLine(STATIC_MESSAGES.CHECK_CONNECTIVITY, '', '-', 60, true)); + console.log(Utils.formatAndBeautifyLine(STATIC_MESSAGES.CHECK_CONNECTIVITY, '', '-', constants.LINE_LENGTH, true)); RdGlobalConfig.connHandler('Initial Connectivity', null, function () { - console.log(Utils.formatAndBeautifyLine(STATIC_MESSAGES.CONNECTIVITY_CHECKS_DONE, '', '-', 60, true)); + console.log(Utils.formatAndBeautifyLine(STATIC_MESSAGES.CONNECTIVITY_CHECKS_DONE, '', '-', constants.LINE_LENGTH, true)); }); - RdHandler.startProxy(RdGlobalConfig.RD_HANDLER_PORT, function (err, result) { + proxy.startProxyServer(RdGlobalConfig.RD_HANDLER_PROXY_PORT, function (err, result) { if (err) { - console.log(STATIC_MESSAGES.ERR_STARTING_TOOL, err); - console.log('Exiting the Tool...'); + console.log(STATIC_MESSAGES.ERR_STARTING_TOOL_PROXY, err); + console.log('Exiting the Proxy Server...'); process.exit(1); } - console.log(Utils.formatAndBeautifyLine(STATIC_MESSAGES.TOOL_STARTED_ON_PORT + result, '', '-', 60, true)); + console.log(Utils.formatAndBeautifyLine(STATIC_MESSAGES.TOOL_PROXY_STARTED_ON_PORT + result, '', '-', constants.LINE_LENGTH, true)); }); + + reverseProxy.startReverseProxyServer(RdGlobalConfig.RD_HANDLER_REVERSE_PROXY_PORT, function (err, result) { + if (err) { + console.log(STATIC_MESSAGES.ERR_STARTING_TOOL_REVERSE_PROXY, err); + console.log('Exiting the Reverse Proxy Server...'); + process.exit(1); + } + console.log(Utils.formatAndBeautifyLine(STATIC_MESSAGES.TOOL_REVESE_PROXY_STARTED_ON_PORT + result, '', '-', constants.LINE_LENGTH, true)); + }); + } }; diff --git a/src/reverseProxy.js b/src/reverseProxy.js new file mode 100644 index 0000000..0b7580b --- /dev/null +++ b/src/reverseProxy.js @@ -0,0 +1,178 @@ +/** + * Reverse Proxy Server to Intercept the client's requests and handle them on their behalf. + * Initiates stats and connectivity checks when a requests fails. + * It also responds in selenium understandable error when a request fails + * at tool. + */ + +var http = require('http'); +var url = require('url'); +var uuidv4 = require('uuid/v4'); +var Utils = require('./utils'); +var constants = require('../config/constants'); +var ReqLib = require('./requestLib'); + +var RdGlobalConfig = constants.RdGlobalConfig; + +var RdHandler = { + + _requestCounter: 0, + + /** + * Generates the request options for firing requests + * @param {http.IncomingMessage} clientRequest + * @returns {Object} + */ + _generateRequestOptions: function (clientRequest) { + var requestOptions = { + headers: {} + }; + var parsedClientUrl = url.parse(clientRequest.url); + requestOptions.port = RdGlobalConfig.SCHEME == 'http' ? 80 : 443; + requestOptions.path = parsedClientUrl.path; + requestOptions.method = clientRequest.method; + // Below line copies clientRequest.headers object to requestOptions.headers instead of refering same object + requestOptions.headers = Object.assign({}, clientRequest.headers); + requestOptions.host = constants.HUB_HOST; + requestOptions.headers.host = constants.HUB_HOST; + requestOptions.headers['X-Requests-Debugger'] = clientRequest.id; + if (parsedClientUrl.auth) { + requestOptions.headers['Authorization'] = Utils.proxyAuthToBase64(parsedClientUrl.auth); + } + return requestOptions; + }, + + /** + * Frames the error response based on the type of request. + * i.e., if its a request originating for Hub, the response + * is in the format which the client binding would understand. + * @param {Object} parsedRequest + * @param {String} errorMessage + */ + _frameErrorResponse: function (parsedRequest, errorMessage) { + errorMessage += '. ' + constants.STATIC_MESSAGES.REQ_FAILED_MSG; + var parseSessionId = parsedRequest.path.match(/\/wd\/hub\/session\/([a-z0-9]+)\/*/); + if (parseSessionId) { + var sessionId = parseSessionId[1]; + return { + data: { + sessionId: sessionId, + status: 13, + value: { + message: errorMessage, + error: constants.STATIC_MESSAGES.REQ_FAILED_MSG + }, + state: 'error' + }, + statusCode: constants.CUSTOM_ERROR_RESPONSE_CODE + }; + } else { + return { + data: { + message: errorMessage, + error: constants.STATIC_MESSAGES.REQ_FAILED_MSG + }, + statusCode: constants.CUSTOM_ERROR_RESPONSE_CODE + }; + } + }, + + /** + * Handler for incoming requests to Requests Debugger Tool server. + * @param {http.IncomingMessage} clientRequest + * @param {http.ServerResponse} clientResponse + */ + requestHandler: function (clientRequest, clientResponse) { + var parsedClientUrl = url.parse(clientRequest.url); + var metaData = { + clientRequestUrl: "http://" + clientRequest.headers.host + parsedClientUrl.path, + toolRequestUrl: RdGlobalConfig.SCHEME + "://" + constants.HUB_HOST + parsedClientUrl.path + }; + clientRequest.id = ++RdHandler._requestCounter + '::' + uuidv4(); + var request = { + method: clientRequest.method, + url: clientRequest.url, + headers: clientRequest.headers, + data: [] + }; + RdGlobalConfig.reqLogger.info(constants.TOPICS.CLIENT_REQUEST_START, clientRequest.method + ' ' + metaData.clientRequestUrl, + false, { + headers: request.headers + }, + clientRequest.id); + + var furtherRequestOptions = RdHandler._generateRequestOptions(clientRequest); + + var paramsForRequest = { + request: request, + furtherRequestOptions: furtherRequestOptions + }; + ReqLib.call(paramsForRequest, clientRequest, metaData) + .then(function (response) { + RdGlobalConfig.reqLogger.info(constants.TOPICS.CLIENT_RESPONSE_END, clientRequest.method + ' ' + metaData.clientRequestUrl + ', Status Code: ' + response.statusCode, + false, { + data: response.data, + headers: response.headers, + }, + clientRequest.id); + + clientResponse.writeHead(response.statusCode, response.headers); + clientResponse.end(response.data); + }) + .catch(function (err) { + RdGlobalConfig.reqLogger.error(err.customTopic || constants.TOPICS.UNEXPECTED_ERROR, clientRequest.method + ' ' + metaData.toolRequestUrl, + false, { + errorMessage: err.message.toString() + }, + clientRequest.id); + + var errorResponse = RdHandler._frameErrorResponse(furtherRequestOptions, err.message.toString()); + RdGlobalConfig.reqLogger.error(constants.TOPICS.CLIENT_RESPONSE_END, clientRequest.method + ' ' + metaData.clientRequestUrl + ', Status Code: ' + errorResponse.statusCode, + false, + errorResponse.data, + clientRequest.id); + + clientResponse.writeHead(errorResponse.statusCode); + clientResponse.end(JSON.stringify(errorResponse.data)); + }); + }, + + /** + * Starts the reverse proxy server on the given port + * @param {String|Number} port + * @param {Function} callback + */ + startReverseProxyServer: function (port, callback) { + try { + RdHandler.reverseProxyServer = http.createServer(RdHandler.requestHandler); + RdHandler.reverseProxyServer.listen(port); + RdHandler.reverseProxyServer.on('listening', function () { + callback(null, port); + }); + RdHandler.reverseProxyServer.on('error', function (err) { + callback(err.toString(), null); + }); + } catch (e) { + callback(e.toString(), null); + } + }, + + /** + * Stops the currently running reverse proxy server + * @param {Function} callback + */ + stopReverseProxyServer: function (callback) { + try { + if (RdHandler.reverseProxyServer) { + RdHandler.reverseProxyServer.close(); + RdHandler.reverseProxyServer = null; + } + callback(null, true); + } catch (e) { + callback(e.toString(), null); + } + } + +}; + +module.exports = RdHandler; diff --git a/src/server.js b/src/server.js deleted file mode 100644 index 451f326..0000000 --- a/src/server.js +++ /dev/null @@ -1,211 +0,0 @@ -/** - * Server to Intercept the client's requests and handle them on their behalf. - * Initiates stats and connectivity checks when a requests fails. - * It also responds in selenium understandable error when a request fails - * at tool. - */ - -var http = require('http'); -var url = require('url'); -var uuidv4 = require('uuid/v4'); -var Utils = require('./utils'); -var constants = require('../config/constants'); -var ReqLib = require('./requestLib'); -var RdGlobalConfig = constants.RdGlobalConfig; - -var RdHandler = { - - _requestCounter: 0, - - /** - * Generates the request options template for firing requests based on - * whether the user had provided any proxy input or not. - */ - generatorForRequestOptionsObject: function () { - RdHandler._reqObjTemplate = { - method: null, - headers: {}, - host: null, - port: null, - path: null - }; - - if (RdGlobalConfig.proxy) { - RdHandler._reqObjTemplate.host = RdGlobalConfig.proxy.host; - RdHandler._reqObjTemplate.port = RdGlobalConfig.proxy.port; - - if (RdGlobalConfig.proxy.username && RdGlobalConfig.proxy.password) { - RdHandler._reqObjTemplate.headers['Proxy-Authorization'] = Utils.proxyAuthToBase64(RdGlobalConfig.proxy); - } - - /** - * Sets the internal method to generate request options if external/upstream - * proxy exists - * @param {http.IncomingMessage} clientRequest - * @returns {Object} - */ - RdHandler._generateRequestOptions = function (clientRequest) { - var parsedClientUrl = url.parse(clientRequest.url); - var headersCopy = Object.assign({}, clientRequest.headers, RdHandler._reqObjTemplate.headers); - var requestOptions = Object.assign({}, RdHandler._reqObjTemplate); - requestOptions.path = parsedClientUrl.href; - requestOptions.method = clientRequest.method; - requestOptions.headers = headersCopy; - return requestOptions; - }; - } else { - - /** - * Sets the internal method to generate request options if external/upstream proxy - * doesn't exists - * @param {http.IncomingMessage} clientRequest - * @returns {Object} - */ - RdHandler._generateRequestOptions = function (clientRequest) { - var parsedClientUrl = url.parse(clientRequest.url); - var requestOptions = Object.assign({}, RdHandler._reqObjTemplate); - requestOptions.host = parsedClientUrl.hostname; - requestOptions.port = parsedClientUrl.port || 80; - requestOptions.path = parsedClientUrl.path; - requestOptions.method = clientRequest.method; - requestOptions.headers = clientRequest.headers; - if (parsedClientUrl.auth) { - requestOptions.headers['authorization'] = Utils.proxyAuthToBase64(parsedClientUrl.auth); - } - return requestOptions; - }; - } - }, - - /** - * Frames the error response based on the type of request. - * i.e., if its a request originating for Hub, the response - * is in the format which the client binding would understand. - * @param {Object} parsedRequest - * @param {String} errorMessage - */ - _frameErrorResponse: function (parsedRequest, errorMessage) { - errorMessage += '. ' + constants.STATIC_MESSAGES.REQ_FAILED_MSG; - var parseSessionId = parsedRequest.path.match(/\/wd\/hub\/session\/([a-z0-9]+)\/*/); - if (parseSessionId) { - var sessionId = parseSessionId[1]; - return { - data: { - sessionId: sessionId, - status: 13, - value: { - message: errorMessage, - error: constants.STATIC_MESSAGES.REQ_FAILED_MSG - }, - state: 'error' - }, - statusCode: constants.CUSTOM_ERROR_RESPONSE_CODE - }; - } else { - return { - data: { - message: errorMessage, - error: constants.STATIC_MESSAGES.REQ_FAILED_MSG - }, - statusCode: constants.CUSTOM_ERROR_RESPONSE_CODE - }; - } - }, - - /** - * Handler for incoming requests to Requests Debugger Tool proxy server. - * @param {http.IncomingMessage} clientRequest - * @param {http.ServerResponse} clientResponse - */ - requestHandler: function (clientRequest, clientResponse) { - clientRequest.id = ++RdHandler._requestCounter + '::' + uuidv4(); - - var request = { - method: clientRequest.method, - url: clientRequest.url, - headers: clientRequest.headers, - data: [] - }; - - RdGlobalConfig.reqLogger.info(constants.TOPICS.CLIENT_REQUEST_START, request.method + ' ' + request.url, - false, { - headers: request.headers - }, - clientRequest.id); - - var furtherRequestOptions = RdHandler._generateRequestOptions(clientRequest); - - var paramsForRequest = { - request: request, - furtherRequestOptions: furtherRequestOptions - }; - - ReqLib.call(paramsForRequest, clientRequest) - .then(function (response) { - RdGlobalConfig.reqLogger.info(constants.TOPICS.CLIENT_RESPONSE_END, clientRequest.method + ' ' + clientRequest.url + ', Status Code: ' + response.statusCode, - false, { - data: response.data, - headers: response.headers, - }, - clientRequest.id); - - clientResponse.writeHead(response.statusCode, response.headers); - clientResponse.end(response.data); - }) - .catch(function (err) { - RdGlobalConfig.reqLogger.error(err.customTopic || constants.TOPICS.UNEXPECTED_ERROR, clientRequest.method + ' ' + clientRequest.url, - false, { - errorMessage: err.message.toString() - }, - clientRequest.id); - - var errorResponse = RdHandler._frameErrorResponse(furtherRequestOptions, err.message.toString()); - RdGlobalConfig.reqLogger.error(constants.TOPICS.CLIENT_RESPONSE_END, clientRequest.method + ' ' + clientRequest.url + ', Status Code: ' + errorResponse.statusCode, - false, - errorResponse.data, - clientRequest.id); - - clientResponse.writeHead(errorResponse.statusCode); - clientResponse.end(JSON.stringify(errorResponse.data)); - }); - }, - - /** - * Starts the proxy server on the given port - * @param {String|Number} port - * @param {Function} callback - */ - startProxy: function (port, callback) { - try { - RdHandler.generatorForRequestOptionsObject(); - RdHandler.server = http.createServer(RdHandler.requestHandler); - RdHandler.server.listen(port); - RdHandler.server.on('listening', function () { - callback(null, port); - }); - RdHandler.server.on('error', function (err) { - callback(err.toString(), null); - }); - } catch (e) { - callback(e.toString(), null); - } - }, - - /** - * Stops the currently running proxy server - * @param {Function} callback - */ - stopProxy: function (callback) { - try { - if (RdHandler.server) { - RdHandler.server.close(); - RdHandler.server = null; - } - callback(null, true); - } catch (e) { - callback(e.toString(), null); - } - } -}; - -module.exports = RdHandler; diff --git a/src/stats/linuxStats.js b/src/stats/linuxStats.js index 3aff278..4c70d72 100644 --- a/src/stats/linuxStats.js +++ b/src/stats/linuxStats.js @@ -45,7 +45,6 @@ LinuxStats.mem = function (callback) { memStats.free = parseInt(Utils.fetchPropertyValue(memStatLines, 'memfree')); memStats.free = memStats.free ? memStats.free * 1024 : os.freemem(); memStats.used = memStats.total - memStats.free; - memStats.swapTotal = parseInt(Utils.fetchPropertyValue(memStatLines, 'swaptotal')); memStats.swapTotal = memStats.swapTotal ? memStats.swapTotal * 1024 : 0; memStats.swapFree = parseInt(Utils.fetchPropertyValue(memStatLines, 'swapfree')); diff --git a/src/stats/macStats.js b/src/stats/macStats.js index 49afc7d..fe32928 100644 --- a/src/stats/macStats.js +++ b/src/stats/macStats.js @@ -49,11 +49,11 @@ MacStats.mem = function (callback) { case 'total': memStats.swapTotal = parseFloat(statLines[index].split('=')[1].trim()) * 1024 * 1024; break; - + case 'used': memStats.swapUsed = parseFloat(statLines[index].split('=')[1].trim()) * 1024 * 1024; break; - + case 'free': memStats.swapFree = parseFloat(statLines[index].split('=')[1].trim()) * 1024 * 1024; break; diff --git a/src/utils.js b/src/utils.js index e71821a..20c69b3 100644 --- a/src/utils.js +++ b/src/utils.js @@ -8,7 +8,7 @@ var fs = require('fs'); * Input can be in the form of: * 1. 'user:pass' * 2. { username: 'user', password: 'pass' } - * @param {String|{username: String, password: String, host: String, port: String|Number}} proxyObj + * @param {String|{username: String, password: String, host: String, port: String|Number}} proxyObj * @returns {String} */ var proxyAuthToBase64 = function (proxyObj) { @@ -23,9 +23,9 @@ var proxyAuthToBase64 = function (proxyObj) { /** * Fetch the property value from the string array of content, each separated by a separator - * @param {Array} content - * @param {String} propertyToFetch - * @param {String} separator + * @param {Array} content + * @param {String} propertyToFetch + * @param {String} separator * @returns {String} */ var fetchPropertyValue = function (content, propertyToFetch, separator) { @@ -48,11 +48,11 @@ var fetchPropertyValue = function (content, propertyToFetch, separator) { /** * Beautifies the lines and add prefix/suffix characters to make the line of the required length. - * @param {String} line - * @param {String} prefix - * @param {String} suffix - * @param {Number} idealLength - * @param {Boolean} newLine + * @param {String} line + * @param {String} prefix + * @param {String} suffix + * @param {Number} idealLength + * @param {Boolean} newLine * @returns {String} */ var formatAndBeautifyLine = function (line, prefix, suffix, idealLength, newLine) { @@ -79,10 +79,10 @@ var formatAndBeautifyLine = function (line, prefix, suffix, idealLength, newLine /** * Generates header and footer for the given content. - * @param {String} content - * @param {String} title - * @param {Date} generatedAt - * @param {Date} startTime + * @param {String} content + * @param {String} title + * @param {Date} generatedAt + * @param {Date} startTime * @returns {String} */ var generateHeaderAndFooter = function (content, title, generatedAt, startTime) { @@ -109,8 +109,8 @@ var generateHeaderAndFooter = function (content, title, generatedAt, startTime) /** * Performs multiple exec commands asynchronously and returns the * result in the same order of the commands array. - * @param {Array} commands - * @param {Function} callback + * @param {Array} commands + * @param {Function} callback * @returns {Array} */ var execMultiple = function (commands, callback) { @@ -131,7 +131,7 @@ var execMultiple = function (commands, callback) { if (++totalCommandsCompleted === commands.length) { if (isValidCallback(callback)) callback(resultArray); - } + } }); }); }; @@ -164,9 +164,9 @@ var getWmicPath = function () { /** * Beautifies the whole object and returns in a format which can be logged and read easily. * Can take an object or array of objects as input. - * @param {Object|Array} obj - * @param {String} keysTitle - * @param {String} valuesTitle + * @param {Object|Array} obj + * @param {String} keysTitle + * @param {String} valuesTitle * @param {Number} maxKeyLength Optional * @param {Number} maxValLength Optional * @returns {String} @@ -215,7 +215,7 @@ var beautifyObject = function (obj, keysTitle, valuesTitle, maxKeyLength, maxVal /** * Returns the length of the longest entry in the Array - * @param {Array} arr + * @param {Array} arr * @returns {Number} */ var getLongestVal = function (arr) { @@ -230,7 +230,7 @@ var getLongestVal = function (arr) { /** * Returns string value by trying .toString() & JSON.stringify() - * @param {any} val + * @param {any} val */ var safeToString = function (val) { try { diff --git a/test/commandLine.test.js b/test/commandLine.test.js index 6730d42..445813b 100644 --- a/test/commandLine.test.js +++ b/test/commandLine.test.js @@ -8,6 +8,7 @@ var testHelper = require('./testHelper'); describe('CommandLineManager', function () { var argv; + var proxy_host_actual_value = "http://host"; before(function () { console.log("NOTE: 'console.log' will be stubbed. In case any test fails, try removing the stub to see the logs"); @@ -34,22 +35,32 @@ describe('CommandLineManager', function () { argv = argv.concat(['--proxy-host', 'host', '--proxy-port', '9687']); CommandLineManager.processArgs(argv); console.log.restore(); - expect(RdGlobalConfig.proxy.host).to.eql('host'); + expect(RdGlobalConfig.proxy.host).to.eql(proxy_host_actual_value); expect(RdGlobalConfig.proxy.port).to.eql(9687); }); - it('remove any protocol part from proxy-host', function () { + it('parse proxy-host inputted without protocol', function () { + sinon.stub(console, 'log'); + argv = argv.concat(['--proxy-host', 'host']); + CommandLineManager.processArgs(argv); + console.log.restore(); + expect(RdGlobalConfig.proxy.host).to.eql(proxy_host_actual_value); + }); + + it('parse proxy-host with protocol http', function () { sinon.stub(console, 'log'); argv = argv.concat(['--proxy-host', 'http://host']); CommandLineManager.processArgs(argv); - expect(RdGlobalConfig.proxy.host).to.eql('host'); + console.log.restore(); + expect(RdGlobalConfig.proxy.host).to.eql(proxy_host_actual_value); }); - it('remove any prefix slashes from proxy-host', function () { - argv = argv.concat(['--proxy-host', '//host']); + it('parse proxy-host with protocol https', function () { + sinon.stub(console, 'log'); + argv = argv.concat(['--proxy-host', 'https://host', '--proxy-port', '9687']); CommandLineManager.processArgs(argv); console.log.restore(); - expect(RdGlobalConfig.proxy.host).to.eql('host'); + expect(RdGlobalConfig.proxy.host).to.eql('https://host'); }); it('proxy-port is set to the default value when its not in the expected range', function () { @@ -57,7 +68,7 @@ describe('CommandLineManager', function () { argv = argv.concat(['--proxy-host', 'host', '--proxy-port', '99999']); CommandLineManager.processArgs(argv); console.log.restore(); - expect(RdGlobalConfig.proxy.host).to.eql('host'); + expect(RdGlobalConfig.proxy.host).to.eql(proxy_host_actual_value); expect(RdGlobalConfig.proxy.port).to.eql(constants.DEFAULT_PROXY_PORT); }); @@ -66,7 +77,7 @@ describe('CommandLineManager', function () { argv = argv.concat(['--proxy-host', 'host', '--proxy-port', '9687', '--proxy-user', 'user', '--proxy-pass', 'pass']); CommandLineManager.processArgs(argv); console.log.restore(); - expect(RdGlobalConfig.proxy.host).to.eql('host'); + expect(RdGlobalConfig.proxy.host).to.eql(proxy_host_actual_value); expect(RdGlobalConfig.proxy.port).to.eql(9687); expect(RdGlobalConfig.proxy.username).to.eql('user'); expect(RdGlobalConfig.proxy.password).to.eql('pass'); @@ -77,7 +88,7 @@ describe('CommandLineManager', function () { argv = argv.concat(['--proxy-host', 'host']); CommandLineManager.processArgs(argv); console.log.restore(); - expect(RdGlobalConfig.proxy.host).to.eql('host'); + expect(RdGlobalConfig.proxy.host).to.eql(proxy_host_actual_value); expect(RdGlobalConfig.proxy.port).to.eql(constants.DEFAULT_PROXY_PORT); }); @@ -86,14 +97,14 @@ describe('CommandLineManager', function () { argv = argv.concat(['--proxy-host', 'host', '--proxy-port', '9687', '--proxy-user', 'user']); CommandLineManager.processArgs(argv); console.log.restore(); - expect(RdGlobalConfig.proxy.host).to.eql('host'); + expect(RdGlobalConfig.proxy.host).to.eql(proxy_host_actual_value); expect(RdGlobalConfig.proxy.port).to.eql(9687); expect(RdGlobalConfig.proxy.username).to.eql('user'); expect(RdGlobalConfig.proxy.password).to.eql(''); }); it("proxy won't be set if proxy host is not provided", function () { - sinon.stub(console, 'log'); + sinon.stub(console, 'log'); argv = argv.concat(['--proxy-port', '9687']); CommandLineManager.processArgs(argv); console.log.restore(); @@ -106,7 +117,7 @@ describe('CommandLineManager', function () { argv = argv.concat(['--proxy-host', 'host', '--proxy-port', '9687', '--proxy-pass', 'pass']); CommandLineManager.processArgs(argv); console.log.restore(); - expect(RdGlobalConfig.proxy.host).to.eql('host'); + expect(RdGlobalConfig.proxy.host).to.eql(proxy_host_actual_value); expect(RdGlobalConfig.proxy.port).to.eql(9687); expect(RdGlobalConfig.proxy.username).to.eql(undefined); expect(RdGlobalConfig.proxy.password).to.eql(undefined); @@ -211,11 +222,11 @@ describe('CommandLineManager', function () { // --port it("sets the port of Requests Debugger Tool Proxy using the '--port' argument", function () { - argv = argv.concat(['--port', '9098']); + argv = argv.concat(['--port', '9687']); sinon.stub(console, 'log'); CommandLineManager.processArgs(argv); console.log.restore(); - expect(RdGlobalConfig.RD_HANDLER_PORT).to.eql(9098); + expect(RdGlobalConfig.RD_HANDLER_PROXY_PORT).to.eql(9687); }); it('Uses the default port of Requests Debugger Tool Proxy if not provided via arguments', function () { @@ -253,6 +264,95 @@ describe('CommandLineManager', function () { sinon.assert.called(process.exit); }); + // --reverse-proxy-port + it("sets the port of Requests Debugger Tool Reverse Proxy using the '--reverse-proxy-port' argument", function () { + argv = argv.concat(['--reverse-proxy-port', '9688']); + CommandLineManager.processArgs(argv); + expect(RdGlobalConfig.RD_HANDLER_REVERSE_PROXY_PORT).to.eql(9688); + }); + + it('Uses the default reverse port of Requests Debugger Tool Proxy if not provided via arguments', function () { + var portBeforeParsing = RdGlobalConfig.RD_HANDLER_REVERSE_PROXY_PORT; + CommandLineManager.processArgs(argv); + expect(RdGlobalConfig.RD_HANDLER_REVERSE_PROXY_PORT).to.eql(portBeforeParsing); + }); + + it("exits with invalid args if reverse port provided doesn't lie in the Max Min Range", function () { + argv = argv.concat(['--reverse-proxy-port', '99999']); + sinon.stub(console, 'log'); + CommandLineManager.processArgs(argv); + sinon.assert.calledWith(console.log, '\nInvalid Argument(s): ', '--reverse-proxy-port', '\n'); + console.log.restore(); + sinon.assert.called(process.exit); + }); + + it('exits with invalid args if the reverse port provided is not a number', function () { + argv = argv.concat(['--reverse-proxy-port', 'random string']); + sinon.stub(console, 'log'); + CommandLineManager.processArgs(argv); + sinon.assert.calledWith(console.log, '\nInvalid Argument(s): ', '--reverse-proxy-port', '\n'); + console.log.restore(); + sinon.assert.called(process.exit); + }); + + it('exits with invalid args if the reverse port arg is provided without any value', function () { + argv = argv.concat(['--reverse-proxy-port']); + sinon.stub(console, 'log'); + CommandLineManager.processArgs(argv); + sinon.assert.calledWith(console.log, '\nInvalid Argument(s): ', '--reverse-proxy-port', '\n'); + console.log.restore(); + sinon.assert.called(process.exit); + }); + + it('exits with invalid args if the reverse port arg is provided without any value', function () { + argv = argv.concat(['--reverse-proxy-port']); + sinon.stub(console, 'log'); + CommandLineManager.processArgs(argv); + sinon.assert.calledWith(console.log, '\nInvalid Argument(s): ', '--reverse-proxy-port', '\n'); + console.log.restore(); + sinon.assert.called(process.exit); + }); + + // --scheme + it('Uses the default https scheme for Requests Debugger Tool Proxy if not provided via arguments', function () { + sinon.stub(console, 'log'); + var schemeBeforeParsing = RdGlobalConfig.SCHEME; + CommandLineManager.processArgs(argv); + console.log.restore(); + expect(RdGlobalConfig.SCHEME).to.eql(schemeBeforeParsing); + }); + + it("sets the port of Requests Debugger Tool Reverse scheme to http using the '--scheme' argument", function () { + argv = argv.concat(['--scheme', 'http']); + CommandLineManager.processArgs(argv); + expect(RdGlobalConfig.SCHEME).to.eql('http'); + }); + + it('exits with invalid args if the scheme arg is provided with wrong value', function () { + argv = argv.concat(['--scheme', 'random string']); + sinon.stub(console, 'log'); + CommandLineManager.processArgs(argv); + sinon.assert.calledWith(console.log, '\nInvalid Argument(s): ', '--scheme', '\n'); + console.log.restore(); + sinon.assert.called(process.exit); + }); + + it('exits with invalid args if the scheme arg is provided without any value', function () { + argv = argv.concat(['--scheme']); + sinon.stub(console, 'log'); + CommandLineManager.processArgs(argv); + sinon.assert.calledWith(console.log, '\nInvalid Argument(s): ', '--scheme', '\n'); + console.log.restore(); + sinon.assert.called(process.exit); + }); + + // --reverse-proxy-port + it("sets the port of Requests Debugger Tool Reverse scheme to https using the '--scheme' argument", function () { + argv = argv.concat(['--scheme', 'https']); + CommandLineManager.processArgs(argv); + expect(RdGlobalConfig.SCHEME).to.eql('https'); + }); + // --request-timeout it("sets the timeout for the request being fired from the tool using the arg --request-timeout", function () { argv = argv.concat(['--request-timeout', '200000']); diff --git a/test/connectivity.test.js b/test/connectivity.test.js index 58621af..b64e759 100644 --- a/test/connectivity.test.js +++ b/test/connectivity.test.js @@ -32,13 +32,13 @@ describe('Connectivity Checker for BrowserStack Components', function () { data: '{"data":"value"}', statusCode: 200, errorMessage: null, - description: 'HTTPS Request To ' + constants.HUB_STATUS_URL + ' Without Proxy', + description: 'HTTPS Request To ' + constants.HUB_STATUS_URL_HTTPS + ' Without Proxy', result: 'Passed' }, { data: '{"data":"value"}', statusCode: 302, errorMessage: null, - description: 'HTTPS Request to ' + constants.RAILS_AUTOMATE + ' Without Proxy', + description: 'HTTPS Request to ' + constants.RAILS_AUTOMATE_HTTPS + ' Without Proxy', result: 'Passed' }]; @@ -58,13 +58,13 @@ describe('Connectivity Checker for BrowserStack Components', function () { data: [], statusCode: null, errorMessage: 'Error: something terrible', - description: 'HTTPS Request To ' + constants.HUB_STATUS_URL + ' Without Proxy', + description: 'HTTPS Request To ' + constants.HUB_STATUS_URL_HTTPS+ ' Without Proxy', result: 'Failed' }, { data: [], statusCode: null, errorMessage: 'Error: something terrible', - description: 'HTTPS Request to ' + constants.RAILS_AUTOMATE + ' Without Proxy', + description: 'HTTPS Request to ' + constants.RAILS_AUTOMATE_HTTPS + ' Without Proxy', result: 'Failed' }]; @@ -99,8 +99,11 @@ describe('Connectivity Checker for BrowserStack Components', function () { testHelper.initializeDummyProxy(); ConnectivityChecker.connectionChecks = []; testHelper.nockGetRequest(constants.HUB_STATUS_URL, 'http', null, 200); + testHelper.nockGetRequest(constants.HUB_STATUS_URL, 'http', null, 200); + testHelper.nockGetRequest(constants.HUB_STATUS_URL, 'https', null, 200); testHelper.nockGetRequest(constants.HUB_STATUS_URL, 'https', null, 200); testHelper.nockGetRequest(constants.RAILS_AUTOMATE, 'http', null, 301); + testHelper.nockGetRequest(constants.RAILS_AUTOMATE, 'http', null, 301); testHelper.nockGetRequest(constants.RAILS_AUTOMATE, 'https', null, 302); testHelper.nockProxyUrl(RdGlobalConfig.proxy, 'http', 'hub', null, 200); testHelper.nockProxyUrl(RdGlobalConfig.proxy, 'http', 'automate', null, 301); @@ -113,6 +116,8 @@ describe('Connectivity Checker for BrowserStack Components', function () { it('HTTP(S) to Hub & Rails', function (done) { this.timeout(2000); + testHelper.nockGetRequest(constants.HUB_STATUS_URL, 'https', null, 200); + testHelper.nockGetRequest(constants.RAILS_AUTOMATE, 'https', null, 302); sinon.stub(Utils, 'beautifyObject'); var resultWithProxy = resultWithoutProxy.concat([{ data: '{"data":"value"}', @@ -126,6 +131,18 @@ describe('Connectivity Checker for BrowserStack Components', function () { errorMessage: null, result: "Passed", statusCode: 301 + },{ + data: '{"data":"value"}', + description: "HTTPS Request To " + constants.HUB_STATUS_URL_HTTPS + " With Proxy", + errorMessage: null, + result: "Passed", + statusCode: 200 + }, { + data: '{"data":"value"}', + description: "HTTPS Request To " + constants.RAILS_AUTOMATE_HTTPS + " With Proxy", + errorMessage: null, + result: "Passed", + statusCode: 302 }]); ConnectivityChecker.fireChecks("some topic", 1, function () { diff --git a/test/proxy.test.js b/test/proxy.test.js new file mode 100644 index 0000000..cf5c993 --- /dev/null +++ b/test/proxy.test.js @@ -0,0 +1,266 @@ +var constants = require('../config/constants'); +var RdGlobalConfig = constants.RdGlobalConfig; +var nock = require('nock'); +var RdHandler = require('../src/proxy'); +var http = require('http'); +var assert = require('chai').assert; +var testHelper = require('./testHelper'); + + +describe('RdHandler', function () { + context('Proxy Server with http scheme', function () { + var originalScheme; + before(function (done) { + this.timeout = 5000; + testHelper.initializeDummyLoggers(); + testHelper.initializeDummyHandlers(); + testHelper.initializeDummyProxy(); + originalScheme = RdGlobalConfig.SCHEME; + RdGlobalConfig.SCHEME = 'http'; + + RdHandler.startProxyServer(RdGlobalConfig.RD_HANDLER_PROXY_PORT, function (port) { + console.log('Test Network Utility Proxy Server Started on Port: ', port); + done(); + }); + }); + + after(function (done) { + this.timeout = 5000; + RdHandler.stopProxyServer(function () { + done(); + }); + testHelper.deleteLoggers(); + testHelper.deleteHandlers(); + nock.cleanAll(); + RdGlobalConfig.SCHEME = originalScheme; + }); + + it('Requests on behalf of the client and returns the response', function (done) { + this.timeout = 5000; + testHelper.nockGetRequest(constants.HUB_STATUS_URL, 'http', null, 200); + var reqOptions = { + method: 'GET', + host: 'localhost', + port: RdGlobalConfig.RD_HANDLER_PROXY_PORT, + headers: {}, + path: constants.HUB_STATUS_URL + }; + + var responseData = []; + var request = http.request(reqOptions, function (response) { + + response.on('data', function (chunk) { + responseData.push(chunk); + }); + + response.on('end', function () { + assert(Buffer.concat(responseData).toString() === '{"data":"value"}'); + done(); + }); + }); + + request.end(); + }); + + it('Requests on behalf of the client via external proxy and returns the response', function (done) { + this.timeout = 5000; + testHelper.nockGetRequest(constants.HUB_STATUS_URL, 'http', null, 200); + testHelper.initializeDummyProxy(); + testHelper.nockProxyUrl(RdGlobalConfig.proxy, 'http', 'hub', null, 200); + var reqOptions = { + method: 'GET', + host: 'localhost', + port: RdGlobalConfig.RD_HANDLER_PROXY_PORT, + headers: {}, + path: "http://user1:pass1@" + constants.HUB_HOST + constants.HUB_STATUS_PATH + }; + + var responseData = []; + var request = http.request(reqOptions, function (response) { + + response.on('data', function (chunk) { + responseData.push(chunk); + }); + + response.on('end', function () { + assert(Buffer.concat(responseData).toString() === '{"data":"value"}'); + done(); + testHelper.deleteProxy(); + }); + }); + request.end(); + }); + + it('Requests on behalf of the client via external proxy and returns the response even if request by tool fails', function (done) { + this.timeout = 5000; + for (var i = 0; i <= constants.MAX_RETRIES; i++) { + testHelper.nockGetRequestWithError(constants.HUB_STATUS_URL, 'http'); + } + var reqOptions = { + method: 'GET', + host: 'localhost', + port: RdGlobalConfig.RD_HANDLER_PROXY_PORT, + headers: {}, + path: constants.HUB_STATUS_URL + }; + + var responseData = []; + var request = http.request(reqOptions, function (response) { + + response.on('data', function (chunk) { + responseData.push(chunk); + }); + + response.on('end', function () { + assert(Buffer.concat(responseData).toString() === '{"message":"Error: something terrible. Request Failed At Requests Debugger","error":"Request Failed At Requests Debugger"}'); + done(); + }); + }); + + request.end(); + }); + }); + + context('Proxy Server with https scheme', function () { + var originalScheme; + before(function (done) { + this.timeout = 5000; + testHelper.initializeDummyLoggers(); + testHelper.initializeDummyHandlers(); + testHelper.initializeDummyProxy(); + originalScheme = RdGlobalConfig.SCHEME; + RdGlobalConfig.SCHEME = 'https'; + + RdHandler.startProxyServer(RdGlobalConfig.RD_HANDLER_PROXY_PORT, function (port) { + console.log('Test Network Utility Proxy Server Started on Port: ', port); + done(); + }); + }); + + after(function (done) { + this.timeout = 5000; + RdHandler.stopProxyServer(function () { + done(); + }); + testHelper.deleteLoggers(); + testHelper.deleteHandlers(); + nock.cleanAll(); + RdGlobalConfig.SCHEME.restore(); + RdGlobalConfig.SCHEME = originalScheme; + }); + + it('Requests on behalf of the client and returns the response', function (done) { + this.timeout = 5000; + testHelper.nockGetRequest(constants.HUB_STATUS_URL, 'https', null, 200); + var reqOptions = { + method: 'GET', + host: 'localhost', + port: RdGlobalConfig.RD_HANDLER_PROXY_PORT, + headers: {}, + path: constants.HUB_STATUS_URL + }; + + var responseData = []; + var request = http.request(reqOptions, function (response) { + + response.on('data', function (chunk) { + responseData.push(chunk); + }); + + response.on('end', function () { + assert(Buffer.concat(responseData).toString() === '{"data":"value"}'); + done(); + }); + }); + + request.end(); + }); + + it('Requests on behalf of the client via external proxy and returns the response', function (done) { + this.timeout = 5000; + testHelper.nockGetRequest(constants.HUB_STATUS_URL, 'https', null, 200); + testHelper.initializeDummyProxy(); + testHelper.nockProxyUrl(RdGlobalConfig.proxy, 'https', 'hub', null, 200); + var reqOptions = { + method: 'GET', + host: 'localhost', + port: RdGlobalConfig.RD_HANDLER_PROXY_PORT, + headers: {}, + path: constants.HUB_STATUS_URL + }; + + var responseData = []; + var request = http.request(reqOptions, function (response) { + + response.on('data', function (chunk) { + responseData.push(chunk); + }); + + response.on('end', function () { + assert(Buffer.concat(responseData).toString() === '{"data":"value"}'); + done(); + testHelper.deleteProxy(); + }); + }); + request.end(); + }); + + it('Requests on behalf of the client with username and password in url via external proxy and returns the response', function (done) { + this.timeout = 5000; + testHelper.nockGetRequest(constants.HUB_STATUS_URL, 'https', null, 200); + testHelper.initializeDummyProxy(); + testHelper.nockProxyUrl(RdGlobalConfig.proxy, 'https', 'hub', null, 200); + var reqOptions = { + method: 'GET', + host: 'localhost', + port: RdGlobalConfig.RD_HANDLER_PROXY_PORT, + headers: {}, + path: "http://user1:pass1@" + constants.HUB_HOST + constants.HUB_STATUS_PATH + }; + + var responseData = []; + var request = http.request(reqOptions, function (response) { + + response.on('data', function (chunk) { + responseData.push(chunk); + }); + + response.on('end', function () { + assert(Buffer.concat(responseData).toString() === '{"data":"value"}'); + done(); + testHelper.deleteProxy(); + }); + }); + request.end(); + }); + + it('Requests on behalf of the client via external proxy and returns the response even if request by tool fails', function (done) { + this.timeout = 5000; + for (var i = 0; i <= constants.MAX_RETRIES; i++) { + testHelper.nockGetRequestWithError(constants.HUB_STATUS_URL, 'https'); + } + var reqOptions = { + method: 'GET', + host: 'localhost', + port: RdGlobalConfig.RD_HANDLER_PROXY_PORT, + headers: {}, + path: constants.HUB_STATUS_URL + }; + + var responseData = []; + var request = http.request(reqOptions, function (response) { + + response.on('data', function (chunk) { + responseData.push(chunk); + }); + + response.on('end', function () { + assert(Buffer.concat(responseData).toString() === '{"message":"Error: something terrible. Request Failed At Requests Debugger","error":"Request Failed At Requests Debugger"}'); + done(); + }); + }); + + request.end(); + }); + }); +}); diff --git a/test/reverseProxy.test.js b/test/reverseProxy.test.js new file mode 100644 index 0000000..5a1b62b --- /dev/null +++ b/test/reverseProxy.test.js @@ -0,0 +1,264 @@ +var constants = require('../config/constants'); +var RdGlobalConfig = constants.RdGlobalConfig; +var nock = require('nock'); +var RdHandler = require('../src/reverseProxy'); +var http = require('http'); +var assert = require('chai').assert; +var testHelper = require('./testHelper'); + + +describe('RdHandler', function () { + context('Reverse Proxy Server with http scheme', function () { + var originalScheme; + before(function (done) { + this.timeout = 5000; + testHelper.initializeDummyLoggers(); + testHelper.initializeDummyHandlers(); + originalScheme = RdGlobalConfig.SCHEME; + RdGlobalConfig.SCHEME = 'http'; + + RdHandler.startReverseProxyServer(RdGlobalConfig.RD_HANDLER_REVERSE_PROXY_PORT, function (port) { + console.log('Test Network Utility Reverse Proxy Server Started on Port: ', port); + done(); + }); + }); + + after(function (done) { + this.timeout = 5000; + RdHandler.stopReverseProxyServer(function () { + done(); + }); + testHelper.deleteLoggers(); + testHelper.deleteHandlers(); + nock.cleanAll(); + RdGlobalConfig.SCHEME = originalScheme; + }); + + it('Requests on behalf of the client and returns the response', function (done) { + this.timeout = 5000; + testHelper.nockGetRequest(constants.HUB_STATUS_URL, 'http', null, 200); + var reqOptions = { + method: 'GET', + host: 'localhost', + port: RdGlobalConfig.RD_HANDLER_REVERSE_PROXY_PORT, + headers: {}, + path: constants.HUB_STATUS_PATH + }; + + var responseData = []; + var request = http.request(reqOptions, function (response) { + + response.on('data', function (chunk) { + responseData.push(chunk); + }); + + response.on('end', function () { + assert(Buffer.concat(responseData).toString() === '{"data":"value"}'); + done(); + }); + }); + + request.end(); + }); + + it('Requests on behalf of the client via external proxy and returns the response', function (done) { + this.timeout = 5000; + testHelper.nockGetRequest(constants.HUB_STATUS_URL, 'http', null, 200); + testHelper.initializeDummyProxy(); + testHelper.nockProxyUrl(RdGlobalConfig.proxy, 'http', 'hub', null, 200); + var reqOptions = { + method: 'GET', + host: 'localhost', + port: RdGlobalConfig.RD_HANDLER_REVERSE_PROXY_PORT, + headers: {}, + path: constants.HUB_STATUS_PATH + }; + + var responseData = []; + var request = http.request(reqOptions, function (response) { + + response.on('data', function (chunk) { + responseData.push(chunk); + }); + + response.on('end', function () { + assert(Buffer.concat(responseData).toString() === '{"data":"value"}'); + done(); + testHelper.deleteProxy(); + }); + }); + request.end(); + }); + + it('Requests on behalf of the client via external proxy and returns the response even if request by tool fails', function (done) { + this.timeout = 5000; + for (var i = 0; i <= constants.MAX_RETRIES; i++) { + testHelper.nockGetRequestWithError(constants.HUB_STATUS_URL, 'http'); + } + var reqOptions = { + method: 'GET', + host: 'localhost', + port: RdGlobalConfig.RD_HANDLER_REVERSE_PROXY_PORT, + headers: {}, + path: constants.HUB_STATUS_PATH + }; + + var responseData = []; + var request = http.request(reqOptions, function (response) { + + response.on('data', function (chunk) { + responseData.push(chunk); + }); + + response.on('end', function () { + assert(Buffer.concat(responseData).toString() === '{"message":"Error: something terrible. Request Failed At Requests Debugger","error":"Request Failed At Requests Debugger"}'); + done(); + }); + }); + + request.end(); + }); + }); + + context('Reverse Proxy Server with https scheme', function () { + var originalScheme; + before(function (done) { + this.timeout = 5000; + testHelper.initializeDummyLoggers(); + testHelper.initializeDummyHandlers(); + originalScheme = RdGlobalConfig.SCHEME; + RdGlobalConfig.SCHEME = 'https'; + + RdHandler.startReverseProxyServer(RdGlobalConfig.RD_HANDLER_REVERSE_PROXY_PORT, function (port) { + console.log('Test Network Utility Reverse Proxy Server Started on Port: ', port); + done(); + }); + }); + + after(function (done) { + this.timeout = 5000; + RdHandler.stopReverseProxyServer(function () { + done(); + }); + testHelper.deleteLoggers(); + testHelper.deleteHandlers(); + nock.cleanAll(); + RdGlobalConfig.SCHEME.restore(); + RdGlobalConfig.SCHEME = originalScheme; + }); + + it('Requests on behalf of the client and returns the response', function (done) { + this.timeout = 5000; + testHelper.nockGetRequest(constants.HUB_STATUS_URL, 'https', null, 200); + var reqOptions = { + method: 'GET', + host: 'localhost', + port: RdGlobalConfig.RD_HANDLER_REVERSE_PROXY_PORT, + headers: {}, + path: constants.HUB_STATUS_PATH + }; + + var responseData = []; + var request = http.request(reqOptions, function (response) { + + response.on('data', function (chunk) { + responseData.push(chunk); + }); + + response.on('end', function () { + assert(Buffer.concat(responseData).toString() === '{"data":"value"}'); + done(); + }); + }); + + request.end(); + }); + + it('Requests on behalf of the client via external proxy and returns the response', function (done) { + this.timeout = 5000; + testHelper.nockGetRequest(constants.HUB_STATUS_URL, 'https', null, 200); + testHelper.initializeDummyProxy(); + testHelper.nockProxyUrl(RdGlobalConfig.proxy, 'https', 'hub', null, 200); + var reqOptions = { + method: 'GET', + host: 'localhost', + port: RdGlobalConfig.RD_HANDLER_REVERSE_PROXY_PORT, + headers: {}, + path: constants.HUB_STATUS_PATH + }; + + var responseData = []; + var request = http.request(reqOptions, function (response) { + + response.on('data', function (chunk) { + responseData.push(chunk); + }); + + response.on('end', function () { + assert(Buffer.concat(responseData).toString() === '{"data":"value"}'); + done(); + testHelper.deleteProxy(); + }); + }); + request.end(); + }); + + it('Requests on behalf of the client with username and password in url via external proxy and returns the response', function (done) { + this.timeout = 5000; + testHelper.nockGetRequest(constants.HUB_STATUS_URL, 'https', null, 200); + testHelper.initializeDummyProxy(); + testHelper.nockProxyUrl(RdGlobalConfig.proxy, 'https', 'hub', null, 200); + var reqOptions = { + method: 'GET', + host: 'localhost', + port: RdGlobalConfig.RD_HANDLER_REVERSE_PROXY_PORT, + headers: {}, + path: "http://user1:pass1@" + constants.HUB_HOST + constants.HUB_STATUS_PATH + }; + + var responseData = []; + var request = http.request(reqOptions, function (response) { + + response.on('data', function (chunk) { + responseData.push(chunk); + }); + + response.on('end', function () { + assert(Buffer.concat(responseData).toString() === '{"data":"value"}'); + done(); + testHelper.deleteProxy(); + }); + }); + request.end(); + }); + + it('Requests on behalf of the client via external proxy and returns the response even if request by tool fails', function (done) { + this.timeout = 5000; + for (var i = 0; i <= constants.MAX_RETRIES; i++) { + testHelper.nockGetRequestWithError(constants.HUB_STATUS_URL, 'https'); + } + var reqOptions = { + method: 'GET', + host: 'localhost', + port: RdGlobalConfig.RD_HANDLER_REVERSE_PROXY_PORT, + headers: {}, + path: constants.HUB_STATUS_PATH + }; + + var responseData = []; + var request = http.request(reqOptions, function (response) { + + response.on('data', function (chunk) { + responseData.push(chunk); + }); + + response.on('end', function () { + assert(Buffer.concat(responseData).toString() === '{"message":"Error: something terrible. Request Failed At Requests Debugger","error":"Request Failed At Requests Debugger"}'); + done(); + }); + }); + + request.end(); + }); + }); +}); diff --git a/test/server.test.js b/test/server.test.js deleted file mode 100644 index 5307bd7..0000000 --- a/test/server.test.js +++ /dev/null @@ -1,120 +0,0 @@ -var constants = require('../config/constants'); -var RdGlobalConfig = constants.RdGlobalConfig; -var nock = require('nock'); -var RdHandler = require('../src/server'); -var http = require('http'); -var assert = require('chai').assert; -var testHelper = require('./testHelper'); - -describe('RdHandler', function () { - context('Proxy Server', function () { - - before(function (done) { - this.timeout = 5000; - testHelper.nockGetRequest(constants.HUB_STATUS_URL, 'http', null, 200); - testHelper.initializeDummyLoggers(); - testHelper.initializeDummyHandlers(); - - RdHandler.startProxy(RdGlobalConfig.RD_HANDLER_PORT, function (port) { - console.log('Test Network Utility Proxy Started on Port: ', port); - done(); - }); - }); - - after(function (done) { - this.timeout = 5000; - RdHandler.stopProxy(function () { - done(); - }); - testHelper.deleteLoggers(); - testHelper.deleteHandlers(); - nock.cleanAll(); - }); - - it('Requests on behalf of the client and returns the response', function (done) { - this.timeout = 5000; - var reqOptions = { - method: 'GET', - host: 'localhost', - port: RdGlobalConfig.RD_HANDLER_PORT, - headers: {}, - path: constants.HUB_STATUS_URL - }; - - var responseData = []; - var request = http.request(reqOptions, function (response) { - - response.on('data', function (chunk) { - responseData.push(chunk); - }); - - response.on('end', function () { - assert(Buffer.concat(responseData).toString() === '{"data":"value"}'); - done(); - }); - }); - - request.end(); - }); - - it('Requests on behalf of the client via external proxy and returns the response', function (done) { - this.timeout = 5000; - testHelper.initializeDummyProxy(); - testHelper.nockProxyUrl(RdGlobalConfig.proxy, 'http', 'hub', null, 200); - RdHandler.generatorForRequestOptionsObject(); - var reqOptions = { - method: 'GET', - host: 'localhost', - port: RdGlobalConfig.RD_HANDLER_PORT, - headers: {}, - path: constants.HUB_STATUS_URL - }; - - var responseData = []; - var request = http.request(reqOptions, function (response) { - - response.on('data', function (chunk) { - responseData.push(chunk); - }); - - response.on('end', function () { - assert(Buffer.concat(responseData).toString() === '{"data":"value"}'); - done(); - }); - }); - - request.end(); - testHelper.deleteProxy(); - }); - - it('Requests on behalf of the client via external proxy and returns the response even if request by tool fails', function (done) { - this.timeout = 5000; - for (var i = 0; i <= constants.MAX_RETRIES; i++) { - testHelper.nockGetRequestWithError(constants.HUB_STATUS_URL, 'http'); - } - RdHandler.generatorForRequestOptionsObject(); - var reqOptions = { - method: 'GET', - host: 'localhost', - port: RdGlobalConfig.RD_HANDLER_PORT, - headers: {}, - path: constants.HUB_STATUS_URL - }; - - var responseData = []; - var request = http.request(reqOptions, function (response) { - - response.on('data', function (chunk) { - responseData.push(chunk); - }); - - response.on('end', function () { - assert(Buffer.concat(responseData).toString() === '{"message":"Error: something terrible. Request Failed At Requests Debugger","error":"Request Failed At Requests Debugger"}'); - done(); - }); - }); - - request.end(); - }); - }); -}); diff --git a/test/stats/macStats.test.js b/test/stats/macStats.test.js index d2e3266..b9bf4b4 100644 --- a/test/stats/macStats.test.js +++ b/test/stats/macStats.test.js @@ -11,21 +11,21 @@ describe('MacStats', function () { it('callbacks with the result of cpu stats', function () { var stats = "CPU Stats Generated"; var statsWithHeaderFooter = "Header" + os.EOL + stats + os.EOL + "Footer" + os.EOL; - + sinon.stub(cp, 'exec').callsArgWith(1, null, stats); sinon.stub(Utils, 'generateHeaderAndFooter').returns(statsWithHeaderFooter); - + MacStats.cpu(function (result) { expect(result).to.eql(statsWithHeaderFooter); }); - + cp.exec.restore(); Utils.generateHeaderAndFooter.restore(); }); it('callbacks with proper message when no stats are available', function () { sinon.stub(cp, 'exec').callsArgWith(1, "err", null); - + MacStats.cpu(function (result) { expect(result).to.eql(constants.STATIC_MESSAGES.NO_REPORT_GENERATED + 'CPU' + os.EOL); }); @@ -99,10 +99,10 @@ describe('MacStats', function () { content: 'resultThree', generatedAt: new Date().toISOString() }]; - + sinon.stub(Utils, 'execMultiple').callsArgWith(1, results); sinon.stub(Utils, 'generateHeaderAndFooter').returns('headerFooterContent'); - + MacStats.network(function (result) { sinon.assert.calledThrice(Utils.generateHeaderAndFooter); expect(result).to.eql('headerFooterContent'.repeat(3)); diff --git a/test/testHelper.js b/test/testHelper.js index caa91d6..8504345 100644 --- a/test/testHelper.js +++ b/test/testHelper.js @@ -52,7 +52,7 @@ function nockGetRequestWithError(reqUrl, type) { function initializeDummyProxy() { constants.RdGlobalConfig.proxy = { host: "dummyhost12345.com", - port: "3128", + port: "3130", username: "user", password: "pass" };