Question

I am trying to continue a PHP Script after the page/connection is closed.

Users will POLL the script in every 1 hour, I want to return some json output and want to continue the script in the background. I am using a shared host and I cannot use cron job.

Here is what I've tried.

ob_start();

ignore_user_abort();

echo "JSON_OUTPUT GOES HERE";

$ob_length = ob_get_length();

header("Content-Type : text/plain",TRUE);
header("Content-Length : $ob_length",TRUE);
header("Connection : Close",TRUE);

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

sleep(3);

echo "You cant see me..";

exit();

I am using Codeigniter framework, But its not working on my live server. It waits 3 seconds and then outputting You cant see me.. too.

Please help me.

Note

Project is hosted in LINUX/WINDOWS/WAMP-SERVER shared hosts.

Was it helpful?

Solution

After some research i got it work, Sometime it may be useful to some others.

function closeOutput($stringToOutput){   
        set_time_limit(0);
        ignore_user_abort(true);
        header("Connection: close\r\n");
        header("Content-Encoding: none\r\n");  
        ob_start();          
        echo $stringToOutput;   
        $size = ob_get_length();   
        header("Content-Length: $size",TRUE);  
        ob_end_flush();
        ob_flush();
        flush();   
} 

You can use it like

$outputContent = 'Contentent Goes Here...';

closeOutput( $outputContent );

sleep(5);

//do some background works ...

exit();

OTHER TIPS

First, don't use space after Connection and before : it should be Header: value not Header : value. Second, Connection: close don't force browser to stop getting current response and display blank page. Here http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html chapter 14.10 it states: Connection: close in either the request or the response header fields indicates that the connection SHOULD NOT be considered 'persistent' (section 8.1) after the current request/response is complete

So how can you try if your code works:

ignore_user_abort();
header("Content-Type: text/plain; charset=UTF-8");

// just to try show following echo immediately, working depends on server configuration
while (@ob_end_flush()); 

echo date('Y-m-d H:i:s'), PHP_EOL;

echo "JSON_OUTPUT GOES HERE", PHP_EOL;

sleep(10); // 10 seconds so you can close browser tab before

// this file should be created after 10 seconds, even after you closed browser tab
// also check if permissions to write to __DIR__ are set for apache.
file_put_contents(__DIR__ . '/tmp.txt', "Text after 10 sec");

exit;

Open this php file in browser and after 2-3 seconds close tab (even if you don't see anything on screen), wait a little longer and check if file is created. It's working on my linux machine.

Because of this cool possibility, which Red posted, I've written a small utility class which provides a queue where you can add Closures for later execution:

<?php

namespace Company\Project\Utilities;

/**
 * Class ContinueUtility
 *
 * @package Company\Project\Utilities
 */
class ContinueUtility {
/**
 * Stored tasks
 * @var array
 */
static protected $tasks = array();

/** Constant for new line in HTTP Header */
const HEADER_NEW_LINE = "\r\n";

/**
 * Add task (closure/function) to queue, with set arguments
 *
 * @param \Closure $task
 * @param array $arguments
 * @return void
 */
public static function addTask(\Closure $task, array $arguments = array()) {
    self::$tasks[] = array(
        'closure' => $task,
        'arguments' => $arguments
    );
}

/**
 * Returns TRUE if tasks has been set, otherwise FALSE
 *
 * @return boolean
 */
public static function hasTasks() {
    return !empty(self::$tasks);
}

/**
 * Clear all previous set tasks
 *
 * @return void
 */
protected static function clearTasks() {
    self::$tasks = array();
}

/**
 * Execute all previous set tasks
 *
 * @return void
 */
protected static function executeTasks() {
    foreach (self::$tasks as $task) {
        call_user_func_array($task['closure'], $task['arguments']);
    }
}

/**
 * Execute and clear all previous set tasks
 *
 * @return void
 */
public static function executeAndClearTasks() {
    self::executeTasks();
    self::clearTasks();
}

/**
 * Closes the HTTP connection to client immediately and outputs given string.
 *
 * @param string $instantOutput
 * @return void
 */
public static function closeConnection($instantOutput = '') {
    set_time_limit(0);
    ignore_user_abort(TRUE);
    header('Connection: close' . self::HEADER_NEW_LINE);
    header('Content-Encoding: none' . self::HEADER_NEW_LINE);
    ob_start();
    echo $instantOutput;
    $size = ob_get_length();
    header('Content-Length: ' . $size, TRUE);
    ob_end_flush();
    ob_flush();
    flush();
}
}

This is how you add new tasks to queue:

use Company\Project\Utilities\ContinueUtility;

$a = 4;
$b = 5;
ContinueUtility::addTask(function($a, $b){
    sleep(5);
    $c = a + b;
    file_put_contents(__DIR__ . '/whatever.log', $a . '+' . $b . '=' . $c);
}, array(
    $a, $b
));

And this is how you trigger execution of all previous added tasks:

ContinueUtility::closeConnection('Ready.');
ContinueUtility::executeAndClearTasks();

If you are using PHP-FPM, a cleaner and more versatile solution would be to simply execute fastcgi_finish_request();

From PHP.net's documentation

This function flushes all response data to the client and finishes the request. This allows for time consuming tasks to be performed without leaving the connection to the client open.

This is how Symfony handles its onTerminate.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top