lanjutkan proses php setelah mengirim respon http


101

Skrip saya dipanggil oleh server. Dari server saya akan menerima ID_OF_MESSAGEdan TEXT_OF_MESSAGE.

Dalam skrip saya, saya akan menangani teks yang masuk dan menghasilkan respons dengan params: ANSWER_TO_IDdan RESPONSE_MESSAGE.

Masalahnya adalah saya mengirim tanggapan ke incomming "ID_OF_MESSAGE", tetapi server yang mengirimi saya pesan untuk ditangani akan mengatur pesannya sebagai dikirim kepada saya (Artinya saya dapat mengirimnya tanggapan ke ID itu), setelah menerima tanggapan http 200.

Salah satu solusinya adalah menyimpan pesan ke database dan membuat beberapa cron yang akan berjalan setiap menit, tetapi saya perlu segera menghasilkan pesan tanggapan.

Apakah ada solusi bagaimana mengirim ke server http response 200 dan kemudian melanjutkan menjalankan script php?

Terima kasih banyak

Jawaban:


191

Iya. Kamu bisa melakukan ini:

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
Apakah mungkin melakukannya dengan koneksi yang tetap hidup?
Congelli501

3
Luar biasa !! Ini adalah satu-satunya jawaban untuk pertanyaan ini yang benar-benar berfungsi !!! 10p +
Martin_Lakes

3
apakah ada alasan Anda menggunakan ob_flush setelah ob_end_flush? Saya memahami perlunya fungsi flush di sana pada akhirnya tetapi saya tidak yakin mengapa Anda perlu ob_flush dengan pemanggilan ob_end_flush.
ars265

9
Harap perhatikan bahwa jika header encoding konten disetel ke selain 'tidak ada', contoh ini dapat menjadi tidak berguna karena tetap membiarkan pengguna menunggu waktu eksekusi penuh (hingga waktu tunggu habis?). Jadi untuk benar-benar yakin ini akan bekerja secara lokal dan di lingkungan produksi, setel header 'content-encoding' ke 'none':header("Content-Encoding: none")
Brian

17
Tip: Saya mulai menggunakan PHP-FPM, jadi saya harus menambahkan fastcgi_finish_request()di bagian akhir
vcampitelli

44

Saya telah melihat banyak tanggapan di sini yang menyarankan penggunaan ignore_user_abort(true);tetapi kode ini tidak diperlukan. Semua ini dilakukan untuk memastikan skrip Anda terus berjalan sebelum respons dikirim jika pengguna membatalkan (dengan menutup browser mereka atau menekan escape untuk menghentikan permintaan). Tapi bukan itu yang Anda tanyakan. Anda meminta untuk melanjutkan eksekusi SETELAH tanggapan dikirim. Yang Anda butuhkan hanyalah sebagai berikut:

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

Jika Anda khawatir bahwa pekerjaan latar belakang Anda akan memakan waktu lebih lama dari batas waktu eksekusi skrip default PHP, maka tetaplah set_time_limit(0);di bagian atas.


3
Mencoba BANYAK kombinasi yang berbeda, INI adalah salah satu yang berhasil !!! Terima kasih Kosta Kontos !!!
Martin_Lakes

1
Bekerja dengan sempurna di apache 2, php 7.0.32 dan ubuntu 16.04! Terima kasih!
KyleBunga

1
Saya sudah mencoba solusi lain, dan hanya yang ini yang berhasil untuk saya. Urutan garis juga penting.
Sinisa

32

Jika Anda menggunakan pemrosesan FastCGI atau PHP-FPM, Anda dapat:

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

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

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


2
Terima kasih untuk ini, setelah menghabiskan beberapa jam ini bekerja untuk saya di nginx
Ehsan

7
dat sum perhitungan mahal tho: o sangat terkesan, mahal sekali!
Friedrich Roell

Terima kasih DarkNeuron! Jawaban bagus bagi kami yang menggunakan php-fpm, baru saja menyelesaikan masalah saya!
Sinisa

21

Saya menghabiskan beberapa jam untuk masalah ini dan saya datang dengan fungsi ini yang berfungsi pada Apache dan Nginx:

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

Anda dapat memanggil fungsi ini sebelum pemrosesan panjang Anda.


2
Ini sangat indah! Setelah mencoba semua hal lain di atas, ini adalah satu-satunya hal yang berfungsi dengan nginx.
rempah

Kode Anda hampir persis sama dengan kode di sini tetapi kiriman Anda lebih lama :) +1
Akuntan م

hati-hati dengan filter_inputfungsinya kadang-kadang mengembalikan NULL. lihat kontribusi pengguna ini untuk detail
Akuntan م

4

