diff --git a/lib/utils/http-requestor.js b/lib/utils/http-requestor.js index a3c4aa61..04e2cb8c 100644 --- a/lib/utils/http-requestor.js +++ b/lib/utils/http-requestor.js @@ -28,7 +28,7 @@ class HttpRequestor extends BaseRequestor { assert(['GET', 'POST'].includes(this.method)); const u = this._parsedUrl = parseUrl(this.url); - this._baseUrl = `${u.protocol}://${u.resource}`; + this._baseUrl = `${u.protocol}://${u.resource}:${u.port || 80}`; this._resource = u.resource; this._protocol = u.protocol; this._search = u.search; @@ -49,7 +49,7 @@ class HttpRequestor extends BaseRequestor { this.logger.debug(`HttpRequestor:created pool for ${this._baseUrl}`); } } - else this.client = new Client(`${u.protocol}://${u.resource}`); + else this.client = new Client(`${u.protocol}://${u.resource}:${u.port || 80}`); } get baseUrl() { @@ -101,7 +101,7 @@ class HttpRequestor extends BaseRequestor { query = u.query; } else { - client = newClient = new Client(`${u.protocol}://${u.resource}`); + client = newClient = new Client(`${u.protocol}://${u.resource}:${u.port || 80}`); path = u.pathname; query = u.query; } diff --git a/lib/utils/requestor.js b/lib/utils/requestor.js index 72765412..2d21f46b 100644 --- a/lib/utils/requestor.js +++ b/lib/utils/requestor.js @@ -1,42 +1,7 @@ -const bent = require('bent'); -const parseUrl = require('parse-url'); const assert = require('assert'); -const snakeCaseKeys = require('./snakecase-keys'); -const crypto = require('crypto'); const timeSeries = require('@jambonz/time-series'); let alerter ; -const toBase64 = (str) => Buffer.from(str || '', 'utf8').toString('base64'); - -function computeSignature(payload, timestamp, secret) { - assert(secret); - const data = `${timestamp}.${JSON.stringify(payload)}`; - return crypto - .createHmac('sha256', secret) - .update(data, 'utf8') - .digest('hex'); -} - -function generateSigHeader(payload, secret) { - const timestamp = Math.floor(Date.now() / 1000); - const signature = computeSignature(payload, timestamp, secret); - const scheme = 'v1'; - return { - 'Jambonz-Signature': `t=${timestamp},${scheme}=${signature}` - }; -} - -function basicAuth(username, password) { - if (!username || !password) return {}; - const creds = `${username}:${password || ''}`; - const header = `Basic ${toBase64(creds)}`; - return {Authorization: header}; -} - -function isRelativeUrl(u) { - return typeof u === 'string' && u.startsWith('/'); -} - function isAbsoluteUrl(u) { return typeof u === 'string' && u.startsWith('https://') || u.startsWith('http://'); @@ -49,14 +14,6 @@ class Requestor { this.logger = logger; this.url = hook.url; this.method = hook.method || 'POST'; - this.authHeader = basicAuth(hook.username, hook.password); - - const u = parseUrl(this.url); - const myPort = u.port ? `:${u.port}` : ''; - const baseUrl = this._baseUrl = `${u.protocol}://${u.resource}${myPort}`; - - this.get = bent(baseUrl, 'GET', 'buffer', 200, 201); - this.post = bent(baseUrl, 'POST', 'buffer', 200, 201); this.username = hook.username; this.password = hook.password; @@ -78,72 +35,15 @@ class Requestor { } } - get baseUrl() { - return this._baseUrl; - } - - /** - * Make an HTTP request. - * All requests use json bodies. - * All requests expect a 200 statusCode on success - * @param {object|string} hook - may be a absolute or relative url, or an object - * @param {string} [hook.url] - an absolute or relative url - * @param {string} [hook.method] - 'GET' or 'POST' - * @param {string} [hook.username] - if basic auth is protecting the endpoint - * @param {string} [hook.password] - if basic auth is protecting the endpoint - * @param {object} [params] - request parameters - */ - async request(hook, params) { - const payload = params ? snakeCaseKeys(params, ['customerData', 'sip']) : null; - const url = hook.url || hook; - const method = hook.method || 'POST'; - - assert.ok(url, 'Requestor:request url was not provided'); - assert.ok, (['GET', 'POST'].includes(method), `Requestor:request method must be 'GET' or 'POST' not ${method}`); - const {url: urlInfo = hook, method: methodInfo = 'POST'} = hook; // mask user/pass - this.logger.debug({url: urlInfo, method: methodInfo, payload}, `Requestor:request ${method} ${url}`); - const startAt = process.hrtime(); - - let buf; - try { - const sigHeader = generateSigHeader(payload, this.secret); - const headers = {...sigHeader, ...this.authHeader}; - //this.logger.info({url, headers}, 'send webhook'); - buf = isRelativeUrl(url) ? - await this.post(url, payload, headers) : - await bent(method, 'buffer', 200, 201, 202)(url, payload, headers); - } catch (err) { - this.logger.error({err, secret: this.secret, baseUrl: this.baseUrl, url, statusCode: err.statusCode}, - `web callback returned unexpected error code ${err.statusCode}`); - let opts = {account_sid: this.account_sid}; - if (err.code === 'ECONNREFUSED') { - opts = {...opts, alert_type: alerter.AlertType.WEBHOOK_CONNECTION_FAILURE, url}; - } - else if (err.name === 'StatusError') { - opts = {...opts, alert_type: alerter.AlertType.WEBHOOK_STATUS_FAILURE, url, status: err.statusCode}; - } - else { - opts = {...opts, alert_type: alerter.AlertType.WEBHOOK_CONNECTION_FAILURE, url, detail: err.message}; - } - alerter.writeAlerts(opts).catch((err) => this.logger.info({err, opts}, 'Error writing alert')); - - throw err; - } - const diff = process.hrtime(startAt); - const time = diff[0] * 1e3 + diff[1] * 1e-6; - const rtt = time.toFixed(0); - if (buf) this.stats.histogram('app.hook.response_time', rtt, ['hook_type:app']); - - if (buf && buf.toString().length > 0) { - try { - const json = JSON.parse(buf.toString()); - this.logger.info({response: json}, `Requestor:request ${method} ${url} succeeded in ${rtt}ms`); - return json; - } - catch (err) { - //this.logger.debug({err, url, method}, `Requestor:request returned non-JSON content: '${buf.toString()}'`); - } + get Alerter() { + if (!alerter) { + alerter = timeSeries(this.logger, { + host: process.env.JAMBONES_TIME_SERIES_HOST, + commitSize: 50, + commitInterval: 'test' === process.env.NODE_ENV ? 7 : 20 + }); } + return alerter; } } diff --git a/package-lock.json b/package-lock.json index 15247a8d..81e54f4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@jambonz/http-health-check": "^0.0.1", "@jambonz/realtimedb-helpers": "^0.4.30", "@jambonz/stats-collector": "^0.1.6", - "@jambonz/time-series": "^0.1.12", + "@jambonz/time-series": "^0.2.1", "@opentelemetry/api": "^1.1.0", "@opentelemetry/exporter-jaeger": "^1.3.1", "@opentelemetry/exporter-trace-otlp-http": "^0.27.0", @@ -33,14 +33,14 @@ "helmet": "^5.1.0", "ip": "^1.1.8", "moment": "^2.29.4", - "parse-url": "^7.0.2", + "parse-url": "^8.1.0", "pino": "^6.14.0", "sdp-transform": "^2.14.1", "short-uuid": "^4.2.0", "to-snake-case": "^1.0.0", "undici": "^5.8.2", "uuid": "^8.3.2", - "verify-aws-sns-signature": "^0.0.7", + "verify-aws-sns-signature": "^0.1.0", "ws": "^8.8.0", "xml2js": "^0.4.23" }, @@ -566,9 +566,9 @@ } }, "node_modules/@jambonz/time-series": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/@jambonz/time-series/-/time-series-0.1.12.tgz", - "integrity": "sha512-TmCG4jcI8oK3NXOc4/PbdRhhMVLEr5FOyG4IIWpNlwB0vbjAGLY3K+O5PF4fXK+UcNYnIrUcrd2C0J9z3+YBxw==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@jambonz/time-series/-/time-series-0.2.1.tgz", + "integrity": "sha512-uAoeZ3ibS7kEOGdT+vaY8BB8hOV4q38eEaF+d5OvLQaHCrPonNiwB8tWhhXDwtYdDompfqVRUy/plNA9fyS7Vw==", "dependencies": { "debug": "^4.3.1", "influx": "^5.9.3" @@ -3614,14 +3614,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-ssh": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.0.tgz", - "integrity": "sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==", - "dependencies": { - "protocols": "^2.0.1" - } - }, "node_modules/is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", @@ -4436,17 +4428,6 @@ "node": ">=8" } }, - "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/nyc": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", @@ -4672,22 +4653,19 @@ } }, "node_modules/parse-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-5.0.0.tgz", - "integrity": "sha512-qOpH55/+ZJ4jUu/oLO+ifUKjFPNZGfnPJtzvGzKN/4oLMil5m9OH4VpOj6++9/ytJcfks4kzH2hhi87GL/OU9A==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz", + "integrity": "sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==", "dependencies": { "protocols": "^2.0.0" } }, "node_modules/parse-url": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-7.0.2.tgz", - "integrity": "sha512-PqO4Z0eCiQ08Wj6QQmrmp5YTTxpYfONdOEamrtvK63AmzXpcavIVQubGHxOEwiIoDZFb8uDOoQFS0NCcjqIYQg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-8.1.0.tgz", + "integrity": "sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==", "dependencies": { - "is-ssh": "^1.4.0", - "normalize-url": "^6.1.0", - "parse-path": "^5.0.0", - "protocols": "^2.0.1" + "parse-path": "^7.0.0" } }, "node_modules/parseurl": { @@ -5993,12 +5971,12 @@ } }, "node_modules/verify-aws-sns-signature": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/verify-aws-sns-signature/-/verify-aws-sns-signature-0.0.7.tgz", - "integrity": "sha512-j/yePIQvLqRGshOwuEs9VT7jGh++1hBoOjjt+Rl4aAffJTu+22GwTPfAD9fLY9VqRrR4Cuiid3eNHOkmxE3TNg==", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/verify-aws-sns-signature/-/verify-aws-sns-signature-0.1.0.tgz", + "integrity": "sha512-giPj4dIrhounlQA+AAy0CZmqARrT2o4WjIcv1GKcnQiKBDmDpJyGIaHu/ESwOGVcZf68aLHFPrEzhudXSp4Krw==", "dependencies": { "bent": "^7.3.12", - "parse-url": "^7.0.2" + "parse-url": "^8.1.0" } }, "node_modules/verror": { @@ -6714,9 +6692,9 @@ } }, "@jambonz/time-series": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/@jambonz/time-series/-/time-series-0.1.12.tgz", - "integrity": "sha512-TmCG4jcI8oK3NXOc4/PbdRhhMVLEr5FOyG4IIWpNlwB0vbjAGLY3K+O5PF4fXK+UcNYnIrUcrd2C0J9z3+YBxw==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@jambonz/time-series/-/time-series-0.2.1.tgz", + "integrity": "sha512-uAoeZ3ibS7kEOGdT+vaY8BB8hOV4q38eEaF+d5OvLQaHCrPonNiwB8tWhhXDwtYdDompfqVRUy/plNA9fyS7Vw==", "requires": { "debug": "^4.3.1", "influx": "^5.9.3" @@ -9030,14 +9008,6 @@ "call-bind": "^1.0.2" } }, - "is-ssh": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.0.tgz", - "integrity": "sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==", - "requires": { - "protocols": "^2.0.1" - } - }, "is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", @@ -9665,11 +9635,6 @@ "process-on-spawn": "^1.0.0" } }, - "normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" - }, "nyc": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", @@ -9838,22 +9803,19 @@ } }, "parse-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-5.0.0.tgz", - "integrity": "sha512-qOpH55/+ZJ4jUu/oLO+ifUKjFPNZGfnPJtzvGzKN/4oLMil5m9OH4VpOj6++9/ytJcfks4kzH2hhi87GL/OU9A==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz", + "integrity": "sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==", "requires": { "protocols": "^2.0.0" } }, "parse-url": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-7.0.2.tgz", - "integrity": "sha512-PqO4Z0eCiQ08Wj6QQmrmp5YTTxpYfONdOEamrtvK63AmzXpcavIVQubGHxOEwiIoDZFb8uDOoQFS0NCcjqIYQg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-8.1.0.tgz", + "integrity": "sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==", "requires": { - "is-ssh": "^1.4.0", - "normalize-url": "^6.1.0", - "parse-path": "^5.0.0", - "protocols": "^2.0.1" + "parse-path": "^7.0.0" } }, "parseurl": { @@ -10869,12 +10831,12 @@ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" }, "verify-aws-sns-signature": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/verify-aws-sns-signature/-/verify-aws-sns-signature-0.0.7.tgz", - "integrity": "sha512-j/yePIQvLqRGshOwuEs9VT7jGh++1hBoOjjt+Rl4aAffJTu+22GwTPfAD9fLY9VqRrR4Cuiid3eNHOkmxE3TNg==", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/verify-aws-sns-signature/-/verify-aws-sns-signature-0.1.0.tgz", + "integrity": "sha512-giPj4dIrhounlQA+AAy0CZmqARrT2o4WjIcv1GKcnQiKBDmDpJyGIaHu/ESwOGVcZf68aLHFPrEzhudXSp4Krw==", "requires": { "bent": "^7.3.12", - "parse-url": "^7.0.2" + "parse-url": "^8.1.0" } }, "verror": { diff --git a/package.json b/package.json index e5b225d7..0e73c178 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "url": "https://github.com/jambonz/jambonz-feature-server.git" }, "bugs": { - "url": "https://github.com/jambonz/jambonz-feature-server/issues" }, "scripts": { "start": "node app", @@ -30,7 +29,7 @@ "@jambonz/http-health-check": "^0.0.1", "@jambonz/realtimedb-helpers": "^0.4.30", "@jambonz/stats-collector": "^0.1.6", - "@jambonz/time-series": "^0.1.12", + "@jambonz/time-series": "^0.2.1", "@opentelemetry/api": "^1.1.0", "@opentelemetry/exporter-jaeger": "^1.3.1", "@opentelemetry/exporter-trace-otlp-http": "^0.27.0", @@ -50,14 +49,14 @@ "helmet": "^5.1.0", "ip": "^1.1.8", "moment": "^2.29.4", - "parse-url": "^7.0.2", + "parse-url": "^8.1.0", "pino": "^6.14.0", "sdp-transform": "^2.14.1", "short-uuid": "^4.2.0", "to-snake-case": "^1.0.0", "undici": "^5.8.2", "uuid": "^8.3.2", - "verify-aws-sns-signature": "^0.0.7", + "verify-aws-sns-signature": "^0.1.0", "ws": "^8.8.0", "xml2js": "^0.4.23" },