Skip to content

Commit

Permalink
chore: Update the screenshot Lambda function (#20427)
Browse files Browse the repository at this point in the history
* Replace vendored dependency with npm packages

* Drop babel, run the code directly on node 10+

* Remove unneeded files

* Use new dependency

* Rewrite

Add readme and manual tests for lambda function

Co-authored-by: GatsbyJS Bot <mathews.kyle+gatsbybot@gmail.com>
  • Loading branch information
m-allanson and GatsbyJS Bot committed Jan 8, 2020
1 parent 51d81e9 commit 242ff89
Show file tree
Hide file tree
Showing 15 changed files with 284 additions and 335 deletions.
1 change: 1 addition & 0 deletions packages/gatsby-transformer-screenshot/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
yarn.lock
lambda-package.zip
lambda-dist
lambda/screenshots
39 changes: 0 additions & 39 deletions packages/gatsby-transformer-screenshot/chrome/buildChrome.sh

This file was deleted.

Binary file not shown.
10 changes: 0 additions & 10 deletions packages/gatsby-transformer-screenshot/lambda/.babelrc

This file was deleted.

23 changes: 23 additions & 0 deletions packages/gatsby-transformer-screenshot/lambda/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# About

This is the Lambda function that gatsby-transformer-screenshot uses to take screenshots. See [the plugin README](../README.md) for instructions on deploying this.

## Manual testing

The files in the `__manual_tests__` directory can be used to test locally or test the deployed version on AWS.

Note that successful runs will cache the screenshot. Be sure to change the `SITE_URL` to re-test screenshot functionality.

### Local

To test the Puppeteer functionality locally:

`TEST_WITH_LOCAL_FS=true SITE_URL=https://en.wikipedia.org/wiki/Wool node __manual-tests__/local-test.js`

Screenshots will be saved to `./screenshots`.

### Remote

To test the function deployed at `<url to Lambda endpoint>`:

`SCREENSHOT_ENDPOINT=<URL to Lambda endpoint> SITE_URL=https://en.wikipedia.org/wiki/Iron node __manual-tests__/remote-test.js`
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const thing = require(`../index`)
const url = process.env.SITE_URL

const start = async () => {
const result = await thing.handler(
{
body: `{"url":"${url}"}`,
},
{
fail: err => {
console.log(`error:`)
console.log(err)
},
succeed: res => {
console.log(`success!`)
console.log(res)
},
}
)

return result
}

start()
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const axios = require(`axios`)
const SCREENSHOT_ENDPOINT = process.env.SCREENSHOT_ENDPOINT
const url = process.env.SITE_URL

const start = async () => {
let screenshotResponse
try {
screenshotResponse = await axios.post(SCREENSHOT_ENDPOINT, { url })
const { status, data /* , headers */ } = screenshotResponse
console.log(status)
console.log(data)
} catch (error) {
console.log(error)
console.log(screenshotResponse)
}
}

start()
192 changes: 58 additions & 134 deletions packages/gatsby-transformer-screenshot/lambda/index.js
Original file line number Diff line number Diff line change
@@ -1,103 +1,75 @@
const setup = require(`./starter-kit/setup`)
const chromium = require(`chrome-aws-lambda`)
const getIO = require(`./screenshot.js`)

const { createContentDigest } = require(`gatsby-core-utils`)
exports.handler = async (event, context) => {
let browser = null
let image
const request = event.body ? JSON.parse(event.body) : {}

const AWS = require(`aws-sdk`)
const s3 = new AWS.S3({
apiVersion: `2006-03-01`,
})

exports.handler = async (event, context, callback) => {
// For keeping the browser launch
context.callbackWaitsForEmptyEventLoop = false

let request = {}
if (event.body) {
request = JSON.parse(event.body)
if (!request.url) {
return proxyError(`no url provided`)
}

const url = request.url

if (!url) {
callback(null, proxyError(`no url provided`))
return
}
const screenshot = await getIO({
url: request.url,
width: request.width || 1024,
height: request.height || 768,
fullPage: request.fullPage || false,
})

const width = request.width || 1024
const height = request.height || 768
console.log(
`Invoked: ${screenshot.url} (${screenshot.width}x${screenshot.height})`
)

const fullPage = request.fullPage || false
console.log(`SCREENSHOT`, screenshot)

const browser = await setup.getBrowser()
exports
.run(browser, url, width, height, fullPage)
.then(result => {
callback(null, proxyResponse(result))
})
.catch(err => {
callback(null, proxyError(err))
// is it cached already?
const maybeFile = await screenshot.getFile()
if (maybeFile) {
console.log(`Cache hit. Returning screenshot from cache`)
return proxyResponse({
url: screenshot.fileUrl,
expires: screenshot.expires,
})
}

exports.run = async (browser, url, width, height, fullPage) => {
console.log(`Invoked: ${url} (${width}x${height})`)

if (!process.env.S3_BUCKET) {
throw new Error(
`Provide the S3 bucket to use by adding an S3_BUCKET` +
` environment variable to this Lambda's configuration`
)
}

const region = await s3GetBucketLocation(process.env.S3_BUCKET)

if (!region) {
throw new Error(`invalid bucket ${process.env.S3_BUCKET}`)
}

const contentDigest = createContentDigest({ url, width, height })
const key = `${contentDigest}.png`

const screenshotUrl = `https://s3-${region}.amazonaws.com/${
process.env.S3_BUCKET
}/${key}`
try {
browser = await chromium.puppeteer.launch({
args: chromium.args,
defaultViewport: chromium.defaultViewport,
executablePath: await chromium.executablePath,
// headless: chromium.headless,
headless: true,
})

const metadata = await s3HeadObject(key)
console.log(`Opening browser`)
const page = await browser.newPage()
await page.setViewport({
width: screenshot.width,
height: screenshot.height,
deviceScaleFactor: 2,
})

const now = new Date()
if (metadata) {
if (metadata.Expiration) {
const expires = getDateFromExpiration(metadata.Expiration)
if (now < expires) {
console.log(`Returning cached screenshot`)
return { url: screenshotUrl, expires }
}
} else {
throw new Error(`no expiration date set`)
console.log(`Taking screenshot`)
await page.goto(screenshot.url, { waitUntil: [`networkidle2`] })
await page.waitFor(1000) // wait for full-size images to fade in
image = await page.screenshot({ fullPage: screenshot.fullPage })
await page.close()
await browser.close()

console.log(`Writing file`)
await screenshot.putFile(image)
return proxyResponse({
url: screenshot.fileUrl,
expires: screenshot.expires,
})
} catch (error) {
return proxyError(error)
} finally {
if (browser !== null) {
await browser.close()
}
}

console.log(`Taking new screenshot`)

const page = await browser.newPage()

await page.setViewport({ width, height, deviceScaleFactor: 2 })
await page.goto(url, { waitUntil: [`load`, `networkidle0`] })
// wait for full-size images to fade in
await page.waitFor(1000)

const screenshot = await page.screenshot({ fullPage })
const up = await s3PutObject(key, screenshot)

await page.close()

let expires

if (up && up.Expiration) {
expires = getDateFromExpiration(up.Expiration)
}

return { url: screenshotUrl, expires }
}

const proxyResponse = body => {
Expand All @@ -124,51 +96,3 @@ const proxyError = err => {
}),
}
}

const s3PutObject = async (key, body) => {
const params = {
ACL: `public-read`,
Bucket: process.env.S3_BUCKET,
Key: key,
Body: body,
ContentType: `image/png`,
}

return new Promise((resolve, reject) => {
s3.putObject(params, (err, data) => {
if (err) reject(err)
else resolve(data)
})
})
}

const s3GetBucketLocation = bucket => {
const params = {
Bucket: bucket,
}

return new Promise((resolve, reject) => {
s3.getBucketLocation(params, (err, data) => {
if (err) resolve(null)
else resolve(data.LocationConstraint)
})
})
}

const s3HeadObject = key => {
const params = {
Bucket: process.env.S3_BUCKET,
Key: key,
}

return new Promise((resolve, reject) => {
s3.headObject(params, (err, data) => {
if (err) resolve(null)
else resolve(data)
})
})
}

const expiryPattern = /expiry-date="([^"]*)"/
const getDateFromExpiration = expiration =>
new Date(expiryPattern.exec(expiration)[1])
11 changes: 6 additions & 5 deletions packages/gatsby-transformer-screenshot/lambda/package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
{
"dependencies": {
"chrome-aws-lambda": "^2.0.1",
"gatsby-core-utils": "^1.0.22",
"puppeteer": "0.13.0",
"tar": "^4.4.13"
"puppeteer-core": "^2.0.0"
},
"devDependencies": {
"aws-sdk": "^2.586.0"
},
"keywords": [
"gatsby-plugin"
]
"keywords": [],
"engines": {
"node": ">=10.13.0"
}
}
Loading

0 comments on commit 242ff89

Please sign in to comment.