Sedikit mengubah jawaban oleh @vcampitelli. Jangan berpikir Anda membutuhkan closeheader. Saya melihat header dekat duplikat di Chrome.

<?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
Saya menyebutkan ini dalam jawaban asli, tetapi saya juga akan mengatakannya di sini. Anda tidak perlu harus menutup koneksi, tetapi yang akan terjadi adalah aset berikutnya yang diminta pada koneksi yang sama akan dipaksa untuk menunggu. Jadi Anda dapat mengirimkan HTML dengan cepat, tetapi salah satu file JS atau CSS Anda mungkin dimuat dengan lambat, karena koneksi harus menyelesaikan respons dari PHP sebelum bisa mendapatkan aset berikutnya. Jadi untuk alasan itu, menutup koneksi adalah ide yang bagus agar browser tidak perlu menunggu untuk dibebaskan.
Nate Lampton

3

Saya menggunakan fungsi register_shutdown_function php untuk ini.

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

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

Edit : Di atas tidak berfungsi. Sepertinya saya disesatkan oleh beberapa dokumentasi lama. Perilaku register_shutdown_function telah berubah sejak link link PHP 4.1


Ini bukan yang diminta - fungsi ini pada dasarnya hanya memperluas peristiwa penghentian skrip dan masih menjadi bagian dari buffer keluaran.
fisk

1
Merasa layak mendapat upvote, karena itu menunjukkan apa yang tidak berfungsi.
Trendfischer

1
Ditto - upvoting karena saya berharap melihat ini sebagai jawaban, dan berguna untuk melihat bahwa itu tidak berhasil.
HappyDog

2

Saya tidak dapat menginstal pthread dan tidak ada solusi sebelumnya yang berfungsi untuk saya. Saya hanya menemukan solusi berikut untuk berfungsi (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

dalam kasus penggunaan file_get_contents php, koneksi dekat tidak cukup. php masih menunggu eof witch dikirim oleh server.

solusi saya adalah membaca 'Panjang-Konten:'

berikut ini contohnya:

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

Perhatikan "\ n" dalam menanggapi baris penutup, jika tidak fget dibaca sambil menunggu eof.

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

Seperti yang Anda lihat, dosis skrip ini menunggu tentang eof jika panjang konten tercapai.

semoga membantu


1

Saya mengajukan pertanyaan ini kepada Rasmus Lerdorf pada bulan April 2012, mengutip artikel berikut:

Saya menyarankan pengembangan fungsi built-in PHP baru untuk memberi tahu platform bahwa tidak ada output lebih lanjut (pada stdout?) Yang akan dihasilkan (fungsi seperti itu mungkin menangani penutupan koneksi). Rasmus Lerdorf menjawab:

Lihat Gearman . Anda benar-benar tidak ingin server Web frontend Anda melakukan pemrosesan backend seperti ini.

Saya dapat memahami maksudnya, dan mendukung pendapatnya untuk beberapa skenario aplikasi / pemuatan! Namun, dalam beberapa skenario lain, solusi dari vcampitelli et al, adalah solusi yang baik.


1

Saya memiliki sesuatu yang dapat dikompresi dan mengirim respons dan membiarkan kode php lain dijalankan.

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

Ada pendekatan lain dan pertimbangan yang berharga jika Anda tidak ingin merusak header respons. Jika Anda memulai utas pada proses lain, fungsi yang dipanggil tidak akan menunggu tanggapannya dan akan kembali ke browser dengan kode http yang telah diselesaikan. Anda perlu mengkonfigurasi pthread .

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

Setelah kita menjalankan $ continue_processing-> start () PHP tidak akan menunggu hasil kembalian dari utas ini dan oleh karena itu sejauh rest_endpoint dianggap. Selesai.

Beberapa tautan untuk membantu dengan pthreads

Semoga berhasil.


0

Saya tahu ini sudah lama, tapi mungkin berguna saat ini.

Dengan jawaban ini saya tidak mendukung pertanyaan yang sebenarnya tetapi bagaimana mengatasi masalah ini dengan benar. Semoga bisa membantu orang lain untuk memecahkan masalah seperti ini.

Saya akan menyarankan untuk menggunakan RabbitMQ atau layanan serupa dan menjalankan beban kerja latar belakang menggunakan instance pekerja . Ada sebuah paket bernama amqplib untuk php yang melakukan semua pekerjaan bagi Anda untuk menggunakan RabbitMQ.

Pro:

  1. Berkinerja tinggi
  2. Terstruktur dengan baik dan dapat dipelihara
  3. Ini benar-benar dapat diskalakan dengan instance pekerja

Neg ini:

  1. RabbitMQ harus diinstal di server, ini bisa menjadi masalah dengan beberapa penghosting web.
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.