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


101

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

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

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

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

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

정말 고마워

답변:


191

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

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 2014-08-01

3
ob_end_flush 후에 ob_flush를 사용하는 이유가 있습니까? 마지막에 플러시 기능의 필요성을 이해하지만 ob_end_flush가 호출되는 ob_flush가 필요한 이유를 잘 모르겠습니다.
ars265

9
콘텐츠 인코딩 헤더가 'none'이외의 값으로 설정된 경우 사용자가 전체 실행 시간 (시간 초과까지?)을 기다릴 수 있기 때문에이 예제를 쓸모 없게 만들 수 있습니다. 따라서 프로덕션 환경에서 로컬 및 로컬로 작동하는지 확인하려면 'content-encoding'헤더를 'none'으로 설정하십시오.header("Content-Encoding: none")
Brian

17
팁 : 내가 추가했다, 그래서 나는, PHP-FPM를 사용하기 시작 fastcgi_finish_request()끝에서
vcampitelli

44

여기에서 사용을 제안하는 많은 응답을 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);맨 위에 머 무르 십시오.


3
많은 다른 조합을 시도했습니다. 이것은 작동하는 것입니다 !!! 감사합니다 Kosta Kontos !!!
Martin_Lakes 2016-08-05

1
아파치 2, PHP 7.0.32 및 우분투 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와 함께 작동하는 유일한 것입니다.
spice

귀하의 코드는 여기에 있는 코드와 거의 동일 하지만 게시물이 더 오래되었습니다. :) +1
Accountant م

filter_input때때로 NULL을 반환 하는 함수에 주의하십시오 . 자세한 내용은이 사용자 기여도 를 참조 하세요.
Accountant م

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 2015 년

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


이것은 요구되는 것이 아닙니다.이 함수는 기본적으로 스크립트 종료 이벤트를 확장하고 여전히 출력 버퍼의 일부입니다.
fisk

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 마녀 보내기를 기다립니다.

내 해결책은 '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);

보시다시피이 스크립트는 콘텐츠 길이에 도달하면 eof를 기다리지 않습니다.

도움이 되길 바랍니다


1

나는 2012 년 4 월에 Rasmus Lerdorf에게 다음 기사를 인용하면서이 질문을했습니다.

나는 더 이상 출력 (표준 출력에서?)이 생성되지 않을 것이라는 것을 플랫폼에 알리기 위해 새로운 PHP 내장 함수의 개발을 제안했습니다 (이러한 함수는 연결을 닫는 것을 처리 할 수 ​​있습니다). Rasmus Lerdorf는 다음과 같이 응답했습니다.

Gearman을 참조하십시오 . 당신은 정말로 당신의 프론트 엔드 웹 서버가 이와 같은 백엔드 처리를하는 것을 원하지 않습니다.

나는 그의 요점을 볼 수 있으며 일부 응용 프로그램 /로드 시나리오에 대한 그의 의견을지지합니다! 그러나 다른 시나리오에서는 vcampitelli 등의 솔루션이 좋은 솔루션입니다.


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가 고려됩니다. 끝났습니다.

pthread에 도움이되는 일부 링크

행운을 빕니다.


0

나는 그것이 오래된 것임을 알고 있지만 현재로서는 유용 할 수 있습니다.

이 답변으로 실제 질문을 지원하지는 않지만이 문제를 올바르게 해결하는 방법. 다른 사람들이 이와 같은 문제를 해결하는 데 도움이되기를 바랍니다.

RabbitMQ 또는 유사한 서비스를 사용하고 작업자 인스턴스를 사용하여 백그라운드 워크로드를 실행하는 것이 좋습니다 . RabbitMQ를 사용하기위한 모든 작업을 수행하는 php 용 amqplib 라는 패키지 가 있습니다.

프로 :

  1. 고성능입니다
  2. 멋지게 구조화되고 유지 관리 가능
  3. 작업자 인스턴스로 절대적으로 확장 가능

네그 :

  1. RabbitMQ가 서버에 설치되어 있어야합니다. 이는 일부 웹 호스팅 업체에 문제가 될 수 있습니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.