developer tip

http 응답을 보낸 후 PHP 처리 계속

copycodes 2020. 8. 31. 08:00
반응형

http 응답을 보낸 후 PHP 처리 계속


내 스크립트는 서버에서 호출됩니다. 서버에서 수신 ID_OF_MESSAGE하고 TEXT_OF_MESSAGE.

내 스크립트에서 들어오는 텍스트를 처리하고 params : ANSWER_TO_IDRESPONSE_MESSAGE.

문제는 내가 incomming에 응답을 보내고 "ID_OF_MESSAGE"있지만 처리 할 메시지를 보내는 서버는 http 응답 200을 수신 한 후 자신의 메시지를 나에게 전달 된 것으로 설정합니다 (즉, 해당 ID에 대한 응답을 보낼 수 있음을 의미합니다).

해결책 중 하나는 메시지를 데이터베이스에 저장하고 매분 실행될 크론을 만드는 것이지만, 즉시 응답 메시지를 생성해야합니다.

서버 http 응답 200으로 보내는 방법과 php 스크립트를 계속 실행하는 방법이 있습니까?

정말 고마워


예. 다음과 같이 할 수 있습니다.

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

여기에서 사용을 제안하는 많은 응답을 ignore_user_abort(true);보았지만이 코드는 필요하지 않습니다. 이 모든 작업은 사용자가 중단 한 경우 응답이 전송되기 전에 스크립트가 계속 실행되도록하는 것입니다 (브라우저를 닫거나 Esc 키를 눌러 요청을 중지). 그러나 그것은 당신이 요구하는 것이 아닙니다. 응답이 전송 된 후 실행을 계속하도록 요청합니다. 필요한 것은 다음과 같습니다.

    // 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);맨 위에 머 무르 십시오.


FastCGI 처리 또는 PHP-FPM을 사용하는 경우 다음을 수행 할 수 있습니다.

session_write_close(); //close the session
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


이 문제에 대해 몇 시간을 보냈고 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();
}

긴 처리 전에이 함수를 호출 할 수 있습니다.


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

이를 위해 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 링크 링크 이후 변경되었습니다.


php file_get_contents 사용의 경우 연결 종료만으로는 충분하지 않습니다. PHP는 여전히 서버에서 eof 마녀 보내기를 기다립니다.

내 해결책은 'Content-Length :'를 읽는 것입니다.

다음은 샘플입니다.

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

fget이 eof를 기다리는 동안 읽지 않으면 닫기 라인에 대한 응답으로 "\ n"을 참고하십시오.

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

As you can see this script dosent wait about eof if content length is reach.

hope it will help


I asked this question to Rasmus Lerdorf in April 2012, citing these articles:

I suggested the development of a new PHP built-in function to notify the platform that no further output (on stdout?) will be generated (such a function might take care of closing the connection). Rasmus Lerdorf responded:

See Gearman. You really really don't want your frontend Web servers doing backend processing like this.

I can see his point, and support his opinion for some applications/ loading scenarios! However, under some other scenarios, the solutions from vcampitelli et al, are good ones.


I have something that can compressed and send the response and let other php code to execute.

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

I can't install pthread and neither the previous solutions work for me. I found only the following solution to work (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');
?>

There is another approach and its worthwhile considering if you don't want to tamper with the response headers. If you start a thread on another process the called function wont wait for its response and will return to the browser with a finalized http code. You will need to configure 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)
}

Once we execute $continue_processing->start() PHP wont wait for the return result of this thread and therefore as far as rest_endpoint is considered. It is done.

Some links to help with pthreads

Good luck.


I know it's an old one, but possibly usefull at this point.

With this answer i don't support the actual question but how to solve this problem correctly. Hope it helps other people to solve problems like this.

I would suggest to use RabbitMQ or similar services and run the background workload using worker instances. There is a package called amqplib for php which does all the work for you to use RabbitMQ.

Pro's:

  1. It's high performant
  2. Nicly structured and maintainable
  3. It's absolutly scalable with worker instances

Neg's:

  1. RabbitMQ must be installed on the server, this can be a problem with some web hoster.

참고URL : https://stackoverflow.com/questions/15273570/continue-processing-php-after-sending-http-response

반응형