Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add image rendering #918

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions scripts/postRender.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@
* }
* ```
*/
script = function () {};
2 changes: 2 additions & 0 deletions src/manager/http-server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
24 changes: 24 additions & 0 deletions src/manager/http-server/middlewares/render-image.js
Original file line number Diff line number Diff line change
@@ -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.'),
),
)))
1 change: 1 addition & 0 deletions src/manager/http-server/middlewares/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}, {
Expand Down
3 changes: 2 additions & 1 deletion src/manager/requestRegistry.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]
}
},
Expand Down
12 changes: 10 additions & 2 deletions src/worker/queue/processHandler.js
Original file line number Diff line number Diff line change
@@ -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) =>
Expand All @@ -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}".`)
}
}
57 changes: 57 additions & 0 deletions src/worker/renderers/chrome/image-renderer.js
Original file line number Diff line number Diff line change
@@ -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()
}
}