基于Ubuntu20.04构建的playwright,基于node js脚本做接口
440
sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \
sed -i 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \
apt-get clean && apt-get update -y
apt-get install -y --no-install-recommends \
curl \
ca-certificates \
gnupg \
unzip \
wget \
fontconfig \
fonts-noto-cjk \
fonts-liberation \
libnss3 \
libatk1.0-0 \
libatk-bridge2.0-0 \
libcups2 \
libdrm2 \
libxkbcommon0 \
libxcomposite1 \
libxdamage1 \
libxfixes3 \
libxrandr2 \
libgbm1 \
libasound2 \
libpangocairo-1.0-0 \
libpango-1.0-0 \
libgtk-3-0 \
libxshmfence1 && \
rm -rf /var/lib/apt/lists/*
curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \
apt-get update -y && apt-get install -y nodejs && \
rm -rf /var/lib/apt/lists/* && \
npm config set registry https://registry.npmmirror.com
{
"name": "render-service",
"private": true,
"type": "module",
"dependencies": {
"express": "^4.19.2",
"playwright": "1.43.0"
}
}
if [ -f package.json ]; then npm install; fi && \
npx playwright install chromium --with-deps
| 说明 | 请求方式 | 地址 | 参数 | 响应 |
|---|---|---|---|---|
| 转pdf | POST | /render/pdf | <html源码> | result.pdf |
| 转PNG | POST | /render/png | <html源码> | result.png |
FROM ubuntu:22.04
LABEL authors="Jule"
# 核心修复1:配置阿里云源(转义符+语法规范)
RUN sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \
sed -i 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \
apt-get clean && apt-get update -y
# 核心修复2:安装基础依赖(单独RUN,避免命令串联错误)
RUN apt-get install -y --no-install-recommends \
curl \
ca-certificates \
gnupg \
unzip \
wget \
fontconfig \
fonts-noto-cjk \
fonts-liberation \
libnss3 \
libatk1.0-0 \
libatk-bridge2.0-0 \
libcups2 \
libdrm2 \
libxkbcommon0 \
libxcomposite1 \
libxdamage1 \
libxfixes3 \
libxrandr2 \
libgbm1 \
libasound2 \
libpangocairo-1.0-0 \
libpango-1.0-0 \
libgtk-3-0 \
libxshmfence1 && \
rm -rf /var/lib/apt/lists/*
# 核心修复3:安装Node.js(单独RUN,避免和基础依赖冲突)
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \
apt-get update -y && apt-get install -y nodejs && \
rm -rf /var/lib/apt/lists/* && \
# 配置国内npm源,解决npm install慢/失败
npm config set registry https://registry.npmmirror.com
# 核心修复4:工作目录+业务文件拷贝(规范执行顺序)
WORKDIR /app
COPY package.json ./
COPY server.js ./
# 核心修复5:安装npm依赖+Playwright(有package.json才执行,避免报错)
RUN if [ -f package.json ]; then npm install; fi && \
npx playwright install chromium --with-deps
# 业务环境变量
ENV BROWSER_WORKERS=2
ENV MAX_CONTEXTS=4
# 暴露端口
EXPOSE 3000
# 启动命令
CMD ["node", "server.js"]
{
"name": "render-service",
"private": true,
"type": "module",
"dependencies": {
"express": "^4.19.2",
"playwright": "1.43.0"
}
}
import express from "express";
import { chromium } from "playwright";
const app = express();
app.use(express.text({ limit: "10mb", type: "*/*" }));
const PORT = 3000;
const BROWSER_WORKERS = parseInt(process.env.BROWSER_WORKERS || "2");
const MAX_CONTEXTS = parseInt(process.env.MAX_CONTEXTS || "4");
/**
* Browser Pool
*/
const pool = [];
class BrowserWorker {
constructor(browser) {
this.browser = browser;
this.availableContexts = MAX_CONTEXTS;
}
}
async function initBrowserPool() {
console.log(`🚀 Starting ${BROWSER_WORKERS} Chromium browsers...`);
for (let i = 0; i < BROWSER_WORKERS; i++) {
const browser = await chromium.launch({
args: [
"--no-sandbox",
"--disable-dev-shm-usage",
"--disable-gpu"
]
});
pool.push(new BrowserWorker(browser));
console.log(`✅ Browser ${i} ready`);
}
}
/**
* Acquire a browser slot (blocking)
*/
async function acquireWorker() {
while (true) {
for (const worker of pool) {
if (worker.availableContexts > 0) {
worker.availableContexts--;
return worker;
}
}
await new Promise(r => setTimeout(r, 10));
}
}
function releaseWorker(worker) {
worker.availableContexts++;
}
/**
* Render PDF
*/
app.post("/render/pdf", async (req, res) => {
const html = req.body;
if (!html) {
return res.status(400).send("HTML content required");
}
const worker = await acquireWorker();
const context = await worker.browser.newContext();
const page = await context.newPage();
try {
await page.setContent(html, { waitUntil: "networkidle" });
const pdf = await page.pdf({ format: "A4" });
res.setHeader("Content-Type", "application/pdf");
res.setHeader("Content-Disposition", "inline; filename=result.pdf");
res.send(pdf);
} catch (err) {
console.error(err);
res.status(500).send("Render PDF failed");
} finally {
await page.close();
await context.close();
releaseWorker(worker);
}
});
/**
* Render PNG
*/
app.post("/render/png", async (req, res) => {
const html = req.body;
if (!html) {
return res.status(400).send("HTML content required");
}
const worker = await acquireWorker();
const context = await worker.browser.newContext({
viewport: { width: 1920, height: 1080 }
});
const page = await context.newPage();
try {
await page.setContent(html, { waitUntil: "networkidle" });
const image = await page.screenshot({ fullPage: true });
res.setHeader("Content-Type", "image/png");
res.send(image);
} catch (err) {
console.error(err);
res.status(500).send("Render PNG failed");
} finally {
await page.close();
await context.close();
releaseWorker(worker);
}
});
/**
* Health check
*/
app.get("/health", (_, res) => {
res.json({
status: "UP",
browsers: BROWSER_WORKERS,
maxContextsPerBrowser: MAX_CONTEXTS
});
});
/**
* Start server
*/
(async () => {
await initBrowserPool();
app.listen(PORT, () => {
console.log(`✅ Render service listening on :${PORT}`);
});
})();
Content type
Image
Digest
sha256:ea643a2b9…
Size
552.4 MB
Last updated
9 months ago
docker pull julezhou/playwright