tiếp tục xử lý php sau khi gửi phản hồi http


101

Tập lệnh của tôi được gọi bởi máy chủ. Từ máy chủ, tôi sẽ nhận được ID_OF_MESSAGETEXT_OF_MESSAGE.

Trong tập lệnh của tôi, tôi sẽ xử lý văn bản đến và tạo phản hồi bằng các tham số: ANSWER_TO_IDRESPONSE_MESSAGE.

Vấn đề là tôi đang gửi phản hồi tới incomming "ID_OF_MESSAGE", nhưng máy chủ gửi tin nhắn cho tôi để xử lý sẽ đặt tin nhắn của anh ấy là đã gửi cho tôi (Có nghĩa là tôi có thể gửi phản hồi cho anh ấy tới ID đó), sau khi nhận được phản hồi http 200.

Một trong những giải pháp là lưu thông báo vào cơ sở dữ liệu và tạo một số cron sẽ chạy mỗi phút, nhưng tôi cần tạo thông báo phản hồi ngay lập tức.

Có một số giải pháp làm thế nào để gửi phản hồi http 200 đến máy chủ và tiếp tục thực thi tập lệnh php?

Cảm ơn bạn rất nhiều

Câu trả lời:


191

Đúng. Bạn có thể làm được việc này:

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
Có thể làm điều đó với một kết nối duy trì?
Congelli501

3
Thông minh!! Đây là câu trả lời duy nhất cho câu hỏi này thực sự hoạt động !!! 10p +
Martin_Lakes

3
có lý do gì bạn sử dụng ob_flush sau ob_end_flush? Tôi hiểu sự cần thiết của hàm xả ở đó nhưng tôi không chắc tại sao bạn lại cần ob_flush với ob_end_flush được gọi.
ars265

9
Xin lưu ý rằng nếu tiêu đề mã hóa nội dung được đặt thành bất kỳ thứ gì khác ngoài 'none', nó có thể làm cho ví dụ này trở nên vô dụng vì nó vẫn để người dùng đợi toàn bộ thời gian thực thi (cho đến khi hết thời gian?). Vì vậy, để hoàn toàn chắc chắn rằng nó sẽ hoạt động cục bộ trong môi trường sản xuất, hãy đặt tiêu đề 'mã hóa nội dung' thành 'không':header("Content-Encoding: none")
Brian

17
Mẹo: Tôi bắt đầu sử dụng PHP-FPM, vì vậy tôi đã phải thêm fastcgi_finish_request()vào cuối
vcampitelli

44

Tôi đã thấy rất nhiều câu trả lời trên đây đề nghị sử dụng ignore_user_abort(true);nhưng mã này không cần thiết. Tất cả những điều này là đảm bảo tập lệnh của bạn tiếp tục thực thi trước khi phản hồi được gửi trong trường hợp người dùng hủy bỏ (bằng cách đóng trình duyệt của họ hoặc nhấn phím thoát để dừng yêu cầu). Nhưng đó không phải là những gì bạn đang hỏi. Bạn đang yêu cầu tiếp tục thực hiện SAU KHI phản hồi được gửi. Tất cả những gì bạn cần là:

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

Nếu bạn lo lắng rằng công việc nền của bạn sẽ mất nhiều thời gian hơn giới hạn thời gian thực thi tập lệnh mặc định của PHP, thì hãy chọn set_time_limit(0);ở trên cùng.


3
Đã thử rất nhiều cách kết hợp khác nhau, ĐÂY là cách kết hợp hiệu quả !!! Cảm ơn Kosta Kontos !!!
Martin_Lakes

1
Hoạt động hoàn hảo trên apache 2, php 7.0.32 và ubuntu 16.04! Cảm ơn!
KyleBunga

1
Tôi đã thử các giải pháp khác và chỉ giải pháp này phù hợp với tôi. Thứ tự của các dòng cũng quan trọng.
Sinisa

32

Nếu bạn đang sử dụng xử lý FastCGI hoặc PHP-FPM, bạn có thể:

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

Nguồn: https://www.php.net/manual/en/ Chức năng.fastcgi-finish-request.php

Sự cố PHP # 68722: https://bugs.php.net/bug.php?id=68772


2
Cảm ơn cho điều này, sau khi trải qua một vài tiếng đồng hồ này làm việc cho tôi trong nginx
Ehsan

7
dat tổng tính toán đắt tho: o nhiều ấn tượng, đắt như vậy!
Friedrich Roell

Cảm ơn DarkNeuron! Câu trả lời tuyệt vời cho chúng tôi bằng cách sử dụng php-fpm, vừa giải quyết được vấn đề của tôi!
Sinisa

21

Tôi đã dành vài giờ cho vấn đề này và tôi đã đi kèm với chức năng này hoạt động trên Apache và 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();
}

Bạn có thể gọi hàm này trước khi xử lý lâu.


2
Đây là máu đáng yêu! Sau khi thử mọi thứ khác ở trên, đây là thứ duy nhất hoạt động với nginx.
gia vị

Mã của bạn gần giống hệt như mã ở đây nhưng bài đăng của bạn cũ hơn :) +1
Kế toán م

hãy cẩn thận với filter_inputhàm đôi khi nó trả về NULL. xem đóng góp của người dùng này để biết chi tiết
Kế toán م

4

