ดำเนินการประมวลผล php ต่อไปหลังจากส่งการตอบกลับ http


102

สคริปต์ของฉันถูกเรียกโดยเซิร์ฟเวอร์ จากเซิร์ฟเวอร์ฉันจะได้รับและID_OF_MESSAGETEXT_OF_MESSAGE

ในสคริปต์ของฉันฉันจะจัดการข้อความขาเข้าและสร้างการตอบสนองด้วย params: ANSWER_TO_IDและRESPONSE_MESSAGE.

ปัญหาคือฉันกำลังส่งการตอบกลับไปยัง incomming "ID_OF_MESSAGE"แต่เซิร์ฟเวอร์ที่ส่งข้อความให้ฉันจัดการจะตั้งค่าข้อความของเขาว่าส่งถึงฉัน (หมายความว่าฉันสามารถส่งการตอบกลับไปยัง ID นั้นได้) หลังจากได้รับการตอบกลับ http 200

วิธีแก้ปัญหาอย่างหนึ่งคือบันทึกข้อความลงในฐานข้อมูลและสร้าง cron ซึ่งจะทำงานในแต่ละนาที แต่ฉันต้องสร้างข้อความตอบกลับทันที

มีวิธีแก้ปัญหาบางอย่างในการส่งไปยังเซิร์ฟเวอร์ http response 200 และดำเนินการต่อจากสคริปต์ php หรือไม่?

ขอบคุณมาก

คำตอบ:


192

ใช่. คุณสามารถทำได้:

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
เป็นไปได้ไหมที่จะทำด้วยการเชื่อมต่อที่มีชีวิตอยู่?
Congelli501

3
ยอดเยี่ยม !! นี่เป็นการตอบคำถามเดียวที่ใช้งานได้จริง !!! 10p +
Martin_Lakes

3
มีเหตุผลที่คุณใช้ ob_flush หลังจาก ob_end_flush หรือไม่ ฉันเข้าใจถึงความจำเป็นในการใช้ฟังก์ชันล้างที่นั่นในตอนท้าย แต่ฉันไม่แน่ใจว่าทำไมคุณถึงต้องมีการเรียกใช้ ob_flush โดยที่ ob_end_flush ถูกเรียก
ars265

9
โปรดทราบว่าหากตั้งค่าส่วนหัวการเข้ารหัสเนื้อหาเป็นอย่างอื่นที่ไม่ใช่ 'ไม่มี' มันอาจทำให้ตัวอย่างนี้ไร้ประโยชน์เนื่องจากยังคงปล่อยให้ผู้ใช้รอเวลาดำเนินการเต็มรูปแบบ (จนกว่าจะหมดเวลา?) ดังนั้นเพื่อให้แน่ใจว่ามันจะใช้งานได้ในท้องถิ่นและในสภาพแวดล้อมการใช้งานจริงให้ตั้งค่าส่วนหัว "การเข้ารหัสเนื้อหา" เป็น "ไม่มี":header("Content-Encoding: none")
Brian

18
เคล็ดลับ: ฉันเริ่มใช้ PHP-FPM ดังนั้นฉันจึงต้องเพิ่มfastcgi_finish_request()ในตอนท้าย
vcampitelli

44

ฉันเห็นคำตอบมากมายในที่นี่ซึ่งแนะนำให้ใช้ignore_user_abort(true);แต่รหัสนี้ไม่จำเป็น ทั้งหมดนี้เพื่อให้แน่ใจว่าสคริปต์ของคุณยังคงดำเนินการต่อก่อนที่จะมีการตอบกลับในกรณีที่ผู้ใช้ยกเลิก (โดยปิดเบราว์เซอร์หรือกด Escape เพื่อหยุดการร้องขอ) แต่นั่นไม่ใช่สิ่งที่คุณถาม คุณกำลังขอให้ดำเนินการต่อหลังจากมีการส่งการตอบกลับ สิ่งที่คุณต้องการมีดังต่อไปนี้:

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

หากคุณกังวลว่าการทำงานเบื้องหลังของคุณจะใช้เวลานานกว่าขีด จำกัด เวลาการเรียกใช้สคริปต์เริ่มต้นของ PHP ให้ติด set_time_limit(0);ที่ด้านบนสุด


3
ลองชุดต่างๆมากมายนี่คือชุดที่ใช้ได้ !!! ขอบคุณ Kosta Kontos !!!
Martin_Lakes

