Home > Programming > PHP - Redirect and continue (without abort)

PHP - Redirect and continue (without abort)

The Problem

Sometimes, in PHP, we have to do things that take a long time (like converting user-submitted videos, say), but there's a problem with that: the user has to wait until we're done with whatever we're doing.  The worst part is, it looks like their request was dropped because the browser just sits there "loading" forever and shows no sign of progress until the page is finally done loading or - *shudder* - times out.

Solutions

There are a few strategies to solve this problem:

  1. You could have your PHP script create a cron job on the server.  This approach is overly complex in my opinion and has some problems: You have to be running your PHP code on a Linux server, and you have to have access to cron - not really an option for most people who don't host their own server.
  2. You could write a special PHP script that gets called via AJAX after the page is loaded and returns the results from the operation.  That works just fine and is a great option for someone comfortable with AJAX.  I've actually used this strategy myself, but it's still too complicated for my taste.
  3. My weapon of choice: The PHP redirect and continue.

Summary

It works like this: worker.php is the worker script.  At the very beginning of worker.php, it redirects the user's browser to messageToUser.php, but worker.php keeps working.  Then you can do anything you want in worker.php just like normal, and you can send the user a message when it's finished by whatever means you choose (you do already have a user message system, right?).

The Code

Say the user just finished uploading a video that needs to be converted.  The page they should be taken to is worker.php, which instantly redirects them to messageToUser.php.

messageToUser.php:

1
2
3
4
5
6
7
8
9
10
<html>
<head>
    <title>We're working on it!</title>
</head>
<body>
    Thank you for your submission.  We're converting your
    video right now, and you will be notified when it is finished.
    <br/><a href="index.php">Go to the home page.</a>
</body>
</html>

worker.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?PHP
    //Redirect to messageToUser.php
    header("Location: messageToUser.php");
    //Erase the output buffer
    ob_end_clean();
    //Tell the browser that the connection's closed
    header("Connection: close");

    //Ignore the user's abort (which we caused with the redirect).
    ignore_user_abort(true);

    //Extend time limit to 30 minutes
    set_time_limit(1800);
    //Extend memory limit to 10MB
    ini_set("memory_limit","10M");
    //Start output buffering again
    ob_start();

    //Tell the browser we're serious... there's really
    //nothing else to receive from this page.
    header("Content-Length: 0");

    //Send the output buffer and turn output buffering off.
    ob_end_flush();
    //Yes... flush again.
    flush();

    //Close the session.
    session_write_close();

    //Do some work

    //Then notify the user that it's finished
?>

Some notes about the last few lines:

  1. flush() has to be called even though we just called ob_end_flush() because:

    flush() has no effect on the buffering scheme of your web server or the browser on the client side. Thus you need to call both ob_flush() and flush() to flush the output buffers. ~PHP.net flush() docs

  2. We need to call session_write_close() because session data is locked while in use, which means only one PHP script can access a single session at a time.  So, since our script is going to take a while, we need to make sure other scripts can access the session instead of waiting for worker.php to finish.  That would kinda defeat the whole purpose.

That's it.  While this may seem like a complex fix to some, as long as you have PHP 4.0.4 or above, it Just Workstm, and you can use and reuse it anywhere you want.  Just copy and paste.  No AJAX.  No cron jobs.  Nothing more to deal with than PHP itself.  I like it.

  • http://sanzay-humor.blogspot.com/ Alex

    Andrew, why do you use sequence ob_end_clean(),ob_start() before ob_end_flush()?
    Does it has something to do with the headers sent or configuration calls?

  • http://andrewensley.com/ Andrew

    @Alex
    The ob_end_clean() erases the output buffer to make sure nothing before that code might have been output to the browser. If any content was to be output, the header() functions wouldn't work, and consequently, neither would the redirect.

    As for starting and ending again: ob_flush() (actually ob_end_flush() in this case) and flush() are used together to make absolutely sure that all headers are sent to the browser immediately. If you call ob_flush() and output buffering is off, it raises a PHP Notice. So I avoid this by turning on output buffering first.

    It is almost certainly overkill if you make sure you haven't output anything to the browser before running this code, but I wanted to make it robust enough to throw (almost) anywhere in a page and have it work.

    The only scenario that would cause the code to fail would be if you never had output buffering on and you output something to the browser before running this code.

  • Pingback: Redirigir al usuario y continuar trabajando en PHP – El blog de Koas