在使用 Nightmare JS 的 AWS 上,导出为 PDF 可以使用一个端口,但不能使用另一个端口
On AWS using Nightmare JS, Export to PDF Works Using One Port But Not On Another
我在一个通过电子邮件将财务数据发送给用户的应用程序中具有导出为 PDF 的功能。我使用我的测试站点获得了此功能 100% 的工作,但是当我部署到我的生产站点时它没有工作。在 prod 网站上,用户通过电子邮件收到一个空白的 1kb 单页 PDF 文件(可以在 PDF 查看器中打开)。
这是我如何使用该功能的最低要求。我用这个 guide 让 Nightmare JS 在 Amazon Linux 上运行。
- 服务器导出收到
/export
调用
调用了一个子进程,它使用 xvfb-run
为 Nightmare JS 创建视觉帧缓冲区。
exec(`xvfb-run -a --server-args="-screen 0 1366x768x24" node
${path.join(__dirname, 'export.js')} ${process.env.TYPE} ${token}
${req.user.email}`)
在 export.js 过程中,Nightmare JS 从站点 运行 关闭的端口执行 HTTP 浏览器加载(测试站点:端口 3001,prod 站点: 3000).
Nightmare()
.viewport(1366, 768)
.goto(
`http://localhost:${process.env.PORT}/export_summary_1?
exportToken=${exportToken}`
)
.wait(3000)
.pdf()
.end()
.then(function(pdfBuffer) {
mailOptions.attachments = [{
filename: 'financial_model.pdf',
content: pdfBuffer
}]
email.send(mailOptions)
})
/export_summary_1
浏览器页面使用导出令牌调用另一个服务器来加载和显示财务数据。然后,如上面的代码所示,Nightmare 将页面捕获为 PDF,最后将此 PDF 通过电子邮件发送给用户。
我不认为我的 AWS 设置是罪魁祸首,因为导出到 PDF 系统的相关部分都是在 AWS 实例上本地完成的(例如,噩梦页面加载完成 http://localhost:[port]/export_summary_1
).
我确实有一个 HTTP->HTTPS 重定向,但我绕过了 Nightmare 页面加载和加载页面的服务器调用以检索财务数据。
尽管我不认为问题出在我的 AWS 设置上,但为了完整起见,这里是我关于 AWS 和端口设置 的注释。
- 在我的实例使用的安全组中启用了端口 3000 和 3001。
- 负载平衡器设置
- 端口 80 和 443 侦听器转发到 'ec2-instance-targets',它使用端口 3000 将 AWS 实例作为注册目标。
- 端口 3001 侦听器转发到 'ec2-devInstance-targets',它使用端口 3001 将 AWS 实例作为注册目标。
此外,AWS 实例是 'ec2-instance-targets' 视图中的 'unhealthy' 注册目标('ec2-instance-targets' 用于产品站点)。这是因为,我相信,'ec2-instance-targets' 目标组的端口设置为 80,而注册目标的端口设置为 3000。
但是,产品站点仍然有效,正如我之前所说,这些 AWS/load 平衡器设置似乎不是问题来源,因为导出到 PDF 系统包含在 AWS 实例中,因为所有意图和目的。
不过,我确实计划在几个小时内尝试解决上述问题,那时对我来说是晚上。
此外,我还注释掉了 Nightmare 加载页面对服务器的金融数据调用。现在,导出为 PDF 的加载页面上只有 'hello world' 文本。问题仍然存在。
如何才能在产品站点上使用导出为 PDF 的功能?
tldr; NightmareJS加载了一个空白页,导出为PDF,因为有sub-calls到https://localhost:3000/bundle.js
和https://localhost:3000/styles/style.css
,负责所有呈现的内容,但无法连接(没有设置 HTTPS 服务器,因为 HTTPS 是通过 AWS 负载均衡器实现的)。
在我的问题中,我提到我认为 export-to-PDF 处理的相关部分全部在本地完成。实际上就是这种情况,但是还有进一步的 HTTP->HTTPS 重定向正在进行。下面是 HTTP->HTTPS 重定向代码的样子:
app.use(function(req, res, next) {
if(!req.secure &&
req.get('X-Forwarded-Proto') !== 'https' &&
// above: achieves HTTP->HTTPS redirecting using AWS load balancer and EC2 instance
!/export_summary/.test(req.url)) {
res.redirect('https:' + req.hostname + req.url)
} else {
next()
}
})
// NOTE: all code above is commented out on test site, and uncommented for production site
这里Whosebug page解释了如何使用 AWS 负载均衡器和 EC2 实例实现 HTTP->HTTPS 重定向,以及上面代码中使用的内容。
当我最初将 export-to-PDF 功能部署到产品站点时,我收到一个 NightmareJS 错误,通知我无法访问 https://localhost:3000/export_summary
。因此,我插入了 !/export_summary/.test(req.url)
条件,以便 NightmareJS 对 export_summary
页面的 HTTP 调用不会被重定向到 HTTPS 调用。
上面的代码块也作用于 NightmareJS 的 sub-calls 加载 export_summary
页面。因此,资源的两个主要 sub-calls http://localhost:3000/bundle.js
和 http://localhost:3000/styles/style.css
被重定向到它们的 HTTPS 对应项。 (注意:bundle.js 调用是 <script src='bundle.js'></script>
在为 /export_summary
服务的 HTML 文件中。
由于没有在 AWS 实例上设置实际的 HTTPS 服务器,那些 sub-calls 无法连接(客户端会将此报告为 'connection refused' 错误消息)。该应用程序的 HTTPS 是通过 AWS 负载均衡器实现的。
这两个资源负责为应用程序呈现的全部内容。因此,没有它们,加载的页面是白色和空白的,如导出的 PDF 所示。
另外,正如上面代码块中的注释中提到的,HTTP->HTTPS 重定向代码在测试站点上被注释掉了。这解释了我如何看到该功能在测试站点上按预期工作,因为麻烦的中间件不是处理的一部分。
解决方案
我更新了绕过 HTTP->HTTPS 重定向的条件,以包括相关的 sub-calls:
!/(export_summary)|(exportPrivate)|(bundle\.js)|(style\.css)|(\.png)|(\.ttf)|(\.woff)/.test(req.url))
注意:还需要绕过 PNG、TTF 和 WOFF 资源调用,因为完整、精美的 PDF 需要它们。
最后,我认为这里没有安全风险,尽管我认为当然可以进行安全改进。
- bundle.js、style.css 和 image/font 文件中未保存机密数据。
- 对于
/export_summary
调用,服务器响应 index.html 用于部署应用程序的文件(那里也没有机密数据)。
- 绕过对包含 'exportPrivate' 子字符串的端点的调用是为了 NightmareJS 调用以加载导出为 PDF 的用户数据。由于允许跨 HTTP 调用,因此在任何情况下都不会在 Internet 上使用此调用。它只允许在服务器上使用无头客户端浏览器,例如 NightmareJS。
欢迎就改进解决方案的安全方面提出任何建议。
我在一个通过电子邮件将财务数据发送给用户的应用程序中具有导出为 PDF 的功能。我使用我的测试站点获得了此功能 100% 的工作,但是当我部署到我的生产站点时它没有工作。在 prod 网站上,用户通过电子邮件收到一个空白的 1kb 单页 PDF 文件(可以在 PDF 查看器中打开)。
这是我如何使用该功能的最低要求。我用这个 guide 让 Nightmare JS 在 Amazon Linux 上运行。
- 服务器导出收到
/export
调用 调用了一个子进程,它使用
xvfb-run
为 Nightmare JS 创建视觉帧缓冲区。exec(`xvfb-run -a --server-args="-screen 0 1366x768x24" node ${path.join(__dirname, 'export.js')} ${process.env.TYPE} ${token} ${req.user.email}`)
在 export.js 过程中,Nightmare JS 从站点 运行 关闭的端口执行 HTTP 浏览器加载(测试站点:端口 3001,prod 站点: 3000).
Nightmare() .viewport(1366, 768) .goto( `http://localhost:${process.env.PORT}/export_summary_1? exportToken=${exportToken}` ) .wait(3000) .pdf() .end() .then(function(pdfBuffer) { mailOptions.attachments = [{ filename: 'financial_model.pdf', content: pdfBuffer }] email.send(mailOptions) })
/export_summary_1
浏览器页面使用导出令牌调用另一个服务器来加载和显示财务数据。然后,如上面的代码所示,Nightmare 将页面捕获为 PDF,最后将此 PDF 通过电子邮件发送给用户。
我不认为我的 AWS 设置是罪魁祸首,因为导出到 PDF 系统的相关部分都是在 AWS 实例上本地完成的(例如,噩梦页面加载完成 http://localhost:[port]/export_summary_1
).
我确实有一个 HTTP->HTTPS 重定向,但我绕过了 Nightmare 页面加载和加载页面的服务器调用以检索财务数据。
尽管我不认为问题出在我的 AWS 设置上,但为了完整起见,这里是我关于 AWS 和端口设置 的注释。
- 在我的实例使用的安全组中启用了端口 3000 和 3001。
- 负载平衡器设置
- 端口 80 和 443 侦听器转发到 'ec2-instance-targets',它使用端口 3000 将 AWS 实例作为注册目标。
- 端口 3001 侦听器转发到 'ec2-devInstance-targets',它使用端口 3001 将 AWS 实例作为注册目标。
此外,AWS 实例是 'ec2-instance-targets' 视图中的 'unhealthy' 注册目标('ec2-instance-targets' 用于产品站点)。这是因为,我相信,'ec2-instance-targets' 目标组的端口设置为 80,而注册目标的端口设置为 3000。
但是,产品站点仍然有效,正如我之前所说,这些 AWS/load 平衡器设置似乎不是问题来源,因为导出到 PDF 系统包含在 AWS 实例中,因为所有意图和目的。
不过,我确实计划在几个小时内尝试解决上述问题,那时对我来说是晚上。
此外,我还注释掉了 Nightmare 加载页面对服务器的金融数据调用。现在,导出为 PDF 的加载页面上只有 'hello world' 文本。问题仍然存在。
如何才能在产品站点上使用导出为 PDF 的功能?
tldr; NightmareJS加载了一个空白页,导出为PDF,因为有sub-calls到https://localhost:3000/bundle.js
和https://localhost:3000/styles/style.css
,负责所有呈现的内容,但无法连接(没有设置 HTTPS 服务器,因为 HTTPS 是通过 AWS 负载均衡器实现的)。
在我的问题中,我提到我认为 export-to-PDF 处理的相关部分全部在本地完成。实际上就是这种情况,但是还有进一步的 HTTP->HTTPS 重定向正在进行。下面是 HTTP->HTTPS 重定向代码的样子:
app.use(function(req, res, next) {
if(!req.secure &&
req.get('X-Forwarded-Proto') !== 'https' &&
// above: achieves HTTP->HTTPS redirecting using AWS load balancer and EC2 instance
!/export_summary/.test(req.url)) {
res.redirect('https:' + req.hostname + req.url)
} else {
next()
}
})
// NOTE: all code above is commented out on test site, and uncommented for production site
这里Whosebug page解释了如何使用 AWS 负载均衡器和 EC2 实例实现 HTTP->HTTPS 重定向,以及上面代码中使用的内容。
当我最初将 export-to-PDF 功能部署到产品站点时,我收到一个 NightmareJS 错误,通知我无法访问 https://localhost:3000/export_summary
。因此,我插入了 !/export_summary/.test(req.url)
条件,以便 NightmareJS 对 export_summary
页面的 HTTP 调用不会被重定向到 HTTPS 调用。
上面的代码块也作用于 NightmareJS 的 sub-calls 加载 export_summary
页面。因此,资源的两个主要 sub-calls http://localhost:3000/bundle.js
和 http://localhost:3000/styles/style.css
被重定向到它们的 HTTPS 对应项。 (注意:bundle.js 调用是 <script src='bundle.js'></script>
在为 /export_summary
服务的 HTML 文件中。
由于没有在 AWS 实例上设置实际的 HTTPS 服务器,那些 sub-calls 无法连接(客户端会将此报告为 'connection refused' 错误消息)。该应用程序的 HTTPS 是通过 AWS 负载均衡器实现的。
这两个资源负责为应用程序呈现的全部内容。因此,没有它们,加载的页面是白色和空白的,如导出的 PDF 所示。
另外,正如上面代码块中的注释中提到的,HTTP->HTTPS 重定向代码在测试站点上被注释掉了。这解释了我如何看到该功能在测试站点上按预期工作,因为麻烦的中间件不是处理的一部分。
解决方案
我更新了绕过 HTTP->HTTPS 重定向的条件,以包括相关的 sub-calls:
!/(export_summary)|(exportPrivate)|(bundle\.js)|(style\.css)|(\.png)|(\.ttf)|(\.woff)/.test(req.url))
注意:还需要绕过 PNG、TTF 和 WOFF 资源调用,因为完整、精美的 PDF 需要它们。
最后,我认为这里没有安全风险,尽管我认为当然可以进行安全改进。
- bundle.js、style.css 和 image/font 文件中未保存机密数据。
- 对于
/export_summary
调用,服务器响应 index.html 用于部署应用程序的文件(那里也没有机密数据)。 - 绕过对包含 'exportPrivate' 子字符串的端点的调用是为了 NightmareJS 调用以加载导出为 PDF 的用户数据。由于允许跨 HTTP 调用,因此在任何情况下都不会在 Internet 上使用此调用。它只允许在服务器上使用无头客户端浏览器,例如 NightmareJS。
欢迎就改进解决方案的安全方面提出任何建议。