1
ทำงานได้อย่างสมบูรณ์บน apache 2, php 7.0.32 และ ubuntu 16.04! ขอบคุณ!
KyleBunga

1
ฉันได้ลองวิธีแก้ปัญหาอื่น ๆ แล้วและมีเพียงวิธีนี้เท่านั้นที่ใช้ได้กับฉัน ลำดับของบรรทัดมีความสำคัญเช่นกัน
Sinisa

32

หากคุณใช้การประมวลผล FastCGI หรือ PHP-FPM คุณสามารถ:

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

ที่มา: https://www.php.net/manual/en/function.fastcgi-finish-request.php

ปัญหา PHP # 68722: https://bugs.php.net/bug.php?id=68772


2
ขอบคุณสำหรับสิ่งนี้หลังจากใช้เวลาสองสามชั่วโมงสิ่งนี้ได้ผลสำหรับฉันใน nginx
Ehsan

7
dat sum การคำนวณราคาแพง tho: o ประทับใจมากแพงจัง!
Friedrich Roell

ขอบคุณ DarkNeuron! คำตอบที่ดีสำหรับเราโดยใช้ php-fpm ช่วยแก้ปัญหาของฉันได้!
Sinisa

21

ฉันใช้เวลาสองสามชั่วโมงกับปัญหานี้และฉันได้มาพร้อมกับฟังก์ชั่นนี้ซึ่งใช้งานได้กับ Apache และ 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();
}

คุณสามารถเรียกใช้ฟังก์ชันนี้ก่อนการประมวลผลที่ยาวนานของคุณ


2
นี่มันน่ารักมาก! หลังจากลองทำทุกอย่างข้างต้นแล้วนี่เป็นสิ่งเดียวที่ใช้ได้กับ nginx
เครื่องเทศ

รหัสของคุณเกือบจะเหมือนกับรหัสที่นี่แต่โพสต์ของคุณเก่ากว่า :) +1
Accountant م

ระวังด้วยfilter_inputฟังก์ชั่นจะคืนค่า NULL ในบางครั้ง ดูรายละเอียดการสนับสนุนของผู้ใช้
นักบัญชี

4

แก้ไขคำตอบโดย @vcampitelli เล็กน้อย อย่าคิดว่าคุณต้องการcloseส่วนหัว ฉันเห็นส่วนหัวปิดที่ซ้ำกันใน 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
ฉันพูดถึงสิ่งนี้ในคำตอบเดิม แต่ฉันจะพูดที่นี่ด้วย คุณไม่จำเป็นต้องปิดการเชื่อมต่อ แต่สิ่งที่จะเกิดขึ้นคือเนื้อหาถัดไปที่ร้องขอในการเชื่อมต่อเดียวกันจะถูกบังคับให้รอ ดังนั้นคุณสามารถส่ง HTML ได้อย่างรวดเร็ว แต่ไฟล์ JS หรือ CSS ไฟล์ใดไฟล์หนึ่งของคุณอาจโหลดช้าเนื่องจากการเชื่อมต่อต้องได้รับการตอบสนองจาก PHP ให้เสร็จสิ้นก่อนจึงจะได้รับเนื้อหาถัดไป ด้วยเหตุนี้การปิดการเชื่อมต่อจึงเป็นความคิดที่ดีดังนั้นเบราว์เซอร์จึงไม่ต้องรอให้มันถูกปล่อย
Nate Lampton

3

ฉันใช้ฟังก์ชัน php register_shutdown_function สำหรับสิ่งนี้

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

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

แก้ไข : ข้างต้นใช้งานไม่ได้ ดูเหมือนว่าฉันจะเข้าใจผิดจากเอกสารเก่า ๆ พฤติกรรมของ register_shutdown_function เปลี่ยนไปตั้งแต่ลิงค์ ลิงค์ PHP 4.1


นี่ไม่ใช่สิ่งที่ถูกถาม - โดยพื้นฐานแล้วฟังก์ชันนี้จะขยายเหตุการณ์การสิ้นสุดสคริปต์และยังคงเป็นส่วนหนึ่งของบัฟเฟอร์เอาต์พุต
ฟิสก์

1
พบว่ามันคุ้มค่ากับการโหวตเพิ่มขึ้นเพราะมันแสดงให้เห็นว่าอะไรไม่ได้ผล
Trendfischer

