тесная связь ранних


Я пытаюсь сделать вызов AJAX (через JQuery), который инициирует довольно длительный процесс. Я бы хотел, чтобы скрипт просто отправил ответ, указывающий, что процесс запущен, но JQuery не вернет ответ, пока скрипт PHP не будет запущен.

Я пробовал это с заголовком "закрыть" (ниже), а также с буферизацией вывода; ни один из них не работает. Какие-нибудь догадки? или это то, что мне нужно сделать в jQuery?

<?php

echo( "We'll email you as soon as this is done." );

header( "Connection: Close" );

// do some stuff that will take a while

mail( 'dude@thatplace.com', "okay I'm done", 'Yup, all done.' );

?>
19 85

19 ответов:

Следующая страница руководства PHP (ВКЛ. user-notes) предлагает несколько инструкций о том, как закрыть TCP-соединение с браузером без завершения PHP-скрипта:

предположительно, это требует немного больше, чем отправка закрыть заголовок.


OP затем подтверждает:да, это сделал трюк: указывая на пользователя-Примечание #71172 (Nov 2006) скопировать здесь:

закрытие соединения с браузером пользователей при сохранении вашего PHP-скрипта было проблемой с [PHP] 4.1, когда поведение register_shutdown_function() был изменен таким образом, что он не будет автоматически закрывать соединение пользователей.

sts в mail dot xubion dot hu опубликовал оригинальное решение:

<?php
header("Connection: close");
ob_start();
phpinfo();
$size = ob_get_length();
header("Content-Length: $size");
ob_end_flush();
flush();
sleep(13);
error_log("do something in the background");
?>

, который работает, пока вы не замените phpinfo() на echo('text I want user to see'); в этом случае заголовки не отправляются!

решение состоит в том, чтобы явно отключить буферизацию вывода и очистить буфер перед отправкой информации заголовка. Пример:

<?php
ob_end_clean();
header("Connection: close");
ignore_user_abort(true); // just to be safe
ob_start();
echo('Text the user will see');
$size = ob_get_length();
header("Content-Length: $size");
ob_end_flush(); // Strange behaviour, will not work
flush(); // Unless both are called !
// Do processing here 
sleep(30);
echo('Text user will never see');
?>

просто потратил 3 часа, пытаясь понять это, надеюсь, что это поможет кому-то :)

проверен в:

  • 7.5730.11 т. е.
  • Mozilla Firefox 1.81

позже, в июле 2010 года в обзоры ответаАрктика Огонь затем связал еще две заметки пользователя, которые были-последующие действия к одному выше:

необходимо отправить эти 2 заголовка:

Connection: close
Content-Length: n (n = size of output in bytes )

поскольку вам нужно знать размер вашего вывода, вам нужно будет буферизировать свой вывод, а затем сбросить его в браузер:

// buffer all upcoming output
ob_start();
echo "We'll email you as soon as this is done.";

// get the size of the output
$size = ob_get_length();

// send headers to tell the browser to close the connection
header("Content-Length: $size");
header('Connection: close');

// flush all output
ob_end_flush();
ob_flush();
flush();

// if you're using sessions, this prevents subsequent requests
// from hanging while the background process executes
if (session_id()) session_write_close();

/******** background process starts here ********/

кроме того, если ваш веб-сервер, используя автоматическое сжатие gzip на выходе (т. е. Apache с mod_deflate), это не будет работать, потому что фактический размер вывода изменяется, и длина содержимого больше не является точной. Отключите сжатие gzip конкретного скрипта.

для более подробной информации, посетите http://www.zulius.com/how-to/close-browser-connection-continue-execution

Полная версия:

ignore_user_abort(true);//avoid apache to kill the php running
ob_start();//start buffer output

echo "show something to user";
session_write_close();//close session file on server side to avoid blocking other requests

header("Content-Encoding: none");//send header to avoid the browser side to take content as gzip format
header("Content-Length: ".ob_get_length());//send length header
header("Connection: close");//or redirect to some url: header('Location: http://www.google.com');
ob_end_flush();flush();//really send content, can't change the order:1.ob buffer to normal buffer, 2.normal buffer to output

//continue do something on server side
ob_start();
sleep(5);//the user won't wait for the 5 seconds
echo 'for diyism';//user can't see this
file_put_contents('/tmp/process.log', ob_get_contents());
ob_end_clean();

вы можете использовать Fast-CGI с PHP-FPM, чтобы использовать fastcgi_end_request() функции. Таким образом, вы можете продолжать выполнять некоторую обработку, пока ответ уже отправлен клиенту.

вы найдете это в руководстве PHP здесь:FastCGI Process Manager (FPM); но эта функция конкретно не задокументирована в руководстве. Здесь отрывок из PHP-FPM: PHP FastCGI Process Manager Wiki:


