diff --git a/scripts/postRender.js b/scripts/postRender.js index edc167f9..9b0abe3a 100644 --- a/scripts/postRender.js +++ b/scripts/postRender.js @@ -21,3 +21,4 @@ * } * ``` */ +script = function () {}; diff --git a/src/manager/http-server/index.js b/src/manager/http-server/index.js index 43f005e0..5908dc5b 100644 --- a/src/manager/http-server/index.js +++ b/src/manager/http-server/index.js @@ -2,12 +2,14 @@ import attachErrorMiddleware from './middlewares/error' import attachNotFoundMiddleware from './middlewares/notFound' import attachNotSupportedMiddleware from './middlewares/notSupported' import attachRenderMiddleware from './middlewares/render' +import attachRenderImageMiddleware from './middlewares/render-image' import express from 'express' import { pipe } from 'ramda' // createHttpServer => (Configuration, Logger, Queue, RequestRegistry) -> HttpServer export default (configuration, logger, queue, requestRegistry) => pipe( attachRenderMiddleware(configuration, logger, queue, requestRegistry), + attachRenderImageMiddleware(configuration, logger, queue, requestRegistry), attachNotFoundMiddleware, attachNotSupportedMiddleware, attachErrorMiddleware(logger), diff --git a/src/manager/http-server/middlewares/render-image.js b/src/manager/http-server/middlewares/render-image.js new file mode 100644 index 00000000..4efce56d --- /dev/null +++ b/src/manager/http-server/middlewares/render-image.js @@ -0,0 +1,24 @@ +import { call, complement, compose, ifElse, isNil, path, pipe } from 'ramda' +import { DEFAULT_JOB_OPTIONS } from '../../../queue' + +// default :: (Configuration, Logger, Queue, RequestRegistry) -> Express.app -> Express.app +export default (configuration, logger, queue, requestRegistry) => app => + app.get('/render-image', (req, res, next) => call(pipe( + () => logger.debug(`Image render request for url "${req.query.url}" started.`), + ifElse( + () => compose(complement(isNil), path(['query', 'url']))(req), + pipe( + () => requestRegistry.add(req, res, next), + jobId => queue.add({ + type: 'image', + url: req.query.url, + queuedAt: Date.now(), + }, { + ...DEFAULT_JOB_OPTIONS, + timeout: configuration.queue.job.timeout, + jobId, + }), + ), + () => res.status(400).end('Missing url query parameter.'), + ), + ))) diff --git a/src/manager/http-server/middlewares/render.js b/src/manager/http-server/middlewares/render.js index 0bb7a3d0..55084325 100644 --- a/src/manager/http-server/middlewares/render.js +++ b/src/manager/http-server/middlewares/render.js @@ -10,6 +10,7 @@ export default (configuration, logger, queue, requestRegistry) => app => pipe( () => requestRegistry.add(req, res, next), jobId => queue.add({ + type: 'html', url: req.query.url, queuedAt: Date.now(), }, { diff --git a/src/manager/requestRegistry.js b/src/manager/requestRegistry.js index 29adbbfc..f7280b1e 100644 --- a/src/manager/requestRegistry.js +++ b/src/manager/requestRegistry.js @@ -28,7 +28,8 @@ export default () => ({ complete: function (id, result, status = 200) { if (this.has(id)) { - this._requests[id].res.status(status).send(result) + //this._requests[id].res.status(status).send(result) + this._requests[id].res.status(status).end(Buffer.from(result, 'base64')) delete this._requests[id] } }, diff --git a/src/worker/queue/processHandler.js b/src/worker/queue/processHandler.js index d88af8ab..440b834b 100644 --- a/src/worker/queue/processHandler.js +++ b/src/worker/queue/processHandler.js @@ -1,4 +1,5 @@ -import render from '../renderers/chrome' +import render from '../renderers/chrome/renderer' +import renderImage from '../renderers/chrome/image-renderer' // isJobExpired :: (Configuration, Job) -> Boolean const isJobExpired = (configuration, job) => @@ -12,5 +13,12 @@ export default (configuration, logger, scriptProvider) => async job => { logger.debug(`Processing job "${job.id}" with url "${job.data.url}".`) - return await render(configuration, logger, scriptProvider)(job.data.url) + switch(job.data.type) { + case 'html': + return await render(configuration, logger, scriptProvider)(job.data.url) + case 'image': + return await renderImage(configuration, logger, scriptProvider)(job.data.url) + default: + throw new Error(`Unknown job type "${job.data.type}".`) + } } diff --git a/src/worker/renderers/chrome/image-renderer.js b/src/worker/renderers/chrome/image-renderer.js new file mode 100644 index 00000000..3bc8478d --- /dev/null +++ b/src/worker/renderers/chrome/image-renderer.js @@ -0,0 +1,57 @@ +import { POST_RENDER_SCRIPT_KEY } from '../../scriptProvider' +import browserRequestHandler from './browserRequestHandler' +import { formatException } from './../../../logger' +import getBrowserProvider from './browserProvider' +import { reduce } from 'ramda' + +// renderPageScreenshot :: (Configuration, Logger, ScriptProvier, BrowserInstance, String) -> RenderedPage +const renderPageScreenshot = async (configuration, logger, scriptProvider, browserInstance, url) => { + const page = await browserInstance.newPage() + + page.on('error', error => logger.error(formatException(error))) + page.on('pageerror', error => logger.error(formatException(error))) + page.on('requestfailed', req => logger.debug(`Browser request failed. ${req.url()}. ${req.failure().errorText}`)) + // See https://github.com/puppeteer/puppeteer/issues/3397#issuecomment-434970058 + page.on('console', async msg => { + const args = await Promise.all(msg.args().map( + jsHandle => jsHandle.executionContext().evaluate(arg => { + if (arg instanceof Error) { + return arg.message + } + + return arg + }), + )) + const text = reduce((acc, cur) => acc += acc !== '' ? `, ${cur}` : cur, '', args) + + logger.debug(`CONSOLE.${msg.type()}: ${msg.text()}\n${text}`) + }) + + await page.goto(url, { + waitUntil: 'networkidle0', + timeout: configuration.worker.renderer.timeout, + }) + + await page.evaluate(scriptProvider.get(POST_RENDER_SCRIPT_KEY)) + + return await page.screenshot({ encoding: 'base64' }) + } + +// render :: (Configuration, Logger, ScriptProvider) -> String +export default (configuration, logger, scriptProvider) => async url => { + const browserProvider = getBrowserProvider(configuration, logger) + const browserInstance = await browserProvider.getInstance() + + try { + return await renderPageScreenshot(configuration, logger, scriptProvider, browserInstance, url) + } catch (error) { + logger.error( + `An error occurred while rendering the url "${url}".`, + formatException(error), + ) + + throw error + } finally { + browserProvider.cleanup() + } + }