Продолжить php скрипт после закрытия соединения
Я пытаюсь продолжить PHP Script после закрытия страницы / соединения.
Пользователи будут опрашивать скрипт каждые 1 час, я хочу вернуть вывод json и продолжить скрипт в фоновом режиме. Я использую общий хост и не могу использовать задание cron.
Вот что я попробовал.
ob_start();
ignore_user_abort();
echo "JSON_OUTPUT GOES HERE";
$ob_length = ob_get_length();
header("Content-Type : text/plain",TRUE);
header("Content-Length : $ob_length",TRUE);
header("Connection : Close",TRUE);
flush();
ob_flush();
ob_end_flush();
sleep(3);
echo "You cant see me..";
exit();
Я использую Codeigniter Framework, но он не работает на моем живом сервере. Это ждет 3 секунды и затем выводит You cant see me..
тоже.
Пожалуйста, помогите мне.
Заметка
Проект размещен на общих хостах LINUX/WINDOWS/WAMP-SERVER.
3 ответа
После некоторого исследования я получил это, Иногда это может быть полезно для некоторых других.
function closeOutput($stringToOutput){
set_time_limit(0);
ignore_user_abort(true);
header("Connection: close\r\n");
header("Content-Encoding: none\r\n");
ob_start();
echo $stringToOutput;
$size = ob_get_length();
header("Content-Length: $size",TRUE);
ob_end_flush();
ob_flush();
flush();
}
Вы можете использовать его как
$outputContent = 'Contentent Goes Here...';
closeOutput( $outputContent );
sleep(5);
//do some background works ...
exit();
Из-за этой классной возможности, которую опубликовал Red, я написал небольшой служебный класс, который предоставляет очередь, в которую вы можете добавить Closures для последующего выполнения:
<?php
namespace Company\Project\Utilities;
/**
* Class ContinueUtility
*
* @package Company\Project\Utilities
*/
class ContinueUtility {
/**
* Stored tasks
* @var array
*/
static protected $tasks = array();
/** Constant for new line in HTTP Header */
const HEADER_NEW_LINE = "\r\n";
/**
* Add task (closure/function) to queue, with set arguments
*
* @param \Closure $task
* @param array $arguments
* @return void
*/
public static function addTask(\Closure $task, array $arguments = array()) {
self::$tasks[] = array(
'closure' => $task,
'arguments' => $arguments
);
}
/**
* Returns TRUE if tasks has been set, otherwise FALSE
*
* @return boolean
*/
public static function hasTasks() {
return !empty(self::$tasks);
}
/**
* Clear all previous set tasks
*
* @return void
*/
protected static function clearTasks() {
self::$tasks = array();
}
/**
* Execute all previous set tasks
*
* @return void
*/
protected static function executeTasks() {
foreach (self::$tasks as $task) {
call_user_func_array($task['closure'], $task['arguments']);
}
}
/**
* Execute and clear all previous set tasks
*
* @return void
*/
public static function executeAndClearTasks() {
self::executeTasks();
self::clearTasks();
}
/**
* Closes the HTTP connection to client immediately and outputs given string.
*
* @param string $instantOutput
* @return void
*/
public static function closeConnection($instantOutput = '') {
set_time_limit(0);
ignore_user_abort(TRUE);
header('Connection: close' . self::HEADER_NEW_LINE);
header('Content-Encoding: none' . self::HEADER_NEW_LINE);
ob_start();
echo $instantOutput;
$size = ob_get_length();
header('Content-Length: ' . $size, TRUE);
ob_end_flush();
ob_flush();
flush();
}
}
Вот как вы добавляете новые задачи в очередь:
use Company\Project\Utilities\ContinueUtility;
$a = 4;
$b = 5;
ContinueUtility::addTask(function($a, $b){
sleep(5);
$c = a + b;
file_put_contents(__DIR__ . '/whatever.log', $a . '+' . $b . '=' . $c);
}, array(
$a, $b
));
И вот как вы запускаете выполнение всех предыдущих добавленных задач:
ContinueUtility::closeConnection('Ready.');
ContinueUtility::executeAndClearTasks();
Во-первых, не используйте пробел после Connection
и раньше :
так должно быть Header: value
не Header : value
, Во-вторых, Connection: close
не заставляйте браузер прекращать получать текущий ответ и отображать пустую страницу. Вот в http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html главе 14.10 говорится: Connection: close in either the request or the response header fields indicates that the connection SHOULD NOT be considered 'persistent' (section 8.1) after the current request/response is complete
Итак, как вы можете попробовать, если ваш код работает:
ignore_user_abort();
header("Content-Type: text/plain; charset=UTF-8");
// just to try show following echo immediately, working depends on server configuration
while (@ob_end_flush());
echo date('Y-m-d H:i:s'), PHP_EOL;
echo "JSON_OUTPUT GOES HERE", PHP_EOL;
sleep(10); // 10 seconds so you can close browser tab before
// this file should be created after 10 seconds, even after you closed browser tab
// also check if permissions to write to __DIR__ are set for apache.
file_put_contents(__DIR__ . '/tmp.txt', "Text after 10 sec");
exit;
Откройте этот файл php в браузере и через 2-3 секунды закройте вкладку (даже если вы ничего не видите на экране), подождите немного дольше и проверьте, создан ли файл. Это работает на моей машине Linux.
Если вы используете PHP-FPM, более чистым и универсальным решением было бы просто выполнить fastcgi_finish_request();
Из документации PHP.net
Эта функция сбрасывает все данные ответа клиенту и завершает запрос. Это позволяет выполнять трудоемкие задачи, не оставляя соединение с клиентом открытым.
Вот как Symfony справляется со своими onTerminate
.