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
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
feat: add image rendering
alexpozzi committed Oct 16, 2024

Verified

This commit was signed with the committer’s verified signature.
alexpozzi Alessandro Pozzi
commit b654dc9f8b6a722f783f08da98031d6b91525e68
1 change: 1 addition & 0 deletions scripts/postRender.js
Original file line number Diff line number Diff line change
@@ -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
@@ -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),
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
@@ -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(),
}, {
3 changes: 2 additions & 1 deletion src/manager/requestRegistry.js
Original file line number Diff line number Diff line change
@@ -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]
}
},
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) =>
@@ -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()
}
}