fastcgi_finish_request ()

объем: функция php Категория: Оптимизация

эта функция позволяет ускорить реализацию некоторых запросов php. Ускорение возможно при наличии действий в процессе выполнения скрипта, которые не влияют на ответ сервера. Например, сохранение сессии в memcached может произойти после того, как страница сформирована и прошла на веб-сервер. fastcgi_finish_request() - это функция php, которая останавливает вывод ответа. Веб-сервер сразу же начинает передавать ответ" медленно и печально " клиенту, а php в то же время может делать много полезных вещей в контексте запроса, таких как сохранение сеанса, преобразование загруженного видео, обработка всех видов статистики и т. д.

fastcgi_finish_request() может вызвать выполнение функции отключения.

если у вас есть сервер Linux и корневой доступ, попробуйте это. Это самое простое решение, которое я нашел.

создайте новый каталог для следующих файлов и дайте ему полные разрешения. (Мы можем сделать его более безопасным позже.)

mkdir test
chmod -R 777 test
cd test

положить это в файл под названием bgping.

echo starting bgping
ping -c 15 www.google.com > dump.txt &
echo ending bgping

Примечание &. Команда ping будет выполняться в фоновом режиме, пока текущий процесс переходит к команду echo. Он будет пинговать www.google.com 15 раз, которые потребуется около 15 секунд.

сделайте его исполняемым.

chmod 777 bgping

положить это в файл под названием bgtest.php.

<?php

echo "start bgtest.php\n";
exec('./bgping', $output, $result)."\n";
echo "output:".print_r($output,true)."\n";
echo "result:".print_r($result,true)."\n";
echo "end bgtest.php\n";

?>

когда вы запрашиваете bgtest.php в вашем браузере, вы должны получить следующий ответ быстро, не дожидаясь около 15 секунд для завершения команды ping.

start bgtest.php
output:Array
(
    [0] => starting bgping
    [1] => ending bgping
)

result:0
end bgtest.php

команда ping теперь должна быть запущена на сервере. Вместо команды ping вы можете запустить PHP-скрипт:

php -n -f largejob.php > dump.txt &

надеюсь, что это помогает!

вот модификация кода Timbo, которая работает с сжатием gzip.

// buffer all upcoming output
if(!ob_start("ob_gzhandler")){
    define('NO_GZ_BUFFER', true);
    ob_start();
}
echo "We'll email you as soon as this is done.";

//Flush here before getting content length if ob_gzhandler was used.
if(!defined('NO_GZ_BUFFER')){
    ob_end_flush();
}

// get the size of the output
$size = ob_get_length();

// send headers to tell the browser to close the connection
header("Content-Length: $size");
header('Connection: close');

// flush all output
ob_end_flush();
ob_flush();
flush();

// if you're using sessions, this prevents subsequent requests
// from hanging while the background process executes
if (session_id()) session_write_close();

/******** background process starts here ********/

лучшим решением является развилка фонового процесса. Это довольно прямо вперед на unix / linux:

<?php
echo "We'll email you as soon as this is done.";
system("php somestuff.php dude@thatplace.com >/dev/null &");
?>

вы должны посмотреть на этот вопрос для лучших примеров:

PHP выполняет фоновый процесс

вы можете попробовать сделать многопоточность.

вы можете создать скрипт, который делает системный вызов ( используя shell_exec), который вызывает PHP-двоичный файл со скриптом для выполнения вашей работы в качестве параметра. Но я не думаю, что это самый безопасный способ. Может быть, вы можете улучшить материал, укореняя процесс php и другие вещи

кроме того, есть класс в phpclasses, которые делают это http://www.phpclasses.org/browse/package/3953.html. но Я не знаю специфики реализации

Я на общем Хосте и fastcgi_finish_request настраивается для полного выхода из скриптов. Мне не нравится connection: close выход. С его помощью принудительно устанавливается отдельное соединение для последующих запросов, что требует дополнительных ресурсов сервера. Я прочитал Transfer-Encoding: cunkedСтатья В Википедии и узнал, что 0\r\n\r\n завершает ответ. Я не полностью протестировал это в разных версиях браузеров и устройствах, но он работает на всех 4 моих текущих браузерах.

// Disable automatic compression
// @ini_set('zlib.output_compression', 'Off');
// @ini_set('output_buffering', 'Off');
// @ini_set('output_handler', '');
// @apache_setenv('no-gzip', 1);

// Chunked Transfer-Encoding & Gzip Content-Encoding
function ob_chunked_gzhandler($buffer, $phase) {
    if (!headers_sent()) header('Transfer-Encoding: chunked');
    $buffer = ob_gzhandler($buffer, $phase);
    return dechex(strlen($buffer))."\r\n$buffer\r\n";
}

