закрыть соединение рано - PullRequest
       123

закрыть соединение рано

90 голосов
/ 26 сентября 2008

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

Я пробовал это с заголовком "close" (ниже), а также с буферизацией вывода; ни то, ни другое не работает. Есть догадки? или это то, что мне нужно сделать в 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 ]

1 голос
/ 03 февраля 2015

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

//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);
1 голос
/ 18 ноября 2014

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

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

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

Возможно, виноват 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 , есть другое решение ... вы можете принудительно сбросить этот буфер .

Код ниже делает именно это, опираясь на решение, предложенное Джоери Себрехтом:

<?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 Professional x64

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

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

    FcgidOutputBufferSize 0

    <Directory "d:/wamp/www/example">
        Require all granted
    </Directory>
</VirtualHost>
1 голос
/ 10 января 2014

Ответ 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();
}

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

1 голос
/ 26 сентября 2008

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

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

0 голосов
/ 26 сентября 2008

Хорошо, поэтому в основном способ, которым jQuery выполняет XHR-запрос, даже метод ob_flush не будет работать, потому что вы не можете запустить функцию для каждого onreadystatechange. JQuery проверяет состояние, а затем выбирает правильные действия (завершено, ошибка, успех, время ожидания). И хотя мне не удалось найти ссылку, я вспоминаю, что слышал, что это не работает со всеми реализациями 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>

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

0 голосов
/ 26 сентября 2008

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

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

0 голосов
/ 10 апреля 2017

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

    // 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
0 голосов
/ 10 марта 2018

Попробовав множество различных решений из этой темы (после того, как ни одно из них не помогло мне), я нашел решение на официальной странице 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();
    }
}
0 голосов
/ 05 мая 2012

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

output_buffering = Off  
zlib.output_compression = Off  
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...