This repository has been archived by the owner on Apr 6, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(assets): render with chromium in lambda
- Loading branch information
Showing
18 changed files
with
662 additions
and
220 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# render urls must start with the following prefix | ||
#URL_WHITELIST=https://www.republik.ch | ||
# provide all allow any url | ||
URL_WHITELIST=all | ||
|
||
# optional: the chrome for puppeteer to connect to (if chrome-aws-lambda is not available) | ||
#PUPPETEER_WS_ENDPOINT= | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# @orbiting/lambdas-chromium | ||
|
||
API to puppeteer - chromium. | ||
|
||
[screenshot.js](screenshots.js) is a NodeJS [request](https://nodejs.org/api/http.html#http_event_request)-[response](https://nodejs.org/api/http.html#http_class_http_serverresponse)-handler to generate screenshots. | ||
|
||
[puppeteer]('https://github.com/GoogleChrome/puppeteer') tries to use locally available chromium (by [chrome-aws-lambda](https://github.com/alixaxel/chrome-aws-lambda)) otherwise tries to fall back to connect to `PUPPETEER_WS_ENDPOINT` | ||
|
||
|
||
## ENVs | ||
|
||
see [.env.example] | ||
|
||
``` | ||
now secret url_whitelist https://www.republik.ch,https://republik.ch | ||
now -e URL_WHITELIST=@url_whitelist | ||
``` | ||
|
||
|
||
## Endpoints | ||
- screenshot.js | ||
- example query: `/?url=:url&[width=[:w]x[:h]]&[zoomFactor=:sf]&[fullPage=:fp]&[cookie=:c]&[basicAuthUser=:u]&[basicAuthPass=:p]` | ||
- renders ?url | ||
- optional &width &height | ||
- default 1200x1 | ||
- optional &fullPage | ||
- default true | ||
- this api screenshots the full page per default (with scrolling), set `fullPage` to `'false'` or `'0'` to crop to viewport | ||
- optional &zoomFactor | ||
- requires viewport or w/h | ||
- default 1 | ||
- optional &cookie | ||
- example: 'id=208h2n' | ||
- optionsl &basicAuthUser &basicAuthPass | ||
- send basic auth on opening urls | ||
|
||
|
||
## Credits | ||
|
||
Inspired by: [now-examples puppeteer-screenshot](https://github.com/zeit/now-examples/tree/master/puppeteer-screenshot) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"name": "@orbiting/lambdas-chromium", | ||
"version": "0.0.1", | ||
"description": "headless chrome providing screenshots", | ||
"main": "index.js", | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/orbiting/backends.git" | ||
}, | ||
"author": "Patrick Recher <patrick.recher@republik.ch>", | ||
"license": "AGPL-3.0", | ||
"bugs": { | ||
"url": "https://github.com/orbiting/backends/issues" | ||
}, | ||
"homepage": "https://github.com/orbiting/backends#readme", | ||
"dependencies": { | ||
"chrome-aws-lambda": "^1.12.0", | ||
"debug": "^3.1.0", | ||
"puppeteer-core": "^1.12.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
const puppeteer = require('puppeteer-core') | ||
const chromium = require('chrome-aws-lambda') | ||
const { parse } = require('url') | ||
const debug = require('debug')('screenshot') | ||
|
||
const { | ||
URL_WHITELIST, | ||
PUPPETEER_WS_ENDPOINT | ||
} = process.env | ||
|
||
const DEFAULT_WIDTH = 1200 | ||
const DEFAULT_HEIGHT = 1 | ||
const DEFAULT_SCALE_FACTOR = 1 | ||
|
||
if (!URL_WHITELIST) { | ||
console.warn('missing env URL_WHITELIST, the /render endpoint will not work') | ||
} | ||
const whitelistedUrls = URL_WHITELIST && URL_WHITELIST.split(',') | ||
|
||
const getBrowser = async () => { | ||
if (chromium.headless) { | ||
debug('rendering with local headless chrome') | ||
return puppeteer.launch({ | ||
args: chromium.args, | ||
executablePath: await chromium.executablePath, | ||
headless: chromium.headless | ||
}) | ||
} else { | ||
if (!PUPPETEER_WS_ENDPOINT) { | ||
console.warn('missing env PUPPETEER_WS_ENDPOINT, cannot render') | ||
return | ||
} | ||
debug(`rendering with chromium @ PUPPETEER_WS_ENDPOINT`) | ||
return puppeteer.connect({ | ||
browserWSEndpoint: PUPPETEER_WS_ENDPOINT | ||
}) | ||
} | ||
} | ||
|
||
const getPosInt = (number) => | ||
Math.ceil(Math.abs(number)) | ||
|
||
// returns buffer | ||
module.exports = async (req, res) => { | ||
const { | ||
query: { | ||
url, | ||
width, | ||
height, | ||
zoomFactor, | ||
fullPage = true, | ||
type = 'png', | ||
quality, | ||
cookie, | ||
basicAuthUser, | ||
basicAuthPass | ||
} | ||
} = parse(req.url, true) | ||
debug({ url, width, height, zoomFactor, fullPage }) | ||
|
||
if (!url) { | ||
res.statusCode = 422 | ||
return res.end('missing url param') | ||
} | ||
|
||
const allowed = | ||
(URL_WHITELIST && URL_WHITELIST === 'all') || | ||
(whitelistedUrls && !!whitelistedUrls.find(whiteUrl => url.indexOf(whiteUrl) === 0)) | ||
|
||
if (!allowed) { | ||
console.warn('unauthorized render url requested: ' + url) | ||
res.statusCode = 403 | ||
return res.end() | ||
} | ||
|
||
try { | ||
const browser = await getBrowser() | ||
|
||
const page = await browser.newPage() | ||
|
||
const promises = [ | ||
page.setViewport({ | ||
width: getPosInt(width) || DEFAULT_WIDTH, | ||
height: getPosInt(height) || DEFAULT_HEIGHT, | ||
deviceScaleFactor: Math.abs(zoomFactor) || DEFAULT_SCALE_FACTOR | ||
}), | ||
page.setExtraHTTPHeaders({ 'DNT': '1' }) | ||
] | ||
|
||
if (cookie) { | ||
const [name, value] = cookie.split('=') | ||
promises.push( | ||
page.setCookie({ name, value, url }) | ||
) | ||
} | ||
|
||
await Promise.all(promises) | ||
|
||
await page.goto(url) | ||
|
||
if (basicAuthUser) { | ||
await page.authenticate({ | ||
username: basicAuthUser, | ||
password: basicAuthPass | ||
}) | ||
} | ||
|
||
const screenshot = await page.screenshot({ | ||
fullPage: !(['false', '0'].includes(fullPage)), | ||
type, | ||
...quality !== undefined | ||
? { quality: getPosInt(quality) } | ||
: {} | ||
}) | ||
|
||
browser.close() | ||
|
||
res.statusCode = 200 | ||
res.setHeader('Content-Type', `image/${type}`) | ||
return res.end(screenshot) | ||
} catch (error) { | ||
res.statusCode = 500 | ||
return res.end(error.message || error) | ||
} | ||
} |
Oops, something went wrong.