Skip to content
This repository has been archived by the owner on Apr 6, 2022. It is now read-only.

Commit

Permalink
feat(assets): render with chromium in lambda
Browse files Browse the repository at this point in the history
  • Loading branch information
patte committed Feb 22, 2019
1 parent 06225f3 commit 0780578
Show file tree
Hide file tree
Showing 18 changed files with 662 additions and 220 deletions.
38 changes: 11 additions & 27 deletions .nowignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ build/Release
node_modules/
jspm_packages/
/packages/*/node_modules
*/node_modules

# Typescript v1 declaration files
typings/
Expand All @@ -55,38 +56,21 @@ typings/
# Yarn Integrity file
.yarn-integrity

# Mapped Docker Volumes
# docker
docker-data/
.docker-config/

# dotenv environment variables file
*.env
.env*

.DS_Store

servers/republik/seeds/seeds.json
servers/republik/seeds/comments.json
servers/republik/local/
servers/publikator/seeds/seeds.json

# now
.docker-config
# only lambdas
.github
servers/republik
servers/publikator
packages/apollo-modules-node
packages/access
packages/base
packages/collections
packages/discussions
packages/documents
packages/election
packages/env
packages/mail
packages/notifications
packages/preview
packages/schedulers
packages/search
packages/slack
packages/sms
packages/voting

.travis*
docker*
Procfile*
servers/
packages/
*.sql
8 changes: 8 additions & 0 deletions lambdas/chromium/.env.example
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=

40 changes: 40 additions & 0 deletions lambdas/chromium/README.md
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)
21 changes: 21 additions & 0 deletions lambdas/chromium/package.json
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"
}
}
125 changes: 125 additions & 0 deletions lambdas/chromium/screenshot.js
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)
}
}
Loading

0 comments on commit 0780578

Please sign in to comment.