ob_start('ob_chunked_gzhandler');

// First Chunk
echo "Hello World";
ob_flush();

// Second Chunk
echo ", Grand World";
ob_flush();

ob_end_clean();

// Terminating Chunk
echo "\x30\r\n\r\n";
ob_flush();
flush();

// Post Processing should not be displayed
for($i=0; $i<10; $i++) {
    print("Post-Processing");
    sleep(1);
}

Joeri Sebrechts' ответ близко, но он уничтожает любое существующее содержимое, которое может быть буферизовано, прежде чем вы захотите отключить. Он не вызывает ignore_user_abort правильно, что позволяет скрипту завершиться преждевременно. diyism это хорошо, но не является общеприменимым. Например, у человека может быть больше или меньше выходных буферов, которые этот ответ не обрабатывает, поэтому он может просто не работать в вашей ситуации, и вы не будете знать, почему.

эта функция позволяет отключаться в любое время (пока заголовки еще не отправлены) и сохраняет контент, который вы создали до сих пор. Дополнительное время обработки по умолчанию не ограничено.

function disconnect_continue_processing($time_limit = null) {
    ignore_user_abort(true);
    session_write_close();
    set_time_limit((int) $time_limit);//defaults to no limit
    while (ob_get_level() > 1) {//only keep the last buffer if nested
        ob_end_flush();
    }
    $last_buffer = ob_get_level();
    $length = $last_buffer ? ob_get_length() : 0;
    header("Content-Length: $length");
    header('Connection: close');
    if ($last_buffer) {
        ob_end_flush();
    }
    flush();
}

Если вам тоже нужна дополнительная память, выделите ее перед вызовом этой функции.

Примечание для пользователей mod_fcgid (пожалуйста, используйте на свой страх и риск).

Быстрый Решение

принятый ответ Joeri Sebrechts действительно функциональной. Однако, если вы используете mod_fcgid вы можете обнаружить, что это решение не работает на свой собственный. Другими словами, когда flush функция называется соединение с клиентом не закрывается.

The FcgidOutputBufferSize параметр конфигурации mod_fcgid может быть виноват. Я нашел этот совет в:

  1. это ответ Трэверса Картера и
  2. это сообщение в блоге Seumas Mackinnon.

прочитав вышеизложенное, вы можете прийти к выводу, что быстрым решением было бы добавить строку (см. "пример виртуального хоста" в конце):

FcgidOutputBufferSize 0

в любом файле конфигурации Apache (например, httpd.conf), ваш FCGI конфигурационный файл (например, fcgid.conf) или в вашем файле виртуальных хостов (например, httpd-vhosts.conf).

в (1) выше упоминается переменная с именем "OutputBufferSize". Это старое название FcgidOutputBufferSize упомянутый в (2) (см. обновление заметок на веб-странице Apache для mod_fcgid).

Детали И Второе Решение

вышеуказанное решение отключает буферизацию, выполняемую mod_fcgid либо для всей сервер или для конкретного виртуального хоста. Это может привести к снижению производительности вашего сайта. С другой стороны, это может быть не так, поскольку PHP выполняет буферизацию самостоятельно.

в случае, если вы не хотите, чтобы отключить mod_fcgidбуферизация есть еще одно решение... вы можете заставить этот буфер для промывки.

приведенный ниже код делает именно это, опираясь на решение, предложенное Joeri Sebrechts:

<?php
    ob_end_clean();
    header("Connection: close");
    ignore_user_abort(true); // just to be safe
    ob_start();
    echo('Text the user will see');

    echo(str_repeat(' ', 65537)); // [+] Line added: Fill up mod_fcgi's buffer.

    $size = ob_get_length();
    header("Content-Length: $size");
    ob_end_flush(); // Strange behaviour, will not work
    flush(); // Unless both are called !
    // Do processing here 
    sleep(30);
    echo('Text user will never see');
?>

что добавленная строка кода по существу делает это заполнить mod_fcgiбуфер, таким образом заставляя его смыть. Число "65537" было выбрано потому, что значение по умолчанию FcgidOutputBufferSize переменная "65536", как указано в Веб-страница Apache для соответствующей директивы. Следовательно, вам может потребоваться соответствующим образом настроить это значение, если в вашей среде установлено другое значение.

Мое Окружение

  • WampServer 2.5
  • Apache 2.4.9
  • PHP 5.5.19 VC11, x86, Non Thread Safe
  • mod_fcgid / 2.3.9
  • Windows 7 Professional x64

Пример Виртуального Хоста

<VirtualHost *:80>
    DocumentRoot "d:/wamp/www/example"
    ServerName example.local

    FcgidOutputBufferSize 0

    <Directory "d:/wamp/www/example">
        Require all granted
    </Directory>