1
Ditto - การเพิ่มคะแนนเพราะฉันคาดหวังว่าจะเห็นสิ่งนี้เป็นคำตอบและมีประโยชน์เมื่อเห็นว่ามันไม่ได้ผล
HappyDog

2

ฉันไม่สามารถติดตั้ง pthread และวิธีแก้ปัญหาก่อนหน้านี้ไม่ได้ผลสำหรับฉัน ฉันพบเพียงวิธีแก้ปัญหาต่อไปนี้เท่านั้นที่ใช้งานได้ (อ้างอิง: 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 การปิดการเชื่อมต่อไม่เพียงพอ php ยังคงรอ eof witch ส่งโดยเซิร์ฟเวอร์

ทางออกของฉันคืออ่าน 'ความยาวเนื้อหา:'

นี่คือตัวอย่าง:

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

สังเกต "\ n" เพื่อตอบสนองต่อบรรทัดปิดถ้าไม่ใช่ fget อ่านขณะรอ 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);

ดังที่คุณเห็นสคริปต์นี้ควรรอประมาณ eof หากความยาวเนื้อหาถึง

หวังว่ามันจะช่วยได้


1

ฉันถามคำถามนี้กับ Rasmus Lerdorf ในเดือนเมษายน 2555 โดยอ้างถึงบทความเหล่านี้:

ฉันแนะนำให้พัฒนาฟังก์ชันในตัว PHP ใหม่เพื่อแจ้งให้แพลตฟอร์มทราบว่าจะไม่มีการสร้างเอาต์พุตเพิ่มเติม (บน stdout?) (ฟังก์ชันดังกล่าวอาจดูแลการปิดการเชื่อมต่อ) Rasmus Lerdorf ตอบ:

ดูGearman คุณไม่ต้องการให้เซิร์ฟเวอร์เว็บส่วนหน้าของคุณทำการประมวลผลแบ็กเอนด์เช่นนี้

ฉันเห็นประเด็นของเขาและสนับสนุนความคิดเห็นของเขาสำหรับบางแอปพลิเคชัน / สถานการณ์การโหลด! อย่างไรก็ตามภายใต้สถานการณ์อื่น ๆ โซลูชันจาก vcampitelli et al เป็นสิ่งที่ดี


1

ฉันมีบางอย่างที่สามารถบีบอัดและส่งการตอบสนองและปล่อยให้โค้ด php อื่นดำเนินการได้

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

มีอีกวิธีหนึ่งที่คุ้มค่าในการพิจารณาหากคุณไม่ต้องการยุ่งเกี่ยวกับส่วนหัวการตอบกลับ หากคุณเริ่มเธรดในกระบวนการอื่นฟังก์ชันที่เรียกว่าจะไม่รอการตอบสนองและจะกลับไปที่เบราว์เซอร์พร้อมกับโค้ด http ที่สรุปแล้ว คุณจะต้องกำหนดค่า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)
}

เมื่อเราดำเนินการ$ continue_processing-> start () PHP จะไม่รอผลลัพธ์ของเธรดนี้ดังนั้นเท่าที่พิจารณา rest_endpoint เป็นอันเสร็จเรียบร้อย

ลิงค์บางส่วนเพื่อช่วยในการ pthreads

โชคดี.


0

ฉันรู้ว่ามันเก่า แต่อาจมีประโยชน์ในตอนนี้

ด้วยคำตอบนี้ฉันไม่สนับสนุนคำถามจริง แต่จะแก้ปัญหานี้ได้อย่างไร หวังว่าจะช่วยคนอื่น ๆ ในการแก้ปัญหาเช่นนี้

ฉันขอแนะนำให้ใช้RabbitMQหรือบริการที่คล้ายกันและเรียกใช้ภาระงานพื้นหลังโดยใช้อินสแตนซ์ของผู้ปฏิบัติงาน มีแพ็คเกจที่เรียกว่าamqplibสำหรับ php ซึ่งทำทุกอย่างให้คุณใช้ RabbitMQ

ข้อดี:

  1. มันมีประสิทธิภาพสูง
  2. มีโครงสร้างที่สวยงามและบำรุงรักษาได้
  3. สามารถปรับขนาดได้อย่างสมบูรณ์ด้วยอินสแตนซ์ของผู้ปฏิบัติงาน

Neg's:

  1. ต้องติดตั้ง RabbitMQ บนเซิร์ฟเวอร์ซึ่งอาจเป็นปัญหากับผู้ให้บริการเว็บบางราย
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.