运行PHP时,您希望它立即将HTML返回浏览器,关闭连接(ish),然后继续处理...
当连接为HTTP / 1.1时,以下方法有效,但使用Apache 2.4.25且启用了mod_http2时,此方法无效,并且您的浏览器支持HTTP / 2(例如Firefox 52或Chrome 57)。
发生什么情况是Connection: close标头未发送。
function http_connection_close($output_html = '') {
apache_setenv('no-gzip', 1); // Disable mod_gzip or mod_deflate
ignore_user_abort(true);
// Close session (if open)
while (ob_get_level() > 0) {
$output_html = ob_get_clean() . $output_html;
}
$output_html = str_pad($output_html, 1023); // Prompt server to send packet.
$output_html .="
"; // For when the client is using fgets()
header('Connection: close');
header('Content-Length: ' . strlen($output_html));
echo $output_html;
flush();
}
http_connection_close('...');
// Do stuff...
?>
有关此问题的类似方法,请参见:
尽早关闭连接
关闭连接后继续处理
连接关闭后继续php脚本
关于为何删除connection标头的原因,nghttp2库的文档(由Apache使用)指出:
https://github.com/nghttp2/nghttp2/blob/master/doc/programmers-guide.rst
HTTP/2 prohibits connection-specific header fields. The
following header fields must not appear:"Connection"...
因此,如果我们不能告诉浏览器通过此标头关闭连接,我们如何使它起作用?
还是有另一种方式告诉浏览器它具有HTML响应所需的所有内容,并且不应该一直等待更多数据到达。
实际上,我怀疑您在第一句话中所作的初步概括性陈述是正确的...
为何如此?标头告诉浏览器关闭连接。
我毫不怀疑。。。但是,通常不希望某些服务器端脚本在将一些有效载荷发送给客户端之后继续运行。如果穷人负责后台任务处理,那会闻起来。
我不知道如何/如果可能的话,但是您实际需要的只是告诉客户不要在该特定响应中等待更多的输出。在HTTP / 1.x中,通过关闭连接很容易实现,但是HTTP / 2的全部要点是重用连接,因此现在有细微的差别。
请重新发表我的先前评论:应该可以通过明确声明Content-Length来实现,因为浏览器应该在读取了那么多输出后立即认为请求已完成。
这不是很典型,我很少使用它。例如一个用户要求生成一些东西,这需要几分钟的时间,因此我显示了一个"加载"页面,该页面每两秒钟刷新一次以显示其进度:-)
不幸的是,浏览器无法执行此操作...太多的网站设置了错误的Content-Length标头。
我第一次听说浏览器正在执行此操作……我不是说它是错误的(我不知道),但是您对此有多确定?你测试了吗:)
是的,上面的代码已经为此目的设置了Content-Length标头...它也需要Connection: close才能在HTTP / 1上工作。至于在HTTP / 2上应该如何工作,我不确定,因为我真的不希望关闭连接(例如,它可能想要请求其他资源)。
如果您使用php-fpm,并且您停止将PHP嵌入到Web服务器中(我很惊讶它仍在完成),那么实现echo your output here; fastcgi_finish_request(); code to be executed once output is sent to browser就是您所要做的。
必须承认它多年来我一直在使用Apache和PHP,并且还没有开始设置php-fpm(优点:更好的分离,缺点:更多的移动部件,优点:fastcgi_finish_request可能会解决此问题)。
@Mjh如果您想创建一个答案,说fastcgi_finish_request()将在php-fpm设置中工作,那么我可以将其标记为正确(我不希望使用mod_php使其正常工作)。
您可以使用特殊的子域通过HTTP / 1.1提供缓慢的PHP脚本。
您需要做的就是设置第二个VirtualHost,它使用Apache的Protocols指令以HTTP / 1.1进行响应:https://httpd.apache.org/docs/2.4/en/mod/core.html#protocols
该技术的最大优点是,在通过HTTP / 2流发送完所有其他内容之后,您的慢速脚本可以将一些数据发送到浏览器。
那是一个有趣的想法;有点棘手的解决方案,但是它应该适合那些需要使用mod_php的人...所以我给您+1,但是我仍然认为FastCGI是目前更好的答案...至于最大的优势提到,我不确定情况是否如此,因为单个HTTP / 2连接可用于所有资源(HTML的连接"已完成",并且在脚本执行实际完成之前用fastcgi_finish_request()发出了这样的信号) )。
我也使用fastcgi :-)
在这种情况下,这不应该是一个问题,或者当我切换时对我而言这不是问题……仅通过HTTP / 2调用fastcgi_finish_request()即可正常工作:-)
它可以工作,但是如果长时间操作后需要结果,这是一种简单的方法,无需断开到服务器的链接即可获得它,并且可以设置一个系统来检查结果是否可用。您可以将其视为脚本标签上的async属性:您使用相同的DocumentRoot和Protocols http / 1.1设置了一个域,那么您从http11.mydomain.com调用的所有内容都将是" async"。当然,您必须限制对该子域的访问,以防止漫游器和用户访问该子域的每个URL。如果您不需要结果,可以使用fastcgi_finish_request()。
也许我错过了一些东西,但是fastcgi_finish_request()并没有关闭连接,它只是告诉Web服务器(Apache)它已经完成了对原始请求的响应(但是PHP代码可以继续执行),这意味着Apache和浏览器将能够保持HTTP / 2连接保持打开状态……而使用单独的HTTP / 1子域,在原始情况下,我们将Connection: close标头与mod_php一起使用,意味着浏览器将关闭连接,如果需要额外的资源,则需要建立一个新的连接...这不是理想的选择。
我写了一个带有示例的长答复,解释了为什么我认为使用HTTP / 1.1和HTTP / 2有时有时有用:pastebin.com/MFYm2cv5简而言之:发送并行请求时,如果期望结果花费时间,则不阻止HTTP / 2流产生。
HTTP / 2的一大功能是它允许通过单个连接(称为多路复用)上传/下载多个内容,因此您说"来自浏览器的后续请求将被排队",而不是情况...您可能同时处理许多请求,只是mod_php出现了一个问题,您无法告诉Apache您已完成响应(同时仍在继续处理数据)。 :-)
在伪长任务期间,每个后续请求都排队,对吗?这不是为什么要使用fastcgi_finish_request()吗?在您的情况下,这无关紧要,但是如果您需要在完成长期任务后向浏览器发送内容,并在服务器执行过程中向服务器执行其他请求,该怎么办?
是的,后续请求没有排队,这是关于HTTP / 2的很酷的事情之一:-) ...如果浏览器需要等待结果(与原始问题不同),那么您(作为程序员)就什么都不做特别的,你让它花很长时间,并且不要使用fastcgi_finish_request();同时,在等待该事件的同时,Apache将能够响应来自浏览器的其他请求(注意,如果您使用PHP会话,则需要及早关闭会话,因为这会阻止PHP处理2该用户立即执行的操作-参考会话锁定)。
不过,对于我最初的问题,我发回了一个"正在加载"页面,该页面允许浏览器显示某些内容(无需等待),并且脚本继续自行执行;但是您是正确的,您无法通过该请求发送其他任何信息(浏览器认为它现在已包含所有内容)...但是您可以将结果写入临时文件(或数据库或PHP会话),并使浏览器保留刷新以查看结果何时出现(每次,如果结果还没有出现,请继续发送回"正在加载"页面,因此会在几秒钟后再次尝试)。
什么,真的吗?!因为那不是我的Apache安装上的工作方式。如果我从H2连接调用sleep(120),则我无法在两分钟内通过H2访问同一FQDN上的任何其他文件(除非将睡眠置于fastcgi_finish_request()之后)。即使从另一个选项卡。 Chrome,Safari,Firefox ...没关系,这就是我的服务器的行为方式:-/这就是为什么我假设您想要关闭连接:让其他文件通过H2流。
好吧,那是不对的。我可以很好地处理它,但是我暂时无法在我的计算机上创建演示。同时,您是否可以执行 ...作为其通常阻止后续请求的PHP会话...,并且如果使用php-fpm,您是否可以检查max_children和max_requests配置,可能设置为一次只处理一个请求:-)
以防万一有人遇到相同的问题:我通过从Apaches Prefork MPM迁移到Event MPM来解决了这个问题。
如何将HTTP响应返回给用户并恢复PHP处理
仅当Web服务器通过FastCGI协议与PHP通信时,此答案才有效。
要将回复发送给用户(Web服务器)并在后台继续处理而不涉及OS调用,请调用fastcgi_finish_request()函数。
例:
echo 'This is a heading'; // Output sent
fastcgi_finish_request(); //"Hang up" with web-server, the user receives what was echoed
while(true)
{
// Do a long task here
// while(true) is used to indicate this might be a long-running piece of code
}
寻找什么
即使用户确实收到了输出,php-fpm子进程也将很忙,无法接受新的请求,直到完成处理此长时间运行的任务为止。
如果所有可用的php-fpm子进程都处于繁忙状态,则您的用户将遇到挂起页面。请谨慎使用。
nginx和apache服务器都知道如何处理FastCGI协议,因此不需要将apache服务器换成nginx。
感谢@Mjh-并确认一下,我之前使用的是mod_php,但似乎没有办法使它关闭连接。我在原始问题中描述的常见方法依赖于Connection标头发送给浏览器,当Apache使用nghttp2库时,HTTP / 2中不再出现这种标头,因此浏览器只是等待服务器关闭连接(因为它本身不信任Content-Length头)。