diff --git a/lib/routes/api/recent-calls.js b/lib/routes/api/recent-calls.js index c9673aa..0b9367d 100644 --- a/lib/routes/api/recent-calls.js +++ b/lib/routes/api/recent-calls.js @@ -1,4 +1,5 @@ const router = require('express').Router(); +const {pipeline} = require('stream'); const sysError = require('../error'); const {DbErrorBadRequest} = require('../../utils/errors'); const {getHomerApiKey, getHomerSipTrace, getHomerPcap} = require('../../utils/homer-utils'); @@ -16,6 +17,8 @@ const { const { parseServiceProviderSid, parseAccountSid } = require('./utils'); +const HOMER_PCAP_TIMEOUT_MS = 30000; + router.get('/', async(req, res) => { const {logger, queryCdrs, queryCdrsSP} = req.app.locals; try { @@ -80,10 +83,22 @@ router.get('/:call_id', async(req, res) => { router.get('/:call_id/:method/pcap', async(req, res) => { const {logger} = req.app.locals; + + /** + * Cancel the requests to homer if the client goes away or homer is too slow. + * Otherwise an unread homer response keeps its socket open forever. + */ + const ac = new AbortController(); + const timer = setTimeout(() => ac.abort(), HOMER_PCAP_TIMEOUT_MS); + res.on('close', () => { + clearTimeout(timer); + ac.abort(); + }); + try { - const token = await getHomerApiKey(logger); + const token = await getHomerApiKey(logger, ac.signal); if (!token) return res.sendStatus(400, {msg: 'getHomerApiKey: Failed to get Homer API token; check server config'}); - const stream = await getHomerPcap(logger, token, [req.params.call_id], req.params.method); + const stream = await getHomerPcap(logger, token, [req.params.call_id], req.params.method, ac.signal); if (!stream) { logger.info(`getHomerApiKey: unable to get sip traces from Homer for ${req.params.call_id}`); return res.sendStatus(404); @@ -92,7 +107,10 @@ router.get('/:call_id/:method/pcap', async(req, res) => { 'Content-Type': 'application/octet-stream', 'Content-Disposition': `attachment; filename=callid-${req.params.call_id}.pcap` }); - stream.pipe(res); + /* unlike pipe(), pipeline() destroys the homer stream if the client disconnects */ + pipeline(stream, res, (err) => { + if (err) logger.info({err}, `pcap: stream for ${req.params.call_id} ended early`); + }); } catch (err) { logger.error({err}, 'getHomerApiKey error retrieving sip traces from homer'); res.sendStatus(err.statusCode || 500); diff --git a/lib/utils/homer-utils.js b/lib/utils/homer-utils.js index 27fcc82..afb8f8a 100644 --- a/lib/utils/homer-utils.js +++ b/lib/utils/homer-utils.js @@ -7,13 +7,14 @@ const { Readable } = require('stream'); const SEVEN_DAYS_IN_MS = (1000 * 3600 * 24 * 7); const HOMER_BASE_URL = process.env.HOMER_BASE_URL || 'http://127.0.0.1'; -const getHomerApiKey = async(logger) => { +const getHomerApiKey = async(logger, signal) => { if (!process.env.HOMER_BASE_URL || !process.env.HOMER_USERNAME || !process.env.HOMER_PASSWORD) { logger.debug('getHomerApiKey: Homer integration not installed'); } try { const response = await fetch(`${HOMER_BASE_URL}/api/v3/auth`, { + signal, method: 'POST', headers: { 'Content-Type': 'application/json' @@ -82,13 +83,14 @@ const getHomerSipTrace = async(logger, apiKey, callId) => { } }; -const getHomerPcap = async(logger, apiKey, callIds, method) => { +const getHomerPcap = async(logger, apiKey, callIds, method, signal) => { if (!process.env.HOMER_BASE_URL || !process.env.HOMER_USERNAME || !process.env.HOMER_PASSWORD) { logger.debug('getHomerPcap: Homer integration not installed'); } try { const now = Date.now(); const response = await fetch(`${HOMER_BASE_URL}/api/v3/export/call/messages/pcap`, { + signal, method: 'POST', headers: { 'Content-Type': 'application/json',