Мой клиент продолжает тестировать сценарий, который я сделал в своих мобильных браузерах … и одним из них является Opera «mini». В какой-то момент процесс должен выполняться в течение нескольких минут, и я не знаю, как с этим справиться в этом браузере. Сначала я хотел показать прогресс, но на данный момент я просто хочу каким-либо образом поставить браузер в режим ожидания, пока процесс не завершится, и получить уведомление об этом.
Вещи, которые я знаю или попробовал:
— Opera mini не поддерживает XMLHTTPRequest 2.0. Таким образом, вы не можете добиться прогресса таким образом.
— Он поддерживает таймеры, но только на ПЯТЬ секунд … так что вы не можете продолжать посылать запросы AJAX для проверки прогресса.
— Я попытался просто отправить один AJAX-запрос, чтобы выполнить задание, просто ожидая успешного обратного вызова, но, похоже, браузер отключил AJAX-запрос через долгое время.
— «Разве вы не можете разделить процесс на более мелкие части?» Вы бы сказали. Я делал это и перезагружал страницу для каждого подпроцесса … пока не осознал недостаток: если вы захотите вернуться с браузером, вы увидите 50 раз одну и ту же страницу.
Есть ли способ справиться с этим ?? Буду признателен за любую идею. Спасибо!
Разве вы не можете отправить частичный ответ пользователю, чтобы он продолжал видеть результат на своей веб-странице, пока процесс продолжает обрабатывать новые данные.
// Turn off output buffering
ini_set('output_buffering', 'off');
// Turn off PHP output compression
ini_set('zlib.output_compression', false);
//Flush (send) the output buffer and turn off output buffering
//ob_end_flush();
while (@ob_end_flush());
// Implicitly flush the buffer(s)
ini_set('implicit_flush', true);
ob_implicit_flush(true);
echo '
<table>
<thead>
<th>Url</th>
<th>Id</th>
<th>Class</th>
</thead>
<tbody>
';
ob_flush();
flush();
Вы можете Google для получения дополнительной информации о чанкованном ответе.
Недавно я столкнулся с подобной проблемой. Выполнение запроса Ajax будет иметь 2 проблемы. 1-й, навигатор будет заморожен. Во-вторых, большинство серверов выдаст ошибку через некоторое время после запуска сценария (обычно в некоторых случаях может быть увеличено 30 секунд).
Сначала я решил записать соответствующие данные в файлы и разделить процесс на более мелкие процессы, и, при каждом успешном ответе ajax, перезапускать следующий шаг до завершения задачи, сохраняя% complete в переменной сеанса при каждом запросе. , а также текущий шаг процессов для его восстановления, примерно так:
function stepOnTask(){
ajax.post("file.php", "action:doPartialTask", function(response){
if ( response.taskFinished ) alert("Task Done");
else{
showProgress(response.currentProgress);
stepOnTask();
}});
}
Но это было действительно интенсивно для моего настольного навигатора, и слишком часто он падал, не говоря уже о том, что вы ничего не могли сделать между тем, поэтому я полностью изменил его на другой подход, используя фоновые процессы в php и сохраняя соответствующую информацию (примерное время, запуск время и т. д.) в файле с именем pid запущенного процесса и каждые x секунд отправляйте в этот файл запрос для проверки и отображения прогресса.
Этот последний пункт немного длиннее, и я не буду публиковать код, если вы не попросите меня об этом, так как я не уверен, что это то решение, которое вы ищете.
Удачи.
РЕДАКТИРОВАТЬ
Стиль фонового процесса PHP
Class BackgroundScript{
public $win_path = "C:\\xampp\\htdocs\\www\\yourProject";
public $unix_path = "/home/yourFolder/yourProject.com";
public $script = NULL;
public $command = NULL;
public $pid = NULL;
public $start_time = NULL;
public $estimated_time = NULL;
public $ellapsed_time = NULL;
public $status_file = NULL;
public function __construct($script = ""){
$this->script = $script;
if ( self::get_os() == "windows" ){
$this->command = "start /b C:\\xampp\\php\\php.exe ".$this->win_path."\\".$this->script;
}else{
$this->command = "php ".$this->unix_path."/".$this->script;
}
}
public static function conPID($pid){
if ( file_exists(dirname(__FILE__)."/pids/".$pid.".json") ){
$bgScript = new BackgroundScript();
$process_info = json_decode(file_get_contents(dirname(__FILE__)."/pids/".$pid.".json"));
foreach ( $process_info as $key=>$val ){
$bgScript->$key = $val;
}
return $bgScript;
}else {
return false;
}
}
public static function get_os(){
if ( substr(php_uname(), 0, 7) == "Windows" ) return "windows";
else return "unix";
}
public function saveToFile(){
$path_to_pfolder = self::get_os()=="windows"? $this->win_path."\\pids":$this->unix_path."/pids";
if ( !( file_exists($path_to_pfolder) && is_dir($path_to_pfolder)) ){
mkdir($path_to_pfolder);
}
$fileHandler = fopen($path_to_pfolder."/".$this->pid.".json", "w");
$this->status_file = $path_to_pfolder."/".$this->pid.".json";
fwrite($fileHandler, json_encode($this));
fclose($fileHandler);
return $this->status_file;
}
public function removeFile(){
$path_to_pfolder = self::get_os()=="windows"? $this->win_path."\\pids":$this->unix_path."/pids";
unlink($path_to_pfolder."/".$this->pid.".json");
}
public function run($outputFile = '/dev/null'){
if ( self::get_os() == "windows" ){
$desc = array(
0 => array("pipe", "r"), // stdin es una tubería usada por el hijo para lectura
1 => array("pipe", "w"), // stdout es una tubería usada por el hijo para escritura
);
//proc_get_status devuelve el pid del proceso que lanza al proceso, o sea, del padre, y hay que hacer algo más para obtener el pid real del proceso que genera el archivo
$p = proc_open($this->command, $desc, $pipes);
$status = proc_get_status($p);
$ppid = $status["pid"];
//Ya tenemos el pid del padre, ahora buscamos el del último proceso hijo, que será el que acabamos de lanzar, y lo guardamos
$output = array_filter(explode(" ", shell_exec("wmic process get parentprocessid,processid | find \"$ppid\"")));
array_pop($output);
$this->pid = end($output);
//Cerramos el proceso padre, esto es lo que hará que no se nos quede pillada la aplicación mientras el "servidor" trabaja.
proc_close($p);
} else{
//En unix e ma facilico
$this->pid = trim(shell_exec(sprintf('%s > %s 2>&1 & echo $!', $this->command, $outputFile)));
}
$this->ellapsed_time = 0;
$this->start_time = date("Y-m-d H:i:s");
return $this->saveToFile();
}
public function isRunning()
{
try {
$result = shell_exec(sprintf('ps %d', $this->pid));
if(count(preg_split("/\n/", $result)) > 2) {
return true;
}
} catch(Exception $e) {}
return false;
}
public function kill(){
$this->removeFile();
if ( self::get_os() == "windows" ){
shell_exec(" taskkill /PID ".$this->pid);
} else{
// shell_exec(sprintf('kill %d 2>&1', $this->pid));
shell_exec(sprintf('kill '.$this->pid));
}
}
public function getPid(){
return $this->pid;
}
public static function getAll(){
$path_to_pfolder = self::get_os()=="windows"? self::$win_path."\\pids":self::$unix_path."/pids";
if ( !( file_exists($path_to_pfolder) && is_dir($path_to_pfolder)) ){
return array();
}
$archivos = scandir($path_to_pfolder);
$processes = array();
foreach ($archivos as $archivo){
if ( is_file($path_to_pfolder."/".$archivo) ){
$json = file_get_contents($path_to_pfolder."/".$archivo);
$info = json_decode($json);
$process = new BackgroundScript();
foreach ( $info as $key=>$val ){
$process->$key = $val;
}
$processes[] = $process;
}
}
return $processes;
}
public function view(){
$segundos_estimados = $this->estimated_time;
$segundos_transcurridos = time() - strtotime($this->start_time);
$segundos_restantes = max($segundos_estimados - $segundos_transcurridos, 0);
/*
$minutos_estimados = floor($segundos_estimados/60);
$segundos_estimados = $segundos_estimados - $minutos_estimados*60;
$minutos_restantes = floor($segundos_restantes/60);
$segundos_restantes = $segundos_restantes - $minutos_restantes*60;
*/
$estimado = date("i:s", strtotime("1983-09-23 00:00:00")+$segundos_estimados);
$restante = date("i:s", strtotime("1983-09-23 00:00:00")+$segundos_restantes);
if (!$segundos_estimados){
$html="<a>".$this->nombre_diario."<!--<br>Tiempo Estimado: <span class='estimado'>Calculando</span>-->
<br>Tiempo Restante: <span class='restante' data-time='".$segundos_restantes."'>Calculando</span></a>";
}elseif (!$segundos_transcurridos){
$html="<a>".$this->nombre_diario."<!--<br>Tiempo Estimado: <span class='estimado'>Guardando</span>-->
<br>Tiempo Restante: <span class='restante' data-time='".$segundos_restantes."'>Guardando</span></a>";
}else{
$html="<a>".$this->nombre_diario."<!--<br>Tiempo Estimado: <span class='estimado'>".$estimado."</span>-->
<br>Tiempo Restante: <span class='restante' data-time='".$segundos_restantes."'>".$restante."</span></a>";
}
return $html;
}
}
Хорошо, я понимаю, что код может выглядеть немного плохо, но он работает.
Теперь я покажу вам, как я его использую, вы должны адаптировать его к своему стилю.
У меня есть файл controller.php, который обрабатывает все действия в моем проекте, который выглядит примерно так:
if (isset($_POST) && isset($_POST["action"]) ) $action= $_POST["action"];
else $action= $argv[1];
switch ($action) {
case "performTask1":{
task1();
}
break;
case "performTask2":{
task2();
}
break;
case "performTask2inBackground":
{
$process = new BackgroundScript("controller.php performTask2");
$response["file_with_info"] = $process->run();
}
break;
echo json_encode($response);
}
И это все.
Конечно, в начале класса вы должны изменить win_path и unix_path, чтобы они соответствовали пути вашей машины к проекту. Я использую их оба, поэтому моя локальная тестовая среда и реальная версия сервера работают одинаково. Нет версии для Mac: P (надеюсь, она вам не нужна).
Следует также отметить, что в конструкторе вам, возможно, придется изменить путь, по которому строится переменная «команда», если ваша папка php находится по другому пути.
В корне проекта будет создан каталог с именем «pid» для сохранения файлов с информацией, имеющей имя {pid_of_the_process} .json. Пожалуйста, обратите внимание, что вы должны заполнить этот файл полезной информацией в вашем процессе, если вы этого не сделаете, он не будет иметь полезной информации.
Правильный способ сделать это в вашем скрипте сделать что-то вроде этого:
...
do{
doLotsOfThings();
$bgScript= BackgroundScript::conPID(getmypid());
$bgScript->estimated_time = recalculate_estimated_time();
$bgScript->ellapsed_time = recalculate_remaining_time();
$bgScript->saveToFile();
} while($whatever)
//END
$process->kill();
Чтобы получить в любой момент информацию о запущенных процессах для любых целей, которые вы можете использовать BackgroundScript::getAll();
чтобы показать снимок предполагаемого оставшегося времени для процессов, например, поэтому оставил метод «просмотра», который может быть бесполезен для вас, но это то, что я использую, чтобы получить статус и показать пользователю оставшееся время по запросу.
В целях отладки я предлагаю вам найти файл журнала ошибок php, очень необходимый, так как у вас не будет прямой обратной связи с браузером, и помните, что вы можете просто вставить сгенерированную команду в вашу консоль и запустить процесс, если хотите из первых рук информация о том, что происходит.
Наконец, я хотел бы поблагодарить @FlorianEckerstorfer, чья библиотека фоновых процессов помогла мне разработать решение, которое я разместил здесь.
Если вам не нужен ответ сервера, ваша страница может попытаться загрузить изображение размером 1x1px. Это IMG скрипт PHP, который возвращает это IMG, а затем сбросить соединение. Но с ignore_user_abort (true) скрипт все равно может продолжать работать.