Đã sửa đổi câu trả lời của @vcampitelli một chút. Đừng nghĩ rằng bạn cần closetiêu đề. Tôi đã thấy các tiêu đề gần giống nhau trong 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
Tôi đã đề cập điều này trong câu trả lời ban đầu, nhưng tôi cũng sẽ nói nó ở đây. Bạn không nhất thiết phải đóng kết nối, nhưng sau đó điều gì sẽ xảy ra là nội dung tiếp theo được yêu cầu trên cùng một kết nối sẽ buộc phải đợi. Vì vậy, bạn có thể phân phối HTML nhanh chóng nhưng sau đó một trong các tệp JS hoặc CSS của bạn có thể tải chậm, vì kết nối phải hoàn thành việc nhận phản hồi từ PHP trước khi nó có thể nhận được nội dung tiếp theo. Vì vậy, vì lý do đó, đóng kết nối là một ý kiến ​​hay để trình duyệt không phải đợi nó được giải phóng.
Nate Lampton

3

Tôi sử dụng chức năng php register_shutdown_ Chức năng cho việc này.

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

http://php.net/manual/en/ Chức năng.register-shutdown- Chức năng.php

Chỉnh sửa : Ở trên không hoạt động. Có vẻ như tôi đã bị đánh lừa bởi một số tài liệu cũ. Hành vi của register_shutdown_ Chức năng đã thay đổi kể từ liên kết liên kết PHP 4.1


Đây không phải là những gì đang được yêu cầu - chức năng này về cơ bản chỉ mở rộng sự kiện kết thúc tập lệnh và vẫn là một phần của bộ đệm đầu ra.
fisk

1
Tìm thấy nó đáng để ủng hộ, vì nó hiển thị những gì không hoạt động.
Trendfischer

1
Ditto - ủng hộ vì tôi đã mong đợi xem đây là một câu trả lời và hữu ích khi thấy rằng nó không hoạt động.
HappyDog

2

Tôi không thể cài đặt pthread và các giải pháp trước đó cũng không hoạt động với tôi. Tôi chỉ tìm thấy giải pháp sau để hoạt động (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

trong trường hợp sử dụng php file_get_contents, việc đóng kết nối là không đủ. php vẫn chờ eof witch gửi qua máy chủ.

giải pháp của tôi là đọc 'Nội dung-Độ dài:'

đây là mẫu:

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

Lưu ý "\ n" để phản hồi lại dòng đóng, nếu không phải là tiện ích đọc trong khi chờ đợi.

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

Như bạn có thể thấy tập lệnh này, hãy chờ khoảng thời gian ngắn nếu đạt đến độ dài nội dung.

hy vọng nó sẽ giúp


1

Tôi đã hỏi câu hỏi này với Rasmus Lerdorf vào tháng 4 năm 2012, trích dẫn các bài báo sau:

Tôi đã đề xuất phát triển một hàm tích hợp PHP mới để thông báo cho nền tảng rằng không có đầu ra nào nữa (trên stdout?) Sẽ được tạo (một hàm như vậy có thể đảm nhiệm việc đóng kết nối). Rasmus Lerdorf trả lời:

Xem Gearman . Bạn thực sự không muốn các máy chủ Web giao diện người dùng của mình xử lý phụ trợ như thế này.

Tôi có thể thấy quan điểm của anh ấy và ủng hộ ý kiến ​​của anh ấy đối với một số ứng dụng / kịch bản tải! Tuy nhiên, trong một số kịch bản khác, các giải pháp từ vcampitelli và cộng sự, là những giải pháp tốt.


1

Tôi có một cái gì đó có thể nén và gửi phản hồi và để mã php khác thực thi.

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

Có một cách tiếp cận khác và đáng để cân nhắc nếu bạn không muốn làm xáo trộn các tiêu đề phản hồi. Nếu bạn bắt đầu một chuỗi trên một tiến trình khác, hàm được gọi sẽ không đợi phản hồi của nó và sẽ quay lại trình duyệt với mã http đã hoàn thiện. Bạn sẽ cần phải cấu hình 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)
}

Khi chúng ta thực thi $ continue_processing-> start () PHP sẽ không đợi kết quả trả về của luồng này và do đó, theo như rest_endpoint được xem xét. Nó được thực hiện.

Một số liên kết để trợ giúp với pthreads

Chúc may mắn.


0

Tôi biết nó là một cái cũ, nhưng có thể hữu ích vào thời điểm này.

Với câu trả lời này, tôi không hỗ trợ câu hỏi thực tế nhưng làm thế nào để giải quyết vấn đề này một cách chính xác. Hy vọng nó sẽ giúp những người khác để giải quyết vấn đề như thế này.

Tôi khuyên bạn nên sử dụng RabbitMQ hoặc các dịch vụ tương tự và chạy khối lượng công việc nền bằng các phiên bản worker . Có một gói gọi là amqplib cho php, gói này thực hiện tất cả công việc để bạn sử dụng RabbitMQ.

Pro's:

  1. Nó có hiệu suất cao
  2. Có cấu trúc đẹp và có thể bảo trì
  3. Nó hoàn toàn có thể mở rộng với các phiên bản worker

Người da đen:

  1. RabbitMQ phải được cài đặt trên máy chủ, đây có thể là vấn đề với một số web hoster.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.