How to continue running a script, even after sending the data to the browser?

9

When I asked the question What is the solution for asynchronous processes in PHP? , basically what I had in mind was this question:

  

"Is there a way to complete a request from a client, but leave a process running in the same script that serves this client, such as a record in the database followed by an email?

I came to realize that thinking about the word "asynchronism" might not be the most correct approach, since I do not want parallel processes.

When a request is sent to a PHP script, all processing is done there, at the end of operations, the response is sent to the client. And the client (browser), in turn, is waiting for the execution of that process to end.

The question I asked earlier about asynchronous processes already has several answers on how to "dribble" this small PHP problem.

But what I would like to know is: Is there any way to send a response to the client when a request is made to a PHP script, but that same script continues to run on the server side until a long operation ?

For example:

gravar_dados_no_banco();

// Envia a resposta pro navegador
// Porém ele não precisa mais esperar o término desse script
echo json_encode(['status' => true]);

// Depois da resposta acima
// Faço algumas operações demoradas
// Que não serão "esperadas" pelo navegador

 sleep(3);

 mandar_email_pro_admin();

 atualizar_todos_webservices();
    
asked by anonymous 30.09.2017 / 17:30

2 answers

5

Based on Anderson's response, did Wallace not work?

<?php
ignore_user_abort(true);
set_time_limit(0);

gravar_dados_no_banco();

ob_start();

// Envia a resposta pro navegador
// Porém ele não precisa mais esperar o término desse script
echo json_encode(['status' => true]);

header('Connection: close');
header('Content-Length: '.ob_get_length());

ob_end_flush();
ob_flush();
flush();

// Depois da resposta acima
// Faço algumas operações demoradas
// Que não serão "esperadas" pelo navegador

sleep(3);

mandar_email_pro_admin();

atualizar_todos_webservices();
?>

For me here after flush(); it displayed the result on the screen, and even if I close the browser it continued running the rest of the script.

You have to see then if this is the case, if your other functions below, do not have something q is preventing the return. I'm not sure what to do with PHP, but I'm not sure how to do this.     

30.09.2017 / 19:16
8

How to do this in PHP with common settings?

I did a test in the Laravel framework and got the expected result.

I did this:

Route::get('/', function () {

    $response = Response::json([
        'process' => true
    ]);

    return Response::withShutdownTask($response, function () {
        minha_tarefa_demorada();
    });

});


Response::macro('withShutdownTask', function ($response, callable $callback) {

    $response->header('Connection', 'Close');
    $response->header('Content-Encoding', 'none');
    $response->header('Content-Length', mb_strlen($response->getContent()));

    register_shutdown_function(static function () use ($callback) {
        ignore_user_abort(true);
        flush();
        @ob_end_flush();
        @ob_flush();
        sleep(3);
        $callback();
    });

    ob_start();
    return $response;

});

The value {"process" : true} is sent immediately to the browser, however you will notice that the browser will not be loading any more.

We have some important points that need to be understood here:

  • You need to run ob_start , it starts capturing the output buffer;
  • You need flush and ob_end_flush to download the buffer to the output. I did the test without the function flush , leaving only ob_end_flush , and it worked perfectly.

  • You'll need ob_get_length . In my case I used mb_strlen($response->getContent()) because it is the specific class of the Framework I used, but in the case of pure PHP, you can use ob_get_length to get the current size. This seems to be an important point because the browser seems to read the size of the content received and the size sent in Content-Length to decide whether to close the connection to the server.

  • I put ignore_user_abort(true) as a precaution, to tell PHP not to stop running the script after the connection is closed. In my tests with Laravel it made no difference.

I also put sleep(3) , just to ensure that the test would work correctly, since this function would delay the request in 3 seconds. With the above code, the delay occurs in the execution on the server, but will not affect the browser, since the browser has already closed the connection;

FAST CGI

In FastCGI, it seems that to solve this problem, you simply call your "task" after the call of the fastcgi_finish_request function:

 fastcgi_finish_request();
 minha_tarefa_demorada();
    
30.09.2017 / 19:18