Laravel通过exec运行命令时挂起,而没有将其发送到后台

时间:2018-10-14 19:41:47

标签: php laravel google-chrome pdf exec

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

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

我尝试了其他替代方法,都无济于事。我尝试使用Symfony's Process,它与Laravel捆绑在一起,但也失败了。我尝试使用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');

?>

从像php thefile.php这样的shell运行时,代码运行得很好,这意味着exec中的命令已启动,结束后便存在该文件;那就是我在Laravel上使用的确切代码,除了如上所述,当我将进程发送到后台时,它仅能正常工作。 有人可以在这里给我打个电话吗?谢谢

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

编辑:我也尝试过使用BrowserShot,但是BTW也失败了。 Browsershot提供了一种通过使用Process在后台与人偶进行交互并生成pdf文件的方法。即使它是一个外部程序,在我看来我得到的行为也不正常,即使请求花了10秒才能完成,我也应该能够获得下载,因为它同步执行了外部程序。但就我而言,它由于超时错误而失败

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

2 个答案:

答案 0 :(得分:1)

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

生产服务器是多线程的,因此我们应该没有问题。不能完全确定我是对的,但还是想发表评论。

答案 1 :(得分:0)

我并没有真正了解您的要求,因为您似乎已经了解,如果同步运行,执行长时间运行的任务(如创建快照)会阻止请求。使用其他软件,例如Puppeteer不会改变这一点。如果您的请求需要等待该过程的结果返回,那么使请求更快返回的唯一方法是加快任务本身。

因此,基本上只剩下两个选项:等待时间长(如果要同步执行任务)或异步执行请求/任务。后者可以通过两种方式实现:

  1. 使实际的HTTP请求在后台运行(使用Ajax),并使用加载指示器使用户耐心等待。这样,您仍然可以同步运行该过程,但是我不建议您这样做,因为您必须为ajax请求使用较长的超时时间,并且在某些情况下请求可能仍会超时(取决于服务器的工作负载)。
  2. 使用Laravel的功能并利用队列工作器在后台执行计算。快照生成完成后,可以使用以下三个选项之一返回结果:
    • 在客户端上使用轮询来查看结果是否可用。
    • 通过邮件或类似于用户的方式发送结果或指向结果的链接。
    • 使用通知系统来通知用户有关已完成的过程,并以某种方式返回结果(例如,将其获取或作为通知的一部分发送-有很多可用的选项)。完全符合我的描述的内置通知系统是Laravel Echo。收到通知您该过程已完成的通知后,您可以从服务器获取结果。

当前,Web应用程序和用户体验的标准是通知系统(第3点)的选项2。