Как мне закрыть соединение раньше времени?

Я пытаюсь выполнить вызов 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.' );

?>

вы очистили буфер вывода с помощью ob_flush (), и это не сработало?

Vinko Vrsalovic 26.09.2008 13:05
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
101
1
72 084
19
Перейти к ответу Данный вопрос помечен как решенный

Ответы 19

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

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

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

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

Liam 26.09.2008 13:32

Ваша проблема может быть решена путем параллельного программирования на 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() for echo('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

Eric_WVGG 27.09.2008 00:14

Автор и @Timbo White, можно ли преждевременно закрыть соединение, не зная размера содержимого? IE, без необходимости захватывать контент до закрытия.

skibulk 11.11.2014 21:05

Хакеры и дрянные веб-браузеры могут по-прежнему игнорировать HTTP-заголовок закрытия соединения и получать остальную часть вывода ... убедитесь, что то, что будет дальше, нечувствительно. возможно, ob_start (); подавить все: p

hanshenrik 02.06.2015 11:46

Добавление fastcgi_finish_request (); было сказано, что он успешно закрывает соединение, когда вышеуказанное не работает. Однако в моем случае это помешало моему сценарию продолжить выполнение, поэтому используйте его с осторожностью.

Eric Dubé 16.06.2016 05:22

@RichardSmith Потому что заголовок Connection: close может быть перезаписан другим программным обеспечением в стеке, например обратным прокси в случае CGI (я наблюдал такое поведение с nginx). См. Ответ @hanshenrik по этому поводу. В целом Connection: close выполняется на стороне клиента, и его не следует рассматривать как ответ на этот вопрос. Соединение должно быть закрыто со стороны сервер.

7heo.tk 21.07.2016 18:24

Хороший ответ. Спасибо, что получили представление об обработчике подключения. :)

Developer 22.11.2017 08:55

Не работает с mod_fcgid. См. Ответ @Tasos ниже, чтобы заставить его работать с mod_fcgid.

Tsounabe 23.05.2018 21:42

Необходимо отправить эти 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 18.03.2012 20:03

@GDmac спасибо! Некоторое время я не мог заставить это работать, но отключение сжатия помогло.

Reactgular 16.09.2013 20:02

ob_flush() не является необходимым и фактически вызывает уведомление failed to flush buffer. Я вынул его, и это отлично сработало.

Levi 29.09.2013 18:02

Я обнаружил, что необходима строка ob_flush()был.

Deebster 04.03.2014 15:46

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

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();

полный в каком смысле? Какая проблема потребовала от вас выполнить сценарий принятых ответов (какой?) И какие из ваших различий в конфигурации сделали это необходимым?

hakre 24.08.2013 10:39

эта строка: заголовок ("Content-Encoding: none"); -> очень важно.

Bobby Tables 25.09.2013 18:36

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

user494599 15.09.2016 12:11

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

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


fastcgi_finish_request ()

Область: функция 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();

ЭТО АКТУАЛЬНЫЙ ОТВЕТ!

Kirill Titov 09.07.2015 11:07

если вы используете php-fpm - просто используйте эту функцию - забудьте о заголовках и всем остальном. Сэкономил мне столько времени!

Ross 28.07.2016 21:42

Если функция 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 дня, чтобы разобраться в этом. Это сработало на моем локальном разработчике, но не на хосте. Меня обливали из шланга. ВЫ СПАСЛИ МЕНЯ. СПАСИБО!!!!

Chad Caldwell 13.04.2020 19:14

Ответ Йери Себрехтса близок, но он уничтожает любой существующий контент, который может быть помещен в буфер перед тем, как вы захотите отключиться. Он не вызывает 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 13.03.2018 12:15

@Justin Я написал это давным-давно. Посмотрев на это еще раз, я должен отметить, что может потребоваться дополнить фрагменты до 4 КБ. Я, кажется, помню, что некоторые серверы не сбрасываются, пока не достигнут этого минимума.

skibulk 13.03.2018 16:21

@skibulk Некоторым требуется 64 КБ (несжатого ??) заполнения, например это значение по умолчанию для FcgidOutputBufferSize и не может быть отменено на некоторых совместно используемых серверах.

Jake 05.10.2020 03:47

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

Быстрое решение

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

Возможно, виноват параметр конфигурации FcgidOutputBufferSizemod_fcgid. Я нашел этот совет в:

  1. этот ответ Трэверс Картер и
  2. это сообщение в блоге Семаса Маккиннона.

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

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 FcgidOutputBufferSize mentioned 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 для соответствующей директивы. Следовательно, вам может потребоваться изменить это значение соответствующим образом, если в вашей среде установлено другое значение.

Моя среда

  • WampServer 2.5
  • Apache 2.4.9
  • PHP 5.5.19 VC11, x86, без потоковой передачи
  • mod_fcgid / 2.3.9
  • Windows 7 Профессиональная x64

Пример виртуального хоста

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

    FcgidOutputBufferSize 0

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

Я перепробовал много решений. И это единственное решение, которое у меня работает с mod_fcgid.

Tsounabe 23.05.2018 21:40

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

//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 &");
?>

Чтобы получить лучшие примеры, вам следует взглянуть на этот вопрос:

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

Зависит от того, что вы хотите делать. Если он записывает некоторые данные, которые были определены во время текущего запроса, в кеш, то форк почти наверняка не лучшее решение. Даже в случае OP, если другие решения надежны, они вполне могут сэкономить некоторые циклы ЦП сервера по сравнению с передачей значительного блока данных разветвленному процессу.

Jake 05.10.2020 02:16

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();
    }
}

Другие вопросы по теме