</VirtualHost>

это работает для меня

//avoid apache to kill the php running
ignore_user_abort(true);
//start buffer output
ob_start();

echo "show something to user1";
//close session file on server side to avoid blocking other requests
session_write_close();

//send length header
header("Content-Length: ".ob_get_length());
header("Connection: close");
//really send content, can't change the order:
//1.ob buffer to normal buffer,
//2.normal buffer to output
ob_end_flush();
flush();
//continue do something on server side
ob_start();
//replace it with the background task
sleep(20);

TL; DR ответ:

ignore_user_abort(true); //Safety measure so that the user doesn't stop the script too early.

$content = 'Hello World!'; //The content that will be sent to the browser.

header('Content-Length: ' . strlen($content)); //The browser will close the connection when the size of the content reaches "Content-Length", in this case, immediately.

ob_start(); //Content past this point...

echo $content;

//...will be sent to the browser (the output buffer gets flushed) when this code executes.
ob_end_flush();
ob_flush();
flush();

if(session_id())
{
    session_write_close(); //Closes writing to the output buffer.
}

//Anything past this point will be ran without involving the browser.

Функции Ответа:

ignore_user_abort(true);

function sendAndAbort($content)
{
    header('Content-Length: ' . strlen($content));

    ob_start();

    echo $content;

    ob_end_flush();
    ob_flush();
    flush();
}

sendAndAbort('Hello World!');

//Anything past this point will be ran without involving the browser.

ок, так что в основном так, как jQuery делает запрос XHR, даже метод ob_flush не будет работать, потому что вы не можете запустить функцию на каждом onreadystatechange. jQuery проверяет состояние,а затем выбирает правильные действия (complete,error,success, timeout). И хотя я не смог найти ссылку, я помню, что слышал, что это не работает со всеми реализациями XHR. Метод, который я считаю, должен работать для вас-это нечто среднее между ob_flush и forever-frame голосование.

<?php
 function wrap($str)
 {
  return "<script>{$str}</script>";
 };

 ob_start(); // begin buffering output
 echo wrap("console.log('test1');");
 ob_flush(); // push current buffer
 flush(); // this flush actually pushed to the browser
 $t = time();
 while($t > (time() - 3)) {} // wait 3 seconds
 echo wrap("console.log('test2');");
?>

<html>
 <body>
  <iframe src="ob.php"></iframe>
 </body>
</html>

и потому, что сценарии выполняются встраиваемые, как буферы сбрасываются, вы получаете выполнение. Чтобы сделать это полезным, измените консоль.войдите в метод обратного вызова, определенный в настройке основного скрипта, чтобы получить данные и действовать по ним. Надеюсь, это поможет. Твое Здоровье, Морган.

альтернативным решением является добавление задания в очередь и создание скрипта cron, который проверяет наличие новых заданий и запускает их.

недавно мне пришлось это сделать, чтобы обойти ограничения, наложенные общим хостом-exec () et al был отключен для PHP, запущенного веб-сервером, но мог работать в сценарии оболочки.

ваша проблема может быть решена путем выполнения параллельного программирования на PHP. Я задал вопрос об этом несколько недель назад здесь: как можно использовать многопоточность в PHP-приложениях

и получил отличные ответы. Особенно мне понравился один из них. Писатель сделал ссылку до простая параллельная обработка в PHP (Sep 2008; by johnlim) учебник который на самом деле может решить вашу проблему очень хорошо, как я уже использовал его, чтобы иметь дело с a аналогичная проблема возникла пару дней назад.

Если flush() функция не работает. Вы должны установить следующие параметры в php.ini как:

output_buffering = Off  
zlib.output_compression = Off  

Последнее Рабочее Решение

    // client can see outputs if any
    ignore_user_abort(true);
    ob_start();
    echo "success";
    $buffer_size = ob_get_length();
    session_write_close();
    header("Content-Encoding: none");
    header("Content-Length: $buffer_size");
    header("Connection: close");
    ob_end_flush();
    ob_flush();
    flush();

    sleep(2);
    ob_start();
    // client cannot see the result of code below

попробовав много разных решений из этой темы (После того, как ни один из них не работал для меня), я нашел решение на официальном сайте PHP.net страница:

function sendResponse($response) {
    ob_end_clean();
    header("Connection: close\r\n");
    header("Content-Encoding: none\r\n");
    ignore_user_abort(true);
    ob_start();

    echo $response; // Actual response that will be sent to the user

    $size = ob_get_length();
    header("Content-Length: $size");
    ob_end_flush();
    flush();
    if (ob_get_contents()) {
        ob_end_clean();
    }
}