it-swarm.com.de

verbindung frühzeitig schließen

Ich versuche einen AJAX - Aufruf (über JQuery) auszuführen, der einen ziemlich langen Prozess auslöst. Ich möchte, dass das Skript einfach eine Antwort sendet, aus der hervorgeht, dass der Prozess gestartet wurde. JQuery gibt die Antwort jedoch nicht zurück, bis das Skript PHP abgeschlossen ist.

Ich habe dies mit einem "close" -Header (unten) und auch mit Ausgabepufferung versucht; keiner scheint zu funktionieren. Irgendwelche Ideen? oder muss ich dies in JQuery tun?

<?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.' );

?>
88
Eric_WVGG

In der folgenden PHP -Handbuchseite (einschließlich Benutzerhinweisen) werden mehrere Anweisungen zum Schließen der TCP - Verbindung zum Browser ohne Beenden des Skripts PHP vorgeschlagen:

Angeblich erfordert es etwas mehr als das Senden eines nahen Headers.


OP bestätigt dann: yup, dies hat den Trick:zeigt auf User-Note # 71172 (Nov 2006) hier kopiert:

Das Schließen der Browser-Verbindung des Benutzers bei laufendem PHP-Skript ist ein Problem seit [PHP] 4.1, als das Verhalten von register_shutdown_function() so geändert wurde, dass die Verbindung des Benutzers nicht automatisch geschlossen wird.

sts at mail punkt xubion dot hu Hat die ursprüngliche Lösung gepostet:

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

Was gut funktioniert, bis Sie phpinfo() für echo('text I want user to see'); ersetzen. In diesem Fall werden die Header nie gesendet!

Die Lösung besteht darin, die Ausgabepufferung explizit zu deaktivieren und den Puffer vor dem Senden der Headerinformationen zu löschen. Beispiel:

<?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');
?>

Ich habe gerade 3 Stunden damit verbracht, dieses herauszufinden, hoffe es hilft jemandem :)

Getestet in:

  • IE 7.5730.11
  • Mozilla Firefox 1.81

Später im Juli 2010 verknüpfte eine AntwortArctic Fire zwei weitere Benutzeranmerkungen, die Follow-ups waren, mit der oben genannten:

78
Joeri Sebrechts

Es ist notwendig, diese 2 Header zu senden:

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

Da Sie die Größe Ihrer Ausgabe kennen müssen, müssen Sie Ihre Ausgabe puffern und dann in den Browser leeren:

// 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 ********/

Wenn Ihr Webserver die automatische gzip-Komprimierung für die Ausgabe verwendet (z. B. Apache mit mod_deflate), funktioniert dies nicht, da die tatsächliche Größe der Ausgabe geändert wird und die Inhaltslänge nicht mehr genau ist. Deaktivieren Sie die GZIP-Komprimierung des jeweiligen Skripts.

Weitere Informationen finden Sie unter http://www.zulius.com/how-to/close-browser-connection-continue-execution

53
Timbo White

Komplette Version:

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

Sie können Fast-CGI mit PHP-FPM verwenden, um die Funktion fastcgi_end_request() zu verwenden. Auf diese Weise können Sie die Verarbeitung fortsetzen, während die Antwort bereits an den Client gesendet wurde.

Sie finden dies im Handbuch PHP hier: FastCGI Process Manager (FPM) ; Diese Funktion ist jedoch im Handbuch nicht weiter beschrieben. Hier der Auszug aus dem PHP-FPM: PHP FastCGI Process Manager Wiki :


fastcgi_finish_request ()

Gültigkeitsbereich: PHP-Funktion Kategorie: Optimierung

Mit dieser Funktion können Sie die Implementierung einiger PHP-Abfragen beschleunigen. Eine Beschleunigung ist möglich, wenn während der Skriptausführung Aktionen ausgeführt werden, die die Serverantwort nicht beeinflussen. Das Speichern der Sitzung in memcached kann beispielsweise erfolgen, nachdem die Seite gebildet und an einen Webserver übergeben wurde. fastcgi_finish_request() ist eine PHP-Funktion, die die Antwortausgabe stoppt. Der Webserver beginnt sofort, die Antwort "langsam und traurig" an den Client zu übermitteln, und php kann im Zusammenhang mit einer Abfrage viele nützliche Dinge ausführen, z von Statistiken usw.

fastcgi_finish_request() kann die Ausführung der Herunterfahren-Funktion aufrufen.

13

Gehen Sie davon aus, dass Sie über einen Linux-Server und einen Root-Zugriff verfügen. Es ist die einfachste Lösung, die ich gefunden habe.

Erstellen Sie ein neues Verzeichnis für die folgenden Dateien und erteilen Sie ihm die vollständigen Berechtigungen. (Wir können es später sicherer machen.)

mkdir test
chmod -R 777 test
cd test

