diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c710f99b..4bfd0a87 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 14 + node-version: 16 - run: npm ci - run: npm run jslint - run: docker pull drachtio/sipp diff --git a/app.js b/app.js index 2116756f..fc8ad7dd 100644 --- a/app.js +++ b/app.js @@ -15,7 +15,6 @@ const tracer = require('./tracer')(process.env.JAMBONES_OTEL_SERVICE_NAME || 'ja const api = require('@opentelemetry/api'); srf.locals = {...srf.locals, otel: {tracer, api}}; -const PORT = process.env.HTTP_PORT || 3000; const opts = {level: process.env.JAMBONES_LOGLEVEL || 'info'}; const pino = require('pino'); const logger = pino(opts, pino.destination({sync: false})); @@ -33,17 +32,6 @@ const { invokeWebCallback } = require('./lib/middleware')(srf, logger); -// HTTP -const express = require('express'); -const helmet = require('helmet'); -const app = express(); -Object.assign(app.locals, { - logger, - srf -}); - -const httpRoutes = require('./lib/http-routes'); - const InboundCallSession = require('./lib/session/inbound-call-session'); const SipRecCallSession = require('./lib/session/siprec-call-session'); @@ -82,20 +70,6 @@ srf.invite(async(req, res) => { session.exec(); }); -// HTTP -app.use(helmet()); -app.use(helmet.hidePoweredBy()); -app.use(express.urlencoded({ extended: true })); -app.use(express.json()); -app.use('/', httpRoutes); -app.use((err, req, res, next) => { - logger.error(err, 'burped error'); - res.status(err.status || 500).json({msg: err.message}); -}); -const httpServer = app.listen(PORT); - -logger.info(`listening for HTTP requests on port ${PORT}, serviceUrl is ${srf.locals.serviceUrl}`); - const sessionTracker = srf.locals.sessionTracker = require('./lib/session/session-tracker'); sessionTracker.on('idle', () => { if (srf.locals.lifecycleEmitter.operationalState === LifeCycleEvents.ScaleIn) { @@ -103,19 +77,30 @@ sessionTracker.on('idle', () => { srf.locals.lifecycleEmitter.scaleIn(); } }); - const getCount = () => sessionTracker.count; const healthCheck = require('@jambonz/http-health-check'); -healthCheck({app, logger, path: '/', fn: getCount}); +let httpServer; + +const createHttpListener = require('./lib/utils/http-listener'); +createHttpListener(logger, srf) + .then(({server, app}) => { + httpServer = server; + healthCheck({app, logger, path: '/', fn: getCount}); + return {server, app}; + }) + .catch((err) => { + logger.error(err, 'Error creating http listener'); + }); + setInterval(() => { srf.locals.stats.gauge('fs.sip.calls.count', sessionTracker.count); -}, 5000); +}, 20000); const disconnect = () => { return new Promise ((resolve) => { - httpServer.on('close', resolve); - httpServer.close(); + httpServer?.on('close', resolve); + httpServer?.close(); srf.disconnect(); srf.locals.mediaservers.forEach((ms) => ms.disconnect()); }); diff --git a/lib/utils/aws-sns-lifecycle.js b/lib/utils/aws-sns-lifecycle.js index 184d6efb..f4cb1adf 100644 --- a/lib/utils/aws-sns-lifecycle.js +++ b/lib/utils/aws-sns-lifecycle.js @@ -1,7 +1,7 @@ const Emitter = require('events'); const bent = require('bent'); const assert = require('assert'); -const PORT = process.env.AWS_SNS_PORT || 3001; +const PORT = process.env.AWS_SNS_PORT || 3010; const {LifeCycleEvents} = require('./constants'); const express = require('express'); const app = express(); @@ -21,6 +21,26 @@ class SnsNotifier extends Emitter { this.logger = logger; } + _doListen(logger, app, port, resolve) { + return app.listen(port, () => { + this.snsEndpoint = `http://${this.publicIp}:${port}`; + logger.info(`SNS lifecycle server listening on http://localhost:${port}`); + resolve(app); + }); + } + + _handleErrors(logger, app, resolve, reject, e) { + if (e.code === 'EADDRINUSE' && + process.env.AWS_SNS_PORT_MAX && + e.port < process.env.AWS_SNS_PORT_MAX) { + + logger.info(`SNS lifecycle server failed to bind port on ${e.port}, will try next port`); + const server = this._doListen(logger, app, ++e.port, resolve); + server.on('error', this._handleErrors.bind(null, logger, app, resolve, reject)); + return; + } + reject(e); + } async _handlePost(req, res) { try { @@ -84,11 +104,9 @@ class SnsNotifier extends Emitter { this.logger.debug('SnsNotifier: retrieving instance data'); this.instanceId = await getString('http://169.254.169.254/latest/meta-data/instance-id'); this.publicIp = await getString('http://169.254.169.254/latest/meta-data/public-ipv4'); - this.snsEndpoint = `http://${this.publicIp}:${PORT}`; this.logger.info({ instanceId: this.instanceId, - publicIp: this.publicIp, - snsEndpoint: this.snsEndpoint + publicIp: this.publicIp }, 'retrieved AWS instance data'); // start listening @@ -100,7 +118,10 @@ class SnsNotifier extends Emitter { this.logger.error(err, 'burped error'); res.status(err.status || 500).json({msg: err.message}); }); - app.listen(PORT); + return new Promise((resolve, reject) => { + const server = this._doListen(this.logger, app, PORT, resolve); + server.on('error', this._handleErrors.bind(null, this.logger, app, resolve, reject)); + }); } catch (err) { this.logger.error({err}, 'Error retrieving AWS instance metadata'); diff --git a/lib/utils/db-utils.js b/lib/utils/db-utils.js index c06b9092..40ab32d9 100644 --- a/lib/utils/db-utils.js +++ b/lib/utils/db-utils.js @@ -23,25 +23,28 @@ AND vc.name = ?`; const speechMapper = (cred) => { const {credential, ...obj} = cred; - if ('google' === obj.vendor) { - obj.service_key = decrypt(credential); - } - else if ('aws' === obj.vendor) { - const o = JSON.parse(decrypt(credential)); - obj.access_key_id = o.access_key_id; - obj.secret_access_key = o.secret_access_key; - obj.aws_region = o.aws_region; - } - else if ('microsoft' === obj.vendor) { - const o = JSON.parse(decrypt(credential)); - obj.api_key = o.api_key; - obj.region = o.region; - obj.use_custom_stt = o.use_custom_stt; - obj.custom_stt_endpoint = o.custom_stt_endpoint; - } - else if ('wellsaid' === obj.vendor) { - const o = JSON.parse(decrypt(credential)); - obj.api_key = o.api_key; + try { + if ('google' === obj.vendor) { + obj.service_key = decrypt(credential); + } + else if ('aws' === obj.vendor) { + const o = JSON.parse(decrypt(credential)); + obj.access_key_id = o.access_key_id; + obj.secret_access_key = o.secret_access_key; + obj.aws_region = o.aws_region; + } + else if ('microsoft' === obj.vendor) { + const o = JSON.parse(decrypt(credential)); + obj.api_key = o.api_key; + obj.region = o.region; + obj.use_custom_stt = o.use_custom_stt; + obj.custom_stt_endpoint = o.custom_stt_endpoint; + } + else if ('wellsaid' === obj.vendor) { + const o = JSON.parse(decrypt(credential)); + obj.api_key = o.api_key; + } + } catch (err) { } return obj; }; diff --git a/lib/utils/http-listener.js b/lib/utils/http-listener.js new file mode 100644 index 00000000..f9fd9e61 --- /dev/null +++ b/lib/utils/http-listener.js @@ -0,0 +1,44 @@ + +const express = require('express'); +const httpRoutes = require('../http-routes'); +const PORT = process.env.HTTP_PORT || 3000; + +const doListen = (logger, app, port, resolve) => { + const server = app.listen(port, () => { + const {srf} = app.locals; + logger.info(`listening for HTTP requests on port ${PORT}, serviceUrl is ${srf.locals.serviceUrl}`); + resolve({server, app}); + }); + return server; +}; +const handleErrors = (logger, app, resolve, reject, e) => { + if (e.code === 'EADDRINUSE' && + process.env.HTTP_PORT_MAX && + e.port < process.env.HTTP_PORT_MAX) { + + logger.info(`HTTP server failed to bind port on ${e.port}, will try next port`); + const server = doListen(logger, app, ++e.port, resolve); + server.on('error', handleErrors.bind(null, logger, app, resolve, reject)); + return; + } + reject(e); +}; + +const createHttpListener = (logger, srf) => { + const app = express(); + app.locals = {...app.locals, logger, srf}; + app.use(express.urlencoded({ extended: true })); + app.use(express.json()); + app.use('/', httpRoutes); + app.use((err, req, res, next) => { + logger.error(err, 'burped error'); + res.status(err.status || 500).json({msg: err.message}); + }); + return new Promise((resolve, reject) => { + const server = doListen(logger, app, PORT, resolve); + server.on('error', handleErrors.bind(null, logger, app, resolve, reject)); + }); +}; + + +module.exports = createHttpListener;