注意:这不是问题25363635的重复我正在使用php-fpm + apache mod_fcgi +代理(见下文)。 connection_aborted() 和ignore_user_abort() 在我的情况下不起作用。 TLDR 是如何让它们与我的设置一起工作 - fcgi+proxy。
我正在使用服务器发送事件创建日志代理。我正在使用这些标题:
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: close');
然后我通过套接字接收日志行并像这样输出它们:
foreach($lines as $line) {
echo "data: $line\n\n";
}
flush();
问题是当浏览器关闭时(也尝试使用curl),php 脚本继续运行。这需要内存并且还阻止打开新的服务器套接字来接收日志。
我正在使用 php-fpm (8.1) 和 fcgi 以及 apache 2.4。
Apache 配置看起来像这样:
<FilesMatch "\.php$">
SetHandler "proxy:unix:/usr/local/php81/var/run/website.sock|fcgi://localhost/"
</FilesMatch>
<Proxy "fcgi://localhost/">
ProxySet timeout=600
</Proxy>
是否可以让 php 脚本在浏览器关闭连接时退出,或者找到另一种(相对简单的)方法将日志实时转发到浏览器。我不想编写网络套接字服务器或安装额外的软件,除非真的没有其他办法。我也喜欢将长轮询用于其他目的的想法。但是,如果脚本一直挂在后台而不知道浏览器关闭了连接,它最终将填满允许的 fpm 进程数量,并且服务器将不再响应。
由于某种原因,当 max_execution_time 到来时,进程不会退出。它永远持续下去。这可能是由于我如何使用套接字接收日志:
<?php
$host = "0.0.0.0";
$port = 1234;
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: close');
echo "data: creating socket. pid ".getmypid().", time limit: ".ini_get('max_execution_time')."\n\n";
flush();
$socket = socket_create(AF_INET, SOCK_STREAM, 0);
if(empty($socket)) {
$error = socket_strerror(socket_last_error());
die("data: could not create socket: $error\n\n");
}
// Set the SO_REUSEADDR option
if (!socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1)) {
die('data: socket_set_option() failed: ' . socket_strerror(socket_last_error($this->socket))."\n\n");
}
$success = @socket_bind($socket, $host, $port);
if(!$success) {
$error = socket_strerror(socket_last_error());
die("data: could not bind: $error\n\n");
}
echo "data: listening for connections\n\n";
flush();
socket_listen($socket);
$clientSocket = socket_accept($socket);
echo "data: got a connection\n\n";
flush();
socket_set_nonblock($clientSocket);
$read = [$clientSocket];
$write = null;
$except = null;
$buffer = "";
ignore_user_abort(false); // doesn't help
while(true) {
if (connection_aborted()) { // doesn't help either
break;
}
$changedSockets = $read;
$numChanged = socket_select($changedSockets, $write, $except, 0, 800*1000); // 5000 microseconds timeout
if($numChanged === false)
break;
if ($numChanged <= 0)
continue;
$input = socket_read($clientSocket, 16484);
if($input === false) { // script only quts when the connection with log client is broken
break;
}
$buffer .= $input;
$lines = explode("\n", $buffer);
if(count($lines) > 1) {
$buffer = array_pop($lines); //last line may be incomplete
foreach($lines as $line) {
echo "data: $line\n\n";
}
flush();
}
if (strpos($buffer, "\n") !== false) {
list($line, $buffer) = explode("\n", $buffer, 2);
if(empty($line))
echo "data: empty line\n\n";
echo "data: $line\n\n";
flush();
// fastcgi_finish_request();
}
}
socket_close($clientSocket);
socket_close($socket);
任何提示表示赞赏!
因此,作为解决方法,我在虚拟主机配置中执行了此操作:
SetHandler "proxy:unix:/usr/local/php81/var/run/xmlgen-php-fpm.sock|fcgi://localhost/"
操作 php81-cgi /cgi-bin/xmlgen/php81
SetHandler php81-cgi
因为我花了很多时间来解释为什么 CGI 在这里不起作用,如果你使用 suexec (apache 2.4 中的 SuexecUserGroup),php-cgi 必须位于文档根目录中(
suexec -V
将显示它),这是一个额外的提示它必须与 SuexecUserGroup 具有相同的用户/组。实现此目的的一种方法是放置一个包装脚本:
cat /var/www/cgi-bin/<vhostname>/php81
#!/bin/bash
PHP_CGI=/usr/local/php81/bin/php-cgi
export PHPRC="/webroot/picasse/xmlgen/etc/php.ini"
# this line irrelevant for CGI - you can skip it:
export PHP_FCGI_MAX_REQUESTS=10000
exec $PHP_CGI -c $PHPRC
并确保 chown
/
chgrp
文件具有正确的用户/组和
chmod +x
我很想跳过这种配置复杂性并摆脱缓冲(无论它发生在哪里)。所以我仍在等待答案如何配置apache,以便作为fcgi服务运行的php知道浏览器何时关闭连接。