diff --git a/README.md b/README.md index 337c86c..f3ff60b 100644 --- a/README.md +++ b/README.md @@ -234,6 +234,7 @@ Here we list them all with their purpose. * `TOKEN_2CAPTCHA = undefined` - token of [2captcha service](https://2captcha.com) * `STEALTH_BROWSING = true` - should the service use the [stealth browsing](https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth) mode * `MAX_CONCURRENT_CONTEXTS = undefined` - should the service limit the number of contexts +* `CONTEXT_TIMEOUT = 600000` - time (in ms) after the passage of which the context would close * `PROMETHEUS_HOST = undefined` - host address of the metrics endpoint, if not defined then assigns to "localhost" * `PROMETHEUS_PORT = undefined` - port of address of the metrics endpoint, if not defined then assigns to "9464" diff --git a/app.js b/app.js index 58e4ca4..d14fc17 100644 --- a/app.js +++ b/app.js @@ -24,6 +24,7 @@ const harRouter = require('./routes/har'); const closeContextRouter = require('./routes/close_context'); const middlewares = require('./helpers/middlewares'); +const timeoutContext = require('./helpers/timeout_context'); const limitContext = require('./helpers/limit_context'); const loggers = require("./helpers/loggers"); @@ -40,7 +41,9 @@ const VIEWPORT_HEIGHT = parseInt(process.env.VIEWPORT_HEIGHT) || 720; const TOKEN_2CAPTCHA = process.env.TOKEN_2CAPTCHA; const STEALTH_BROWSING = (process.env.STEALTH_BROWSING || "true").toLowerCase() === "true"; const MAX_CONCURRENT_CONTEXTS = process.env.MAX_CONCURRENT_CONTEXTS === "Infinity" ? Infinity : parseInt(process.env.MAX_CONCURRENT_CONTEXTS); +const CONTEXT_TIMEOUT = parseInt(process.env.CONTEXT_TIMEOUT) || 600000; // 10 minutes +timeoutContext.initTimeoutContext(CONTEXT_TIMEOUT); limitContext.initContextCounter(MAX_CONCURRENT_CONTEXTS); loggers.initLogger(LOG_LEVEL, LOG_FILE, LOGSTASH_HOST, LOGSTASH_PORT); diff --git a/helpers/timeout_context.js b/helpers/timeout_context.js new file mode 100644 index 0000000..8fc6e7d --- /dev/null +++ b/helpers/timeout_context.js @@ -0,0 +1,58 @@ +const {BrowserContext} = require('puppeteer'); +const loggers = require('./loggers'); + +/** + * ContextId -> Timeout timer' IDs + * + * @type {{string: number}} + */ +let contextTimeoutIds = {}; +let contextTimeout; + +/** + * Set timeout for context. + * + * @param {BrowserContext} context + */ +function setContextTimeout(context) { + const logger = loggers.getLogger(); + + contextTimeoutIds[context.id] = setTimeout( + async () => { + logger.warn(`Closing context ${context.id} due to timeout\n`); + await context.close(); + delete contextTimeoutIds[context.id]; + }, + contextTimeout); +} +exports.setContextTimeout = setContextTimeout; + +/** + * The function clears context's timeout timer. + * + * @param {BrowserContext} context context to be cleared + */ +function clearContextTimeout(context) { + clearTimeout(contextTimeoutIds[context.id]); + delete contextTimeoutIds[context.id]; +} +exports.clearContextTimeout = clearContextTimeout; + +/** + * Update timeout for context. + * + * @param {BrowserContext} context + */ +exports.updateContextTimeout = function updateContextTimeout (context) { + clearContextTimeout(context); + setContextTimeout(context); +} + +/** + * Init service that timeouts contexts after CONTEXT_TIMEOUT ms. + * + * @param {number} timeout + */ +exports.initTimeoutContext = function initTimeoutContext (timeout) { + contextTimeout = timeout; +} diff --git a/helpers/utils.js b/helpers/utils.js index 34ad788..0cb484f 100644 --- a/helpers/utils.js +++ b/helpers/utils.js @@ -1,5 +1,6 @@ const exceptions = require("./exceptions"); const { proxyRequest } = require('puppeteer-proxy'); +const timeoutContext = require('./timeout_context'); const limitContext = require('./limit_context'); const PROXY_URL_KEY = 'puppeteer-service-proxy-url' @@ -28,6 +29,7 @@ exports.closeContexts = async function closeContexts(browser, contextIds) { for (const context of browser.browserContexts()) { if (contextIds.includes(context.id)) { limitContext.decContextCounter(); + timeoutContext.clearContextTimeout(context); closePromises.push(context.close()); } } @@ -114,8 +116,10 @@ async function newContext(browser, options = {}) { } try { + const context = await browser.createIncognitoBrowserContext(options); limitContext.incContextCounter(); - return await browser.createIncognitoBrowserContext(options); + timeoutContext.setContextTimeout(context); + return context } catch (err) { limitContext.decContextCounter(); throw err; @@ -207,6 +211,8 @@ exports.performAction = async function performAction(request, action) { delete response.pageId; } + timeoutContext.updateContextTimeout(page.browserContext()); + return response; }); };