http yanıtını gönderdikten sonra php'yi işlemeye devam et


102

Komut dosyam sunucu tarafından çağrılıyor. Sunucudan alacağım ID_OF_MESSAGEve TEXT_OF_MESSAGE.

Komut dosyamda gelen metni ele alacağım ve params ile yanıt oluşturacağım: ANSWER_TO_IDve RESPONSE_MESSAGE.

Sorun şu ki, incomming'e yanıt gönderiyorum "ID_OF_MESSAGE", ancak bana mesaj gönderen sunucu, http yanıtını 200 aldıktan sonra mesajını bana teslim edildi olarak ayarlayacak (Bu kimliğe yanıt gönderebileceğim anlamına geliyor).

Çözümlerden biri, mesajı veritabanına kaydetmek ve her dakika çalışacak bazı cron yapmaktır, ancak hemen yanıt mesajı oluşturmam gerekiyor.

Sunucu http yanıtına 200 göndermenin ve ardından php komut dosyasını çalıştırmaya devam etmenin bir çözümü var mı?

Çok teşekkür ederim

Yanıtlar:


192

Evet. Bunu yapabilirsiniz:

ignore_user_abort(true);
set_time_limit(0);

ob_start();
// do initial processing here
echo $response; // send the response
header('Connection: close');
header('Content-Length: '.ob_get_length());
ob_end_flush();
ob_flush();
flush();

// now the request is sent to the browser, but the script is still running
// so, you can continue...

5
Canlı tutma bağlantısıyla yapmak mümkün mü?
Congelli501

3
Mükemmel!! Bu soruya gerçekten işe yarayan tek yanıt bu !!! 10p +
Martin_Lake

3
ob_end_flush'tan sonra ob_flush kullanmanızın bir nedeni var mı? Sonunda orada flush fonksiyonuna olan ihtiyacı anlıyorum ama ob_end_flush çağrılıyken neden ob_flush'a ihtiyacınız olduğunu bilmiyorum.
ars265

9
Bir içerik kodlama üstbilgisi 'yok'dan başka bir şeye ayarlanırsa, kullanıcının tam yürütme süresini beklemesine izin vereceği için bu örneği işe yaramaz hale getirebileceğini lütfen unutmayın (zaman aşımına kadar?). Bu nedenle, üretim ortamında yerel olarak çalışacağından kesinlikle emin olmak için, 'içerik kodlama' başlığını 'yok' olarak ayarlayın:header("Content-Encoding: none")
Brian

18
İpucu: PHP-FPM kullanmaya başladım, bu yüzden fastcgi_finish_request()sonuna eklemek zorunda kaldım
vcampitelli

44

Burada kullanmayı öneren birçok yanıt gördüm, ignore_user_abort(true);ancak bu kod gerekli değil. Tüm bunlar, kullanıcının iptal etmesi durumunda (tarayıcıyı kapatarak veya isteği durdurmak için escape tuşuna basarak) bir yanıt gönderilmeden önce komut dosyanızın çalışmaya devam etmesini sağlamaktır. Ama istediğin bu değil. Bir yanıt gönderildikten SONRA yürütmeye devam etmek istiyorsunuz. İhtiyacınız olan tek şey şudur:

    // Buffer all upcoming output...
    ob_start();

    // Send your response.
    echo "Here be response";

    // Get the size of the output.
    $size = ob_get_length();

    // Disable compression (in case content length is compressed).
    header("Content-Encoding: none");

    // Set the content length of the response.
    header("Content-Length: {$size}");

    // Close the connection.
    header("Connection: close");

    // Flush all output.
    ob_end_flush();
    ob_flush();
    flush();

    // Close current session (if it exists).
    if(session_id()) session_write_close();

    // Start your background work here.
    ...

Arka plan çalışmanızın PHP'nin varsayılan komut dosyası yürütme süresinden daha uzun süreceğinden endişeleniyorsanız set_time_limit(0);, en üste yapıştırın .


