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

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

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 ]

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

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

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


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

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

sts at 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 часа, пытаясь выяснить это, надеюсь, это кому-нибудь поможет:)

Проверено в:

  • IE 7.5730.11
  • Mozilla Firefox 1.81

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

54 голосов
/ 21 ноября 2009

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

15 голосов
/ 19 августа 2011

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

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();
15 голосов
/ 11 апреля 2012

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

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


fastcgi_finish_request ()

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

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

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

4 голосов
/ 14 октября 2016

Лучшее решение - развить фоновый процесс. Это довольно просто на Unix / Linux:

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

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

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

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

Если у вас есть сервер 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 &

Надеюсь, это поможет!

4 голосов
/ 19 февраля 2013

Вот модификация кода Тимбо, которая работает со сжатием 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 ********/
3 голосов
/ 12 ноября 2014

Я на общем хосте, и 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);
}
2 голосов
/ 30 марта 2017

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.
2 голосов
/ 26 сентября 2008

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

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

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

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