首页 > 解决方案 > 通过 exec 运行命令而不将其发送到后台时,Laravel 挂起

问题描述

我有一个奇怪的问题,我已经坚持了几天了。我正在尝试使用 chrome headless 和这个命令在 Laravel 应用程序中生成 pdf
google-chrome --headless --disable-gpu --print-to-pdf=outputfile.pdf http://localurl/pdf-html

该命令基本上以无头模式打开 chrome,导航到给定的 url 并将其打印为 pdf,将文件保存在指定位置。此命令在我的系统 shell 中运行时运行良好(我使用的是 Ubuntu 18.04)。现在,当尝试从 Laravel 控制器运行相同的命令时出现了我的问题,我尝试了 exec、shell_exec、system 和 passthru,都给了我同样的问题。如果我在不重定向输出的情况下运行命令并在背景上运行进程,则添加>> tmpfile 2>&1 &到命令的末尾,然后请求挂起。在后台运行命令通常不会有问题,除了我需要完成命令才能将文件作为下载发送回客户端。通过在后台运行它,这基本上是异步执行它,我无法知道进程何时结束(或等到它结束)然后将文件作为响应的下载发送。

我尝试了其他替代方案,但无济于事。我尝试使用与 Laravel 捆绑在一起的 Symfony 进程,但它也失败了。我尝试过使用puppeteer,而不是运行 google-chrome 命令,而是使用带有 puppeteer 文档中代码的 node.js 脚本(顺便说一下,当直接在我的系统 shell 中运行时,它也可以工作),但是当从 Laravel 运行时会抛出一个导航超时错误异常。

最后,我使用以下代码创建了一个简单的 php 文件:

<?php

$chromeBinary = 'google-chrome';
$pdfRenderUrl = "http://localhost:8000/pdf-html";
$fileName = 'invoice.pdf';
$outputDirectory = "/path/to/my/file/" . $fileName;

$command = sprintf(
    '%s --headless --disable-gpu --print-to-pdf=%s %s',
    escapeshellarg($chromeBinary),
    escapeshellarg($outputDirectory),
    escapeshellarg($pdfRenderUrl)
);
exec( $command  );
echo ( file_exists("/path/to/my/file/" . $fileName) ? 'TRUE' : 'FALSE');

?>

当从 shell 运行时,代码运行得很好,比如php thefile.php打印 TRUE,这意味着 exec 中的命令已启动,并且在它结束后文件存在;这就是我在 Laravel 上使用的确切代码,只是如上所述,当我将进程发送到后台时它才有效。有人可以在这里给我留言吗?谢谢

编辑:@namoshek 感谢您的快速回复,如果我没有明确表示抱歉。问题是等待时间不长,也许我可以忍受。问题是 exec 永远不会完成,我最终不得不强制终止进程(也不是 exec,也不是任何其他替代方案,它们都永远完全冻结请求,除了 Process 因抛出 TimeoutException 而失败)。我正在使用邮递员查询端点。前端是一个 Angular 应用程序,这意味着发票下载请求最终将异步进行。此外,任务本身并不是一项长期运行的任务,事实上它完成得很快。对我来说,使用投票策略或通知系统似乎不是一个可行的解决方案。想象一个带有下载按钮的应用程序来下载一个简单的文档,您必须单击该按钮,然后等待该应用程序通过电子邮件(或其他方式)通知您文档已准备好。如果它是一个更复杂的过程,我可以理解它,但文档下载似乎是微不足道的。但是让我不知所措的是,为什么从 php 脚本运行任务可以按我的意愿(同步)工作,而我无法复制 laravel 控制器上的行为

编辑:我也尝试过使用BrowserShot,顺便说一句,它也失败了。Browsershot 提供了一种在幕后使用 Process 与 puppeteer 交互并生成 pdf 文件的方法。即使它是一个外部程序,在我看来我得到的行为仍然不正常,即使请求需要 10 秒才能完成,我也应该能够获得下载,因为它同步执行了外部程序。但在我的情况下,由于超时错误而失败

编辑:所以过了一会儿,我发现了服务器挂断的明显原因。问题是我使用的是工匠的开发服务器。最初,这对我来说似乎不是问题,但工匠似乎无法处理这种负载。在我正在实现的功能中,我正在对特定端点执行请求,我们称其为端点 1,以生成 pdf,此端点上的代码触发外部命令,当同步执行时,它意味着端点 1 中的代码正在等待外部命令完成。反过来,外部命令需要浏览到同一服务器上的端点 2,端点 2 包含一个 html 视图,其中包含要放在 pdf 上的内容,因为服务器仍在端点 1 上等待外部命令然后端点的返回2 反应迟钝,这显然会创建一个工匠的开发服务器无法处理的循环。问题是我进行了快速搜索,但没有发现任何迹象表明工匠的开发服务器存在缺陷。我将环境转移到 Apache 只是为了测试我的理论并且它有效,尽管应该注意该请求需要很长时间才能完成(大约 10-20 秒)。到目前为止,这似乎是关于为什么会发生这个问题的唯一合理解释。如果有人知道我可以如何提高此请求的性能,或者任何人都可以对原始问题提供更好的解释,我将不胜感激。我将环境转移到 Apache 只是为了测试我的理论并且它有效,尽管应该注意该请求需要很长时间才能完成(大约 10-20 秒)。到目前为止,这似乎是关于为什么会发生这个问题的唯一合理解释。如果有人知道我可以如何提高此请求的性能,或者任何人都可以对原始问题提供更好的解释,我将不胜感激。我将环境转移到 Apache 只是为了测试我的理论并且它有效,尽管应该注意该请求需要很长时间才能完成(大约 10-20 秒)。到目前为止,这似乎是关于为什么会发生这个问题的唯一合理解释。如果有人知道我可以如何提高此请求的性能,或者任何人都可以对原始问题提供更好的解释,我将不胜感激。

标签: phplaravelgoogle-chromepdfexec

解决方案


@hrivera 我在这里玩游戏有点晚了,但是关于您上次的编辑,我相信您几乎是正确的,但是我对此的想法是 Laravel 用于开发的 PHP 的内置服务器是单线程的。我遇到的问题是,由于线程已在使用中,因此无法加载页面中传递给 Chrome 的任何资产(CSS、js 等),因此它挂起。从 HTML 中删除任何资产解决了这个问题。

生产服务器是多线程的,所以我们应该没有问题。不完全确定我是对的,但无论如何都想发表评论。


推荐阅读