Skip to content

Instantly share code, notes, and snippets.

@rkistner
Created December 5, 2017 12:34
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rkistner/c695c64ec573581b47e349e8bbe98d86 to your computer and use it in GitHub Desktop.
Save rkistner/c695c64ec573581b47e349e8bbe98d86 to your computer and use it in GitHub Desktop.
Using Chrome on Lambda
import { spawn, execSync, ChildProcess } from 'child_process'
import * as net from 'net'
const { chromePath } = require('aws-lambda-chrome');
const Cdp = require('chrome-remote-interface');
export type ChromeHandler<T> = (client: ChromeClient) => Promise<T>;
// These security features are not supported at all, and need to be disabled.
const SECURITY_NOT_SUPPORTED = [
'--single-process',
'--no-sandbox',
'--no-zygote'
// These additional features are disabled in the upstream project, but Chromium seems
// to run fine if we leave them enabled:
// '--nacl-dangerous-no-sandbox-nonsfi',
// '--disable-setuid-sandbox',
// '--disable-seccomp-filter-sandbox',
// '--disable-kill-after-bad-ipc',
// '--disable-namespace-sandbox'
];
// These features are not relevant for generating PDFs
const IRRELEVANT_FEATURES = [
'--disable-background-networking',
'--disable-breakpad',
'--disable-canvas-aa',
'--disable-client-side-phishing-detection',
'--disable-cloud-import',
'--disable-gpu',
'--disable-gpu-sandbox',
'--disable-plugins',
'--disable-print-preview',
'--disable-renderer-backgrounding',
'--disable-smooth-scrolling',
'--disable-sync',
'--disable-translate',
'--disable-translate-new-ux',
'--disable-webgl',
'--disable-composited-antialiasing',
'--disable-default-apps',
'--disable-extensions-http-throttling',
'--no-default-browser-check',
'--no-experiments',
'--no-first-run',
'--no-pings',
'--prerender-from-omnibox=disabled'
];
// Just some internal configuration
const INTERNAL_CONFIGURATION = [
'--disk-cache-dir=/tmp/cache-dir',
'--disk-cache-size=10000000',
'--ipc-connection-timeout=10000',
'--media-cache-size=10000000'
];
// Relevant configuration
const CONFIGURATION = [
'--remote-debugging-port=9222',
'--user-data-dir=tmp/user-data',
'--window-size=1280,720'
];
const START_URL = 'about:blank';
const CHROME_ARGS = [
...SECURITY_NOT_SUPPORTED,
...IRRELEVANT_FEATURES,
...INTERNAL_CONFIGURATION,
...CONFIGURATION,
START_URL
];
export interface ChromeClient {
Network: any;
Page: any;
Runtime: any;
Storage: any;
Console: any;
Log: any;
close: () => Promise<void>;
}
let chromeProcess: ChildProcess = null;
export async function launchChrome<T>(handler: ChromeHandler<T>): Promise<T> {
console.log('Spawning Chrome');
if (chromeProcess == null) {
chromeProcess = spawn(chromePath, CHROME_ARGS, {
env: process.env,
// No need to detach
detached: false
});
// Unref process, so that it doesn't prevent the Lambda request from finishing
chromeProcess.unref();
chromeProcess.on('exit', (code: number) => {
// Fatal error, can't recover from this
console.error(`Chrome has exited with code ${code}`);
process.exit(1);
});
chromeProcess.stdout.on('data', (data) => {
console.log(data);
});
chromeProcess.stderr.on('data', (data) => {
console.error(data);
});
(chromeProcess.stdout as any).unref();
(chromeProcess.stderr as any).unref();
(chromeProcess.stdin as any).unref();
}
const { tab, client } = await tryConnect(100);
try {
return await handler(client);
} finally {
await Cdp.Close({ id: tab.id });
await client.close();
console.log('Closed connection');
if (process.env.LAMBDA_TASK_ROOT) {
// Running on Lambda: Output process stats
console.log(execSync('ps auxf').toString())
}
}
}
async function tryConnect(tries: number) {
for (let i = 0; i < tries; i++) {
try {
const tab = await Cdp.New({ host: '127.0.0.1', port: 9222 });
const client: ChromeClient = await Cdp({target: tab});
return { tab, client };
} catch (error) {
if ((error.code == 'ECONNREFUSED' || error.code == 'ECONNRESET') && i < tries - 1) {
await delay(10);
continue;
} else {
throw error;
}
}
}
throw new Error('Unreachable code reached');
}
async function delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
import { launchChrome } from './chrome';
export async function test(url: string) {
const startedAt = Date.now();
return await launchChrome(async (client) => {
const { Network, Page, Runtime, Storage, Console } = client;
await Promise.all([
Network.enable(),
Page.enable(),
Console.enable(),
Runtime.enable()
]);
const loadEvent = Page.loadEventFired();
// Navigate to provided URL.
await Page.navigate({ url: url });
// Wait for loading
await loadEvent;
// const pdf = await Page.printToPDF({});
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment