Skip to content

Commit

Permalink
Use API Gateway
Browse files Browse the repository at this point in the history
  • Loading branch information
cassiebeckley committed Jan 22, 2018
1 parent bb0acdc commit c928f43
Show file tree
Hide file tree
Showing 14 changed files with 78 additions and 109 deletions.
2 changes: 1 addition & 1 deletion packages/gatsby-transformer-screenshot/.gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*.js
!index.js
yarn.lock
lambda
lambda-package.zip
lambda-dist
3 changes: 1 addition & 2 deletions packages/gatsby-transformer-screenshot/.npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ decls
examples

# Lambda-related
src/lambda
lambda
lambda-package.json
lambda-dist
chrome
lambda-package.zip
37 changes: 15 additions & 22 deletions packages/gatsby-transformer-screenshot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,15 @@ property, and creates `Screenshot` nodes with an `screenshotFile` field.

`npm install gatsby-transformer-screenshot`

## Lambda setup

AWS Lambda is a "serverless" computing platform that lets you run code in response to events, without needing to set up a server. This plugin uses a Lambda function to take screenshots and store them in an AWS S3 bucket.

First, you will need to (create a S3 bucket)[https://docs.aws.amazon.com/AmazonS3/latest/gsg/CreatingABucket.html] for storing screenshots. Once you have done that, create a (Lifecycle Policy)[https://docs.aws.amazon.com/AmazonS3/latest/user-guide/create-lifecycle.html] for the bucket that sets a number of days before files in the bucket expire. Screenshots will be cached until this date.

To build the Lambda package, run `npm run build-lambda-package` in this directory. A file called `lambda-package.zip` will be generated - upload this as the source of your AWS Lambda. Finally, you will need to set `S3_BUCKET` as an environment variable for the lambda.

## How to use

```javascript
// in your gatsby-config.js
plugins: [
{
resolve: `gatsby-transformer-screenshot`,
options: {
lambdaName: `gatsby-screenshot-lambda`,
region: 'us-west-2',
credentials: { // optional
accessKeyId: 'xxxx',
secretAccessKey: 'xxxx',
sessionToken: 'xxxx' // optional
}
}
}
`gatsby-transformer-screenshot`
]
```

AWS provides several ways to configure credentials; see here for more information: https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-credentials-node.html. If you set `credentials` in this plugin's options, it will override all the other methods.

## How to query

You can query for screenshot files as shown below:
Expand All @@ -61,3 +40,17 @@ You can query for screenshot files as shown below:
```

screenshotFile is a PNG file like any other loaded from your filesystem, so you can use this plugin in combination with `gatsby-image`.

## Lambda setup

Gatsby provides a hosted screenshot service for you to use; however, you can run the service yourself on AWS Lambda.

AWS Lambda is a "serverless" computing platform that lets you run code in response to events, without needing to set up a server. This plugin uses a Lambda function to take screenshots and store them in an AWS S3 bucket.

First, you will need to (create a S3 bucket)[https://docs.aws.amazon.com/AmazonS3/latest/gsg/CreatingABucket.html] for storing screenshots. Once you have done that, create a (Lifecycle Policy)[https://docs.aws.amazon.com/AmazonS3/latest/user-guide/create-lifecycle.html] for the bucket that sets a number of days before files in the bucket expire. Screenshots will be cached until this date.

To build the Lambda package, run `npm run build-lambda-package` in this directory. A file called `lambda-package.zip` will be generated - upload this as the source of your AWS Lambda. Finally, you will need to set `S3_BUCKET` as an environment variable for the lambda.

To set up the HTTP interface, you will need to use AWS API Gateway. Create a new API, create a new resource under `/`, select "Configure as proxy resource", and leave all the settings with their defaults. Create a method on the new resource, selecting "Lambda Function Proxy" as the integration type, and fill in the details of your lambda.


Binary file not shown.

This file was deleted.

Binary file not shown.
10 changes: 10 additions & 0 deletions packages/gatsby-transformer-screenshot/lambda/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"presets": [
["env",
{
"targets": {
"node": "6.10"
}
}]
]
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
const setup = require(`./starter-kit/setup`)

const crypto = require(`crypto`)
const fs = require(`fs`)

const tmp = require(`tmp`)
const AWS = require(`aws-sdk`)
const s3 = new AWS.S3({
apiVersion: `2006-03-01`,
Expand All @@ -13,24 +11,29 @@ exports.handler = async (event, context, callback) => {
// For keeping the browser launch
context.callbackWaitsForEmptyEventLoop = false

const url = event.url
let request = {}
if (event.body) {
request = JSON.parse(event.body)
}

const url = request.url

if (!url) {
callback(`no url provided`)
callback(null, proxyError(`no url provided`))
return
}

const width = event.width || 1024
const height = event.height || 768
const width = request.width || 1024
const height = request.height || 768

const browser = await setup.getBrowser()
exports
.run(browser, url, width, height)
.then(result => {
callback(null, result)
callback(null, proxyResponse(result))
})
.catch(err => {
callback(err)
callback(null, proxyError(err))
})
}

Expand Down Expand Up @@ -79,13 +82,12 @@ exports.run = async (browser, url, width, height) => {
console.log(`Taking new screenshot`)

const page = await browser.newPage()

await page.setViewport({ width, height })
await page.goto(url, { waitUntil: [`load`, `networkidle0`] })

const temp = await tmpFile({ postfix: `.png` })
await page.screenshot({ path: temp.path })
const up = await s3FileUpload(temp.path, key)
await tmpCleanup(temp)
const screenshot = await page.screenshot()
const up = await s3PutObject(key, screenshot)

await page.close()

Expand All @@ -98,29 +100,42 @@ exports.run = async (browser, url, width, height) => {
return { url: screenshotUrl, expires }
}

const tmpFile = options =>
new Promise((resolve, reject) => {
tmp.file(options || {}, (err, path, fd, cleanupCallback) => {
if (err) reject(err)
else resolve({ path, fd, cleanupCallback })
})
})
const proxyResponse = body => {
body.success = true

const tmpCleanup = temp =>
new Promise((resolve, reject) => {
temp.cleanupCallback(resolve)
})
return {
statusCode: 200,
body: JSON.stringify(body),
}
}

const proxyError = err => {
let msg = err

if (err instanceof Error) {
msg = err.message
}

return {
statusCode: 400,
body: JSON.stringify({
success: false,
error: msg,
}),
}
}

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

return new Promise((resolve, reject) => {
s3.upload(params, (err, data) => {
s3.putObject(params, (err, data) => {
if (err) reject(err)
else resolve(data)
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
{
"dependencies": {
"puppeteer": "^1.0.0",
"tar": "^4.2.0",
"tmp": "0.0.33"
"puppeteer": "0.10.2",
"tar": "^4.2.0"
},
"devDependencies": {
"aws-sdk": "^2.181.0"
Expand Down
6 changes: 3 additions & 3 deletions packages/gatsby-transformer-screenshot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "Gatsby transformer plugin that uses AWS Lambda to take screenshots of websites",
"main": "index.js",
"dependencies": {
"aws-sdk": "^2.181.0"
"axios": "^0.17.1"
},
"devDependencies": {
"babel-cli": "^6.26.0",
Expand All @@ -14,8 +14,8 @@
"build": "babel src --out-dir . --ignore __tests__",
"watch": "babel -w src --out-dir . --ignore __tests__",
"prepublish": "cross-env NODE_ENV=production npm run build",
"build-lambda-package": "npm run prepare-lambda-package && cp chrome/headless_shell.tar.gz lambda && cd lambda && zip -rq ../lambda-package.zip .",
"prepare-lambda-package": "npm run build && cp lambda-package.json lambda/package.json && cd lambda && PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1 npm install --production"
"build-lambda-package": "npm run prepare-lambda-package && cp chrome/headless_shell.tar.gz lambda-dist && cd lambda-dist && zip -rq ../lambda-package.zip .",
"prepare-lambda-package": "babel lambda --out-dir lambda-dist && cp lambda/package.json lambda-dist/package.json && cd lambda-dist && PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1 npm install --production"
},
"keywords": [
"gatsby",
Expand Down
57 changes: 5 additions & 52 deletions packages/gatsby-transformer-screenshot/src/gatsby-node.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const crypto = require(`crypto`)
const AWS = require(`aws-sdk`)
const axios = require(`axios`)
const _ = require(`lodash`)
const { createRemoteFileNode } = require(`gatsby-source-filesystem`)

var lambda
const SCREENSHOT_ENDPOINT = `https://vuz0le7eki.execute-api.us-west-2.amazonaws.com/production/screenshot` // TODO: replace with version hosted on Gatsby AWS

const createContentDigest = obj =>
crypto
Expand All @@ -17,31 +17,6 @@ exports.onPreBootstrap = (
) => {
const { createNode, touchNode } = boundActionCreators

// Set up the lambda service object based on configuration options

if (!pluginOptions.lambdaName) {
console.log(`
gatsby-transformer-screenshot requires a lambdaName option. Please specify
the name of the AWS Lambda function to invoke.
`)
process.exit(1)
}

const options = {
params: { FunctionName: pluginOptions.lambdaName },
apiVersion: `2015-03-31`,
}

if (pluginOptions.region) {
options.region = pluginOptions.region
}

if (pluginOptions.credentials) {
options.credentials = pluginOptions.credentials
}

lambda = new AWS.Lambda(options)

// Check for updated screenshots
// and prevent Gatsby from garbage collecting remote file nodes
return Promise.all(
Expand Down Expand Up @@ -88,39 +63,17 @@ exports.onCreateNode = async ({ node, boundActionCreators, store, cache }) => {
})
}

const getScreenshot = url => {
const params = {
Payload: JSON.stringify({ url }),
}

return new Promise((resolve, reject) => {
lambda.invoke(params, (err, data) => {
if (err) reject(err)
else {
const payload = JSON.parse(data.Payload)

if (
typeof data.FunctionError === `string` &&
data.FunctionError.length > 0
)
reject(payload)
resolve(payload)
}
})
})
}

const createScreenshotNode = async ({
url,
parent,
store,
cache,
createNode,
}) => {
const screenshotResponse = await getScreenshot(url)
const screenshotResponse = await axios.post(SCREENSHOT_ENDPOINT, { url })

const fileNode = await createRemoteFileNode({
url: screenshotResponse.url,
url: screenshotResponse.data.url,
store,
cache,
createNode,
Expand All @@ -129,7 +82,7 @@ const createScreenshotNode = async ({
const screenshotNode = {
id: `${parent} >>> Screenshot`,
url,
expires: screenshotResponse.expires,
expires: screenshotResponse.data.expires,
parent,
children: [],
internal: {
Expand Down

0 comments on commit c928f43

Please sign in to comment.