3
ÇOK farklı kombinasyon denedim, bu işe yarayan! Teşekkürler Kosta Kontos !!!
Martin_Lake

1
Apache 2, php 7.0.32 ve ubuntu 16.04 üzerinde mükemmel çalışıyor! Teşekkürler!
KyleBunga

1
Diğer çözümleri denedim ve sadece bu benim için çalıştı. Hatların sırası da önemlidir.
Sinisa

32

FastCGI işleme veya PHP-FPM kullanıyorsanız şunları yapabilirsiniz:

session_write_close(); //close the session
ignore_user_abort(true); //Prevent echo, print, and flush from killing the script
fastcgi_finish_request(); //this returns 200 to the user, and processing continues

// do desired processing ...
$expensiveCalulation = 1+1;
error_log($expensiveCalculation);

Kaynak: https://www.php.net/manual/en/function.fastcgi-finish-request.php

PHP sorunu # 68722: https://bugs.php.net/bug.php?id=68772


2
Bunun için teşekkürler, birkaç saat geçirdikten sonra nginx'te benim için çalıştı
Ehsan

7
veri toplamı pahalı hesaplama tho: o çok etkilendim, çok pahalı!
Friedrich Roell

Teşekkürler DarkNeuron! Php-fpm kullanarak bizim için harika cevap, sorunumu çözdü!
Sinisa

21

Bu konu üzerine birkaç saat harcadım ve Apache ve Nginx üzerinde çalışan bu fonksiyonla geldim:

/**
 * respondOK.
 */
protected function respondOK()
{
    // check if fastcgi_finish_request is callable
    if (is_callable('fastcgi_finish_request')) {
        /*
         * This works in Nginx but the next approach not
         */
        session_write_close();
        fastcgi_finish_request();

        return;
    }

    ignore_user_abort(true);

    ob_start();
    $serverProtocole = filter_input(INPUT_SERVER, 'SERVER_PROTOCOL', FILTER_SANITIZE_STRING);
    header($serverProtocole.' 200 OK');
    header('Content-Encoding: none');
    header('Content-Length: '.ob_get_length());
    header('Connection: close');

    ob_end_flush();
    ob_flush();
    flush();
}

Bu işlevi uzun işlemeden önce çağırabilirsiniz.


2
Bu çok güzel! Yukarıdaki her şeyi denedikten sonra nginx ile çalışan tek şey budur.
baharat

Kodunuz buradaki kodla neredeyse tamamen aynı ama gönderiniz daha eski :) +1
Muhasebeci م

filter_inputişlev konusunda dikkatli olun, bazen NULL döndürür. ayrıntı için bu kullanıcı katkısına bakın
Muhasebeci,

4

Cevabı @vcampitelli tarafından biraz değiştirildi. closeBaşlığa ihtiyacınız olduğunu düşünmeyin . Chrome'da yinelenen yakın başlıklar görüyordum.

<?php

ignore_user_abort(true);

ob_start();
echo '{}';
header($_SERVER["SERVER_PROTOCOL"] . " 202 Accepted");
header("Status: 202 Accepted");
header("Content-Type: application/json");
header('Content-Length: '.ob_get_length());
ob_end_flush();
ob_flush();
flush();

sleep(10);

3
Orijinal cevapta bundan bahsetmiştim ama burada da söyleyeceğim. Bağlantıyı mutlaka kapatmanız gerekmez, ancak aynı bağlantıda talep edilen bir sonraki varlık beklemeye zorlanacaktır. Böylece HTML'yi hızlı bir şekilde teslim edebilirsiniz, ancak daha sonra JS veya CSS dosyalarınızdan biri yavaş yüklenebilir, çünkü bağlantının sonraki varlığı alabilmesi için PHP'den yanıt almayı bitirmesi gerekir. Bu nedenle, bağlantının kapatılması iyi bir fikirdir, böylece tarayıcının serbest kalması için beklemesi gerekmez.
Nate Lampton

