带有 Express 的 NodeJS 作为 HTML --> PDF 生成的服务器。能有效率吗?

NodeJS with Express as a server for HTML --> PDF generation. Can it be efficient?

我了解事件循环和 NodeJS 的单线程特性。鉴于此,您认为继续开发一个我们可以用来将 HTML 部分转换为 PDF 页面的 NodeJS/Express 服务是个好主意吗?

我们正在考虑 Puppeteer。我已经使用过它并且效果很好,但我不确定组织中的每个用户是否都必须等待事件循环,因为每个请求都会让流程一直忙到最后?

事件循环

事件循环负责 JavaScript 的 "single-threaded event-driven" 性质,这意味着需要执行的异步 (JavaScript) 代码将被放入队列中,然后一个接一个(通过循环)执行,而不是使用更经典的多线程方法。有关此主题的更多信息,我推荐 this great video explanation

事件循环与您的问题并没有真正的关系,因为大部分工作是在浏览器内异步发生的(而不是在 Node.js 运行时内)。这意味着您的 puppeteer 脚本大部分时间都会等待浏览器 return 结果。

考虑这样一个简单的行:

await browser.newPage();

这实际上是做什么的?它向浏览器(另一个进程中的运行)发送命令以打开一个页面。实际工作发生在浏览器内部,而不是在您的 Node.js 环境中。基本上所有的人偶操作功能也是如此。因此,"main work" 不会在您的 Node.js 环境中发生,因此事件循环与您的问题无关。

实施

您所描述的内容对于 puppeteer 和 Node.js 来说绝对可行。让我们考虑一下应该可以帮助您入门的示例代码:

const puppeteer = require('puppeteer');
const express = require('express');

const app = express();

app.get('/pdf', async (req, res) => { // Call /pdf?url=... to create a PDF of the provided URL
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.goto(req.query.url); // URL is given by the user
    const pdfBuffer = await page.pdf();

    // Respond with the PDF
    res.writeHead(200, {
        'Content-Type': 'application/pdf',
        'Content-Length': pdfBuffer.length
    });
    res.end(pdfBuffer);

    await browser.close();
});

app.listen(4000);

这将提供 API 来生成 URL 的 PDF。每个请求都会打开一个浏览器,打开一个新页面,导航到给定的 URL 和 return 一个 PDF 给用户。由于 JavaScript 的异步环境,这将完全并行发生。只要你的机器能处理并行打开浏览器的数量,你就没问题。

进一步改进

虽然给定的脚本有效,但您应该记住,由于许多打开的浏览器,太多的请求可能会很快消耗太多 memory/CPU,从而导致资源问题。为了改进实施,您希望使用一个 puppeteer 资源池来处理流量。为此,您可能想在线查看 puppeteer-cluster (disclaimer: I'm the author) which provides you with pool of browser instances and will allow to limit the number of running browsers. The library can handle this use case easily. There is actually an example 以了解这个确切的用例(但是,它会生成屏幕截图而不是 PDF)。