Browsers in custom APIs¶
A custom API can drive a headless Chromium with Playwright or Puppeteer : turn HTML into a PDF, take a screenshot, or read a page. A browser needs system libraries next to its npm package, so it runs in one of two places.
| Sandbox | Native process | |
|---|---|---|
| Where the code runs | In sandboxes of a group of its own, in Docker | On the machine of the server (runOnNativeProcess: true) |
| Where the browser is installed | In the Dockerfile of the group : built once, on every server | On each server, next to API Maker |
| Isolation | Your code and the browser stay apart from API Maker | None : the browser shares the server with API Maker |
| Set up by | An admin, in the settings of the custom API | The root user of each server |
A browser needs memory : plan 2 GB of RAM and 40 GB of disk for the server, and avoid many browsers at the same time.
Playwright in the sandbox¶
The sandboxes of an account have no browser : nothing is installed that the account did not add. Give the APIs which drive one a separate sandbox : a group with a Dockerfile which installs Chromium, and the package playwright in the same version. The other code of the account keeps its small image.
| Basic info : a group of sandboxes with Chromium | |
|---|---|
| |
- Saving the API builds the image of the group on every server, in the background : about a minute the first time, then never again while the Dockerfile and the packages stay the same. A call which comes before waits for it.
- Keep the version of the package and of
playwright installthe same : the package looks for the browsers of its own version. --no-sandbox: the container is the sandbox of Chromium.- More APIs with a browser name the same group and share its image and sandboxes.
Playwright on the native process¶
-
Sign in to the server as root (
whoamiprintsroot;sudo -sotherwise) and install Chromium with the libraries it needs : -
Add the package in the folder API Maker runs in (its
package.jsonand.envare there) : the runners of the machine find their packages there. -
Create a custom API and set
runOnNativeProcess: truein its configuration (the Basic Info section of the custom API). -
Use it in the code. This one writes a PDF from HTML into the
uploadsfolder :
import * as T from 'types';
import { join } from 'path';
const playwright = require('playwright');
async function main(g: T.IAMGlobal) {
const browser = await playwright.chromium.launch();
const page = await browser.newPage();
await page.setContent('<p style="font: 18px Arial">Hello from Playwright</p>', { waitUntil: 'load' });
await page.pdf({
path: join(__dirname, 'uploads', 'playwright.pdf'),
format: 'A4',
printBackground: true,
margin: { top: '20mm', right: '20mm', bottom: '20mm', left: '20mm' },
});
await browser.close();
// uploads/playwright.pdf is on the server : upload it to a storage provider or send it as a download.
return 'pdf created on the server';
}
module.exports = main;
Every server
What is installed by hand lives on one server : repeat both steps on every server of the cluster, and on each new one. The sandbox has no such step : its image is built on every server from the settings.
Puppeteer on the native process¶
-
Sign in to the server as root and install the libraries of Chromium (Ubuntu ; for other systems see the troubleshooting page of Puppeteer) :
apt update && apt install -y ca-certificates fonts-liberation libatk1.0-0t64 libatk-bridge2.0-0t64 \ libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libglib2.0-0 \ libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 \ libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 \ libxss1 libxtst6 libasound2t64 lsb-release wget xdg-utils -
Add the package in the folder API Maker runs in (its
package.jsonand.envare there) : the runners of the machine find their packages there. -
Set
runOnNativeProcess: truein the configuration of the custom API, and use it :
import * as T from 'types';
import { join } from 'path';
const puppeteer = require('puppeteer');
async function main(g: T.IAMGlobal) {
const browser = await puppeteer.launch({ headless: 'new', args: ['--no-sandbox', '--disable-setuid-sandbox'] });
const page = await browser.newPage();
await page.setContent('<p>Hello, this paragraph is converted to a PDF using Puppeteer.</p>', { waitUntil: 'load' });
await page.pdf({ path: join(__dirname, 'uploads', 'puppeteer.pdf'), format: 'A4', printBackground: true });
await browser.close();
// uploads/puppeteer.pdf is on the server : upload it to a storage provider or send it as a download.
return 'ok';
}
module.exports = main;
Good to know¶
- Write the files you want to send into the
uploadsfolder and return__am__downloadFilePath: see Download file. Upload them to a storage service instead when they must be kept. - Close the browser in every path of the code, errors included : an open browser keeps its memory.
- The native process runs TypeScript with the
nodeof the server : Node.js must be installed there, on every server. - Code on the native process is not isolated from the server : prefer the sandbox when you can, and keep the native process for the APIs which need it.