3

Bunun için register_shutdown_function php işlevini kullanıyorum.

void register_shutdown_function ( callable $callback [, mixed $parameter [, mixed $... ]] )

http://php.net/manual/en/function.register-shutdown-function.php

Düzenleme : Yukarıdakiler çalışmıyor. Görünüşe göre bazı eski belgeler tarafından yanıltıldım. Register_shutdown_function davranışı PHP 4.1 bağlantı bağlantısından beri değişti


İstenen bu değil - bu işlev temelde yalnızca komut dosyası sonlandırma olayını genişletir ve hala çıktı arabelleğinin bir parçasıdır.
fisk

1
Olumlu oyu buldum, çünkü neyin işe yaramadığını gösteriyor.
Trendfischer

1
Ditto - olumlu oylama çünkü bunu bir cevap olarak görmeyi bekliyordum ve işe yaramadığını görmek faydalı.
HappyDog

2

Pthread yükleyemiyorum ve önceki çözümler benim için çalışmıyor. Çalışmak için yalnızca aşağıdaki çözümü buldum (ref: https://stackoverflow.com/a/14469376/1315873 ):

<?php
ob_end_clean();
header("Connection: close");
ignore_user_abort(); // optional
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 !
session_write_close(); // Added a line suggested in the comment
// Do processing here 
sleep(30);
echo('Text user will never see');
?>

1

php file_get_contents kullanılması durumunda, bağlantının kapatılması yeterli değildir. php hala sunucu tarafından cadı gönderilmesini bekler.

benim çözümüm 'İçerik-Uzunluk:' okumak.

işte örnek:

response.php:

 <?php

ignore_user_abort(true);
set_time_limit(500);

ob_start();
echo 'ok'."\n";
header('Connection: close');
header('Content-Length: '.ob_get_length());
ob_end_flush();
ob_flush();
flush();
sleep(30);

Kapanış satırına yanıt olarak "\ n" ye dikkat edin, eof beklerken fget okunmazsa.

read.php:

<?php
$vars = array(
    'hello' => 'world'
);
$content = http_build_query($vars);

fwrite($fp, "POST /response.php HTTP/1.1\r\n");
fwrite($fp, "Content-Type: application/x-www-form-urlencoded\r\n");
fwrite($fp, "Content-Length: " . strlen($content) . "\r\n");
fwrite($fp, "Connection: close\r\n");
fwrite($fp, "\r\n");

fwrite($fp, $content);

$iSize = null;
$bHeaderEnd = false;
$sResponse = '';
do {
    $sTmp = fgets($fp, 1024);
    $iPos = strpos($sTmp, 'Content-Length: ');
    if ($iPos !== false) {
        $iSize = (int) substr($sTmp, strlen('Content-Length: '));
    }
    if ($bHeaderEnd) {
        $sResponse.= $sTmp;
    }
    if (strlen(trim($sTmp)) == 0) {
        $bHeaderEnd = true;
    }
} while (!feof($fp) && (is_null($iSize) || !is_null($iSize) && strlen($sResponse) < $iSize));
$result = trim($sResponse);

Gördüğünüz gibi bu komut dosyası, içerik uzunluğuna ulaşılırsa eof için bekle.

umarım yardımcı olur


1

Bu soruyu Nisan 2012'de Rasmus Lerdorf'a şu makalelerden alıntı yaparak sordum:

Platforma daha fazla çıktının (stdout'ta?) Üretilmeyeceğini bildirmek için yeni bir PHP yerleşik işlevinin geliştirilmesini önerdim (böyle bir işlev bağlantının kapatılmasını sağlayabilir). Rasmus Lerdorf yanıt verdi:

Gearman'e bakın . Ön uç Web sunucularınızın bu şekilde arka uç işlemleri yapmasını gerçekten istemezsiniz.

Onun görüşünü görebiliyorum ve bazı uygulamalar / yükleme senaryoları için fikrini destekleyebiliyorum! Bununla birlikte, diğer bazı senaryolarda, vcampitelli ve diğerlerinin çözümleri iyidir.


1

Yanıtı sıkıştırıp gönderebilecek ve diğer php kodunun yürütülmesine izin verebilecek bir şeyim var.

function sendResponse($response){
    $contentencoding = 'none';
    if(ob_get_contents()){
        ob_end_clean();
        if(ob_get_contents()){
            ob_clean();
        }
    }
    header('Connection: close');
    header("cache-control: must-revalidate");
    header('Vary: Accept-Encoding');
    header('content-type: application/json; charset=utf-8');
    ob_start();
    if(phpversion()>='4.0.4pl1' && extension_loaded('zlib') && GZIP_ENABLED==1 && !empty($_SERVER["HTTP_ACCEPT_ENCODING"]) && (strpos($_SERVER["HTTP_ACCEPT_ENCODING"], 'gzip') !== false) && (strstr($GLOBALS['useragent'],'compatible') || strstr($GLOBALS['useragent'],'Gecko'))){
        $contentencoding = 'gzip';
        ob_start('ob_gzhandler');
    }
    header('Content-Encoding: '.$contentencoding);
    if (!empty($_GET['callback'])){
        echo $_GET['callback'].'('.$response.')';
    } else {
        echo $response;
    }
    if($contentencoding == 'gzip') {
        if(ob_get_contents()){
            ob_end_flush(); // Flush the output from ob_gzhandler
        }
    }
    header('Content-Length: '.ob_get_length());
    // flush all output
    if (ob_get_contents()){
        ob_end_flush(); // Flush the outer ob_start()
        if(ob_get_contents()){
            ob_flush();
        }
        flush();
    }
    if (session_id()) session_write_close();
}

0

Başka bir yaklaşım daha var ve yanıt başlıklarını değiştirmek istemiyorsanız dikkate almaya değer. Başka bir işlemde bir iş parçacığı başlatırsanız, çağrılan işlev yanıtını beklemez ve sonlandırılmış bir http kodu ile tarayıcıya geri döner. Pthread'i yapılandırmanız gerekecek .

class continue_processing_thread extends Thread 
{
     public function __construct($param1) 
     {
         $this->param1 = $param1
     }

     public function run() 
     {
        //Do your long running process here
     }
}

//This is your function called via an HTTP GET/POST etc
function rest_endpoint()
{
  //do whatever stuff needed by the response.

  //Create and start your thread. 
  //rest_endpoint wont wait for this to complete.
  $continue_processing = new continue_processing_thread($some_value);
  $continue_processing->start();

  echo json_encode($response)
}

Biz Çalıştırdığımızda $ continue_processing-> () başlamak nedenle kadarıyla rest_endpoint kabul edilir olarak PHP alışkanlık bu ipliğin dönüş sonucu için beklemek ve. Tamamdır.

Pthreads ile yardımcı olacak bazı bağlantılar

İyi şanslar.


0

Bunun eski olduğunu biliyorum ama bu noktada muhtemelen faydalıdır.

Bu cevapla asıl soruyu desteklemiyorum ama bu sorunu nasıl doğru bir şekilde çözebilirim. Umarım başkalarının bu gibi sorunları çözmesine yardımcı olur.

RabbitMQ veya benzeri hizmetleri kullanmanızı ve çalışan örneklerini kullanarak arka plan iş yükünü çalıştırmanızı öneririm . RabbitMQ'yu kullanmanız için tüm işi yapan php için amqplib adlı bir paket var .

Profesyoneller:

  1. Yüksek performanslı
  2. Güzel yapılandırılmış ve bakımı yapılabilir
  3. Çalışan örnekleriyle kesinlikle ölçeklenebilir

Negatifler:

  1. RabbitMQ sunucuda kurulu olmalıdır, bu bazı web barındırıcılarda bir sorun olabilir.
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.