Fügen Sie dies in eine Datei mit dem Namen bgping ein.

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

Beachten Sie den &. Der Ping-Befehl wird im Hintergrund ausgeführt, während der aktuelle Prozess mit dem Echo-Befehl ..__ fortfährt. Es wird 15 Mal auf www.google.com gepingt.

Machen Sie es ausführbar.

chmod 777 bgping

Fügen Sie dies in eine Datei mit dem Namen bgtest.php ein.

<?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";

?>

Wenn Sie bgtest.php in Ihrem Browser anfordern, sollten Sie die folgende Antwort schnell erhalten, ohne etwa 15 Sekunden warten zu müssen, bis der Ping-Befehl abgeschlossen ist.

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

result:0
end bgtest.php

Der Ping-Befehl sollte jetzt auf dem Server ausgeführt werden. Anstelle des Ping-Befehls können Sie ein PHP -Skript ausführen:

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

Hoffe das hilft!

4
Liam

Hier ist eine Modifikation des Timbo-Codes, die mit der gzip-Komprimierung funktioniert.

// 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
AWinter

Eine bessere Lösung ist das Abspalten eines Hintergrundprozesses. Unter Unix/Linux ist das ziemlich einfach:

<?php
echo "We'll email you as soon as this is done.";
system("php somestuff.php [email protected] >/dev/null &");
?>

Sie sollten diese Frage für bessere Beispiele betrachten: 

PHP einen Hintergrundprozess ausführen

3
Salman A

Sie könnten versuchen, Multithreading auszuführen.

sie könnten ein Skript erstellen, das einen Systemaufruf ausführt (mithilfe von Shell_exec ), das die PHP-Binärdatei mit dem Skript aufruft, um Ihre Arbeit als Parameter auszuführen. Aber ich glaube nicht, dass dies der sicherste Weg ist. Vielleicht können Sie das Zeug verbessern, indem Sie den PHP-Prozess und andere Sachen chrooten

Alternativ gibt es eine Klasse bei phpclasses, die das http://www.phpclasses.org/browse/package/3953.html tut. Aber ich kenne die Einzelheiten der Implementierung nicht

2
paan

Ich bin auf einem gemeinsam genutzten Host und fastcgi_finish_request ist so eingestellt, dass Skripts vollständig beendet werden. Die connection: close-Lösung gefällt mir auch nicht. Die Verwendung erzwingt eine separate Verbindung für nachfolgende Anforderungen, wodurch zusätzliche Serverressourcen entstehen. Ich habe den Transfer-Encoding: cunkedWikipedia-Artikel gelesen und festgestellt, dass 0\r\n\r\n eine Antwort abbricht. Ich habe dies nicht in allen Browserversionen und Geräten getestet, aber es funktioniert auf allen meinen vier aktuellen Browsern.

// 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
skibulk

Ihr Problem kann durch paralleles Programmieren in PHP gelöst werden. Ich habe vor ein paar Wochen hier eine Frage dazu gestellt: Wie kann man Multi-Threading in PHP -Anwendungen verwenden

Und tolle Antworten bekommen. Besonders gut hat mir einer gefallen. Der Autor hat einen Verweis gemacht auf das Easy Parallel Processing in PHP (Sep 2008; von johnlim) Tutorial welches tatsächlich Ihr Problem lösen kann Problem sehr gut, da ich es bereits verwendet habe, um ein ähnliches Problem zu behandeln, das vor ein paar Tagen aufgetreten ist.

1
Steve Obbayi

Joeri Sebrechts 'Antwort ist nahe, aber es zerstört alle vorhandenen Inhalte, die gepuffert werden können, bevor Sie die Verbindung trennen möchten. Es ruft ignore_user_abort nicht korrekt auf, sodass das Skript vorzeitig beendet werden kann. diyism's answer ist gut, aber nicht generisch anwendbar. Z.B. Eine Person kann mehr oder weniger Ausgabepuffer haben, mit denen diese Antwort nicht umgehen kann. In Ihrer Situation funktioniert sie also möglicherweise nicht und Sie wissen nicht, warum.

Diese Funktion ermöglicht es Ihnen, die Verbindung jederzeit zu trennen (solange die Header noch nicht gesendet wurden), und der von Ihnen generierte Inhalt bleibt erhalten. Die zusätzliche Bearbeitungszeit ist standardmäßig unbegrenzt.

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

Wenn Sie zusätzlichen Speicher benötigen, müssen Sie ihn vor dem Aufruf dieser Funktion zuordnen.

1
Walf

TL; DR Antwort:

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.

Funktion antwort:

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.
1

das hat für mich funktioniert

//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
Williem

Hinweis für Benutzer von mod_fcgid (bitte auf eigenes Risiko verwenden).

Schnelle Lösung

