Я пытаюсь выполнить вызов 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( '[email protected]', "okay I'm done", 'Yup, all done.' );
?>






Вы можете попробовать сделать многопоточность.
вы можете создать сценарий, который выполняет системный вызов (используя shell_exec), который вызывает двоичный файл php со сценарием для выполнения вашей работы в качестве параметра. Но я не думаю, что это самый безопасный способ. Может быть, вы можете усложнить ситуацию, отключив процесс php и другие вещи
В качестве альтернативы есть класс в phpclasses, который делает это http://www.phpclasses.org/browse/package/3953.html. Но я не знаю специфики реализации
И если вы не хотите ждать завершения процесса, используйте символ & для запуска процесса в фоновом режиме.
Ваша проблема может быть решена путем параллельного программирования на php. Я задал вопрос об этом несколько недель назад здесь: Как можно использовать многопоточность в приложениях PHP
И получил отличные ответы. В частности, мне очень понравился один. Автор сделал ссылку к учебнику Простая параллельная обработка в PHP (сентябрь 2008 г .; автор johnlim), которая действительно может очень хорошо решить вашу проблему, поскольку я уже использовал ее для решения аналогичной проблемы, которая возникла пару дней назад.
Предполагая, что у вас есть сервер Linux и root-доступ, попробуйте это. Это самое простое решение, которое я нашел.
Создайте новый каталог для следующих файлов и предоставьте ему полные разрешения. (Позже мы сможем сделать его более безопасным.)
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 &
Надеюсь это поможет!
Хорошо, так что в основном способ, которым jQuery выполняет запрос XHR, даже метод ob_flush не будет работать, потому что вы не можете запустить функцию на каждом onreadystatechange. jQuery проверяет состояние, а затем выбирает необходимые действия (завершение, ошибка, успех, тайм-аут). И хотя мне не удалось найти ссылку, я помню, что слышал, что это работает не со всеми реализациями XHR. Метод, который, как я считаю, должен работать для вас, представляет собой нечто среднее между ob_flush и опросом с постоянным кадром.
<?php
function wrap($str)
{
return "<script>{$str}</script>";
};
ob_start(); // begin buffering output
echo wrap("console.info('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.info('test2');");
?>
<html>
<body>
<iframe src = "ob.php"></iframe>
</body>
</html>
И поскольку сценарии выполняются в режиме реального времени, вы получаете выполнение при очистке буферов. Чтобы сделать это полезным, измените console.info на метод обратного вызова, определенный в основных настройках сценария, для получения данных и действий с ними. Надеюсь это поможет. Привет, Морган.
Альтернативным решением является добавление задания в очередь и создание сценария cron, который проверяет наличие новых заданий и запускает их.
Недавно мне пришлось сделать это таким образом, чтобы обойти ограничения, налагаемые общим хостом - exec () и другие были отключены для PHP, запускаемого веб-сервером, но могли работать в сценарии оболочки.
На следующей странице руководства по PHP (включая примечания для пользователя) предлагается несколько инструкций о том, как закрыть TCP-соединение с браузером, не завершая PHP-скрипт:
Предположительно, для этого требуется немного больше, чем просто отправка закрытого заголовка.
Затем OP подтверждает: да, это сработало:указывая на примечание пользователя № 71172 (ноябрь 2006 г.) скопировано сюда:
Closing the users browser connection whilst keeping your php script running has been an issue since [PHP] 4.1, when the behaviour of
register_shutdown_function()was modified so that it would not automatically close the users connection.sts at mail dot xubion dot hu Posted the original solution:
<?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"); ?>Which works fine until you substitute
phpinfo()forecho('text I want user to see');in which case the headers are never sent!The solution is to explicitly turn off output buffering and clear the buffer prior to sending your header information. Example:
<?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'); ?>Just spent 3 hours trying to figure this one out, hope it helps someone :)
Tested in:
- IE 7.5730.11
- Mozilla Firefox 1.81
Позже, в июле 2010 года, в связанный ответАрктический огонь связал еще две пользовательские заметки, которые были продолжением предыдущей:
да, это помогло: php.net/manual/en/features.connection-handling.php#71172
Автор и @Timbo White, можно ли преждевременно закрыть соединение, не зная размера содержимого? IE, без необходимости захватывать контент до закрытия.
Хакеры и дрянные веб-браузеры могут по-прежнему игнорировать HTTP-заголовок закрытия соединения и получать остальную часть вывода ... убедитесь, что то, что будет дальше, нечувствительно. возможно, ob_start (); подавить все: p
Добавление fastcgi_finish_request (); было сказано, что он успешно закрывает соединение, когда вышеуказанное не работает. Однако в моем случае это помешало моему сценарию продолжить выполнение, поэтому используйте его с осторожностью.
@RichardSmith Потому что заголовок Connection: close может быть перезаписан другим программным обеспечением в стеке, например обратным прокси в случае CGI (я наблюдал такое поведение с nginx). См. Ответ @hanshenrik по этому поводу. В целом Connection: close выполняется на стороне клиента, и его не следует рассматривать как ответ на этот вопрос. Соединение должно быть закрыто со стороны сервер.
Хороший ответ. Спасибо, что получили представление об обработчике подключения. :)
Не работает с mod_fcgid. См. Ответ @Tasos ниже, чтобы заставить его работать с mod_fcgid.
Необходимо отправить эти 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), это не сработает, потому что фактический размер вывода изменяется, а Content-Length больше не является точным. Отключите сжатие gzip для конкретного скрипта.
Для получения дополнительной информации посетите http://www.zulius.com/how-to/close-browser-connection-continue-execution
Если ваш сервер сжимает вывод, вы можете отключить его с помощью header("Content-Encoding: none\r\n");. Таким образом, apache не сжимает его.
@GDmac спасибо! Некоторое время я не мог заставить это работать, но отключение сжатия помогло.
ob_flush() не является необходимым и фактически вызывает уведомление failed to flush buffer. Я вынул его, и это отлично сработало.
Я обнаружил, что необходима строка ob_flush()был.
Полная версия:
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();
полный в каком смысле? Какая проблема потребовала от вас выполнить сценарий принятых ответов (какой?) И какие из ваших различий в конфигурации сделали это необходимым?
эта строка: заголовок ("Content-Encoding: none"); -> очень важно.
Спасибо, это единственное рабочее решение на этой странице. Это следует принять как ответ.
Вы можете использовать Fast-CGI с PHP-FPM, чтобы использовать fastcgi_end_request() функция. Таким образом, вы можете продолжить некоторую обработку, пока ответ уже отправлен клиенту.
Вы найдете это в руководстве по PHP здесь: Менеджер процессов FastCGI (FPM); Но эта функция отдельно не описывается в руководстве. Вот отрывок из PHP-FPM: PHP FastCGI Process Manager Wiki:
Область: функция php
Категория: Оптимизация
Эта функция позволяет ускорить выполнение некоторых запросов php. Ускорение возможно при наличии в процессе выполнения скрипта действий, не влияющих на реакцию сервера. Например, сохранение сеанса в memcached может произойти после того, как страница была сформирована и передана на веб-сервер. fastcgi_finish_request() - это функция php, которая останавливает вывод ответа. Веб-сервер немедленно начинает передавать ответ клиенту «медленно и грустно», а php в то же время может делать много полезных вещей в контексте запроса, таких как сохранение сеанса, преобразование загруженного видео, обработка всех видов статистики и др.
fastcgi_finish_request() может вызывать выполнение функции выключения.
Примечание:fastcgi_finish_request() имеет причуда, где вызовы flush, print или echo прерывают сценарий раньше.
Чтобы избежать этой проблемы, вы можете вызвать ignore_user_abort(true) прямо перед или после вызова fastcgi_finish_request:
ignore_user_abort(true);
fastcgi_finish_request();
ЭТО АКТУАЛЬНЫЙ ОТВЕТ!
если вы используете php-fpm - просто используйте эту функцию - забудьте о заголовках и всем остальном. Сэкономил мне столько времени!
Если функция flush() не работает. Вы должны установить следующие параметры в php.ini, например:
output_buffering = Off
zlib.output_compression = Off
Вот модификация кода Тимбо, которая работает со сжатием 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 ********/
ТЫ БОГ. Я работал 2 дня, чтобы разобраться в этом. Это сработало на моем локальном разработчике, но не на хосте. Меня обливали из шланга. ВЫ СПАСЛИ МЕНЯ. СПАСИБО!!!!
Ответ Йери Себрехтса близок, но он уничтожает любой существующий контент, который может быть помещен в буфер перед тем, как вы захотите отключиться. Он не вызывает ignore_user_abort должным образом, что приводит к преждевременному завершению сценария. ответ дийизма - это хорошо, но не применимо в целом. Например. у человека может быть больше или меньше буферов вывода, которые этот ответ не обрабатывает, поэтому он может просто не работать в вашей ситуации, и вы не будете знать почему.
Эта функция позволяет отключиться в любое время (если заголовки еще не отправлены) и сохраняет созданный вами контент. Дополнительное время обработки по умолчанию не ограничено.
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();
}
Если вам также нужна дополнительная память, выделите ее перед вызовом этой функции.
Я нахожусь на общем хосте, и 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);
}
Благодаря вашему хорошему ответу я понял, насколько глупо (и ненужно) использовать connection: close. Думаю, некоторые не знакомы с основными принципами работы своего сервера.
@Justin Я написал это давным-давно. Посмотрев на это еще раз, я должен отметить, что может потребоваться дополнить фрагменты до 4 КБ. Я, кажется, помню, что некоторые серверы не сбрасываются, пока не достигнут этого минимума.
@skibulk Некоторым требуется 64 КБ (несжатого ??) заполнения, например это значение по умолчанию для FcgidOutputBufferSize и не может быть отменено на некоторых совместно используемых серверах.
Примечание для пользователей mod_fcgid (пожалуйста, используйте на свой страх и риск).
Принятый ответ Йери Себрехтс действительно работает. Однако, если вы используете mod_fcgid, вы можете обнаружить, что это решение не работает само по себе. Другими словами, когда вызывается функция румянец, соединение с клиентом не закрывается.
Возможно, виноват параметр конфигурации FcgidOutputBufferSizemod_fcgid. Я нашел этот совет в:
Прочитав вышесказанное, вы можете прийти к выводу, что быстрым решением было бы добавить строку (см. «Пример виртуального хоста» в конце):
FcgidOutputBufferSize 0
либо в файле конфигурации Apache (например, httpd.conf), либо в файле конфигурации FCGI (например, fcgid.conf), либо в файле виртуальных хостов (например, httpd-vhosts.conf).
In (1) above, a variable named "OutputBufferSize" is mentioned. This is the old name of the
FcgidOutputBufferSizementioned in (2) (see the upgrade notes in the Apache web page for mod_fcgid).
Вышеупомянутое решение отключает буферизацию, выполняемую mod_fcgid, либо для всего сервера, либо для определенного виртуального хоста. Это может привести к снижению производительности вашего веб-сайта. С другой стороны, это может быть не так, поскольку PHP выполняет буферизацию самостоятельно.
Если вы не хотите отключать буферизацию mod_fcgid, есть другое решение ... вы можете принудительно очистить этот буфер.
Приведенный ниже код делает именно это, опираясь на решение, предложенное Джоэри Себрехтсом:
<?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 для соответствующей директивы. Следовательно, вам может потребоваться изменить это значение соответствующим образом, если в вашей среде установлено другое значение.
<VirtualHost *:80>
DocumentRoot "d:/wamp/www/example"
ServerName example.local
FcgidOutputBufferSize 0
<Directory "d:/wamp/www/example">
Require all granted
</Directory>
</VirtualHost>
Я перепробовал много решений. И это единственное решение, которое у меня работает с mod_fcgid.
это сработало для меня
//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);
Лучшее решение - разветвить фоновый процесс. В unix / linux это довольно просто:
<?php
echo "We'll email you as soon as this is done.";
system("php somestuff.php [email protected] >/dev/null &");
?>
Чтобы получить лучшие примеры, вам следует взглянуть на этот вопрос:
Зависит от того, что вы хотите делать. Если он записывает некоторые данные, которые были определены во время текущего запроса, в кеш, то форк почти наверняка не лучшее решение. Даже в случае OP, если другие решения надежны, они вполне могут сэкономить некоторые циклы ЦП сервера по сравнению с передачей значительного блока данных разветвленному процессу.
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.
Последнее рабочее решение
// 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();
}
}
вы очистили буфер вывода с помощью ob_flush (), и это не сработало?