Die akzeptierte Antwort von Joeri Sebrechts ist in der Tat funktional. Wenn Sie jedoch mod_fcgid verwenden, funktioniert diese Lösung möglicherweise nicht von alleine. Mit anderen Worten, wenn die Funktion flush aufgerufen wird, wird die Verbindung zum Client nicht geschlossen.

Der FcgidOutputBufferSize Konfigurationsparameter von mod_fcgid kann schuld sein. Ich habe diesen Tipp gefunden in:

  1. diese Antwort von Travers Carter und
  2. dieser Blogpost von Seumas Mackinnon .

Nach dem Lesen der obigen Informationen können Sie zu dem Schluss kommen, dass eine schnelle Lösung darin besteht, die Zeile hinzuzufügen (siehe "Beispiel für einen virtuellen Host" am Ende):

FcgidOutputBufferSize 0

in Ihrer Apache-Konfigurationsdatei (z. B. httpd.conf), Ihrer FCGI-Konfigurationsdatei (z. B. fcgid.conf) oder in Ihrer virtuellen Hosts-Datei (z. B. httpd-vhosts.conf).

In (1) oben wird eine Variable mit dem Namen "OutputBufferSize" erwähnt. Dies ist der alte Name des in (2) erwähnten FcgidOutputBufferSize (siehe pgrade-Hinweise auf der Apache-Webseite für mod_fcgid ).

Details & Eine zweite Lösung

Die obige Lösung deaktiviert die Pufferung durch mod_fcgid entweder für den gesamten Server oder für einen bestimmten virtuellen Host. Dies kann zu einer Beeinträchtigung der Leistung Ihrer Website führen. Auf der anderen Seite kann dies durchaus nicht der Fall sein, da PHP die Pufferung alleine durchführt.

Falls Sie die Pufferung von mod_fcgid nicht deaktivieren möchten, gibt es eine andere Lösung ... Sie können diesen Puffer zum Leeren zwingen .

Der folgende Code macht genau das, indem er auf der von Joeri Sebrechts vorgeschlagenen Lösung aufbaut:

<?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');
?>

Die hinzugefügte Codezeile füllt im Wesentlichen den Puffer von mod_fcgi und erzwingt so das Leeren. Die Nummer "65537" wurde gewählt, weil der Standardwert der Variablen FcgidOutputBufferSize "65536" ist, wie auf der Apache-Webseite für die entsprechende Direktive erwähnt. Daher müssen Sie diesen Wert möglicherweise entsprechend anpassen, wenn in Ihrer Umgebung ein anderer Wert festgelegt ist.

Meine Umgebung

  • WampServer 2.5
  • Apache 2.4.9
  • PHP 5.5.19 VC11, x86, nicht threadsicher
  • mod_fcgid/2.3.9
  • Windows 7 Professional x64

Beispiel für einen virtuellen Host

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

    FcgidOutputBufferSize 0

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

Wenn flush() Funktion nicht funktioniert. Sie müssen die nächsten Optionen inphp.inilike einstellen:

output_buffering = Off  
zlib.output_compression = Off  
0
Nir O.

Ok, im Grunde die Art und Weise, wie jQuery die XHR-Anforderung ausführt. Selbst die Methode ob_flush funktioniert nicht, da Sie nicht in jeder onreadystatechange-Funktion eine Funktion ausführen können. jQuery überprüft den Status und wählt dann die richtigen Aktionen aus (Complete, Error, Success, Timeout). Und obwohl ich keine Referenz finden konnte, erinnere ich mich, dass ich nicht gehört habe, dass dies mit allen XHR-Implementierungen funktioniert .. Eine Methode, die meiner Meinung nach für Sie funktionieren sollte, ist eine Kreuzung zwischen ob_flush und ewigen Abfragen.

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

Da die Skripte inline ausgeführt werden und die Puffer geleert werden, wird die Ausführung ausgeführt. Um dies nützlich zu machen, ändern Sie die Datei console.log in eine in Ihrem Hauptskript-Setup definierte Rückrufmethode, um Daten zu empfangen und darauf zu reagieren. Hoffe das hilft. Prost, Morgan.

0

Eine alternative Lösung besteht darin, den Job einer Warteschlange hinzuzufügen und ein Cron-Skript zu erstellen, das nach neuen Jobs sucht und diese ausführt.

Ich musste dies kürzlich tun, um die von einem gemeinsam genutzten Host auferlegten Beschränkungen zu umgehen - exec () et al war für PHP deaktiviert, das vom Webserver ausgeführt wurde, konnte aber in einem Shell-Skript ausgeführt werden.

0
Ole Helgesen

Neueste Arbeitslösung

    // 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

Nachdem ich viele verschiedene Lösungen aus diesem Thread ausprobiert hatte (nachdem keine davon für mich gearbeitet hatte), habe ich auf der offiziellen PHP.net-Seite eine Lösung gefunden:

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
WhiteAngel