0

EDIT

This is what the code is for:

  1. The user clicks a button which starts a long-running process on the server (not actually uploading a file, that's just to get the code working)
  2. If the process finishes before sending the response nothing seems to be happening on the user's browser and they might click again/reload the page/do something else undesirable
  3. An immediate response is required to let the user know they have to wait
  4. A progress indicator shows how far the process has got, i.e. roughly how long the user will have to wait

I just need some way of telling the user in real-time roughly what the progress on the server is. After they click, everything happens on the server until the process in finished, then they get a redirection signal and go to the next page.

Flowchart

If I need to throw away all my code and try something else I'll do it!

END EDIT

The question has been asked many times but I can't find an answer that actually works yet.

I'm setting up a progress indicator for the first time and I have it mostly working except for a lot of warnings. Here is the code:

<?php
session_name('test');
$filesize = 10000000;

// Allow script to send response but keep running afterwards
ignore_user_abort(true);
ob_start();
header('Connection: close');
header('Content-Length: ' . ob_get_length());
ob_end_flush();
//ob_flush();
flush();

for ($i = 0; $i < $filesize; $i += rand(4096, 8192)) { // Just testing...
  ob_start();
  @session_start();
  $_SESSION['progress'] = $i;
  session_write_close();
  ob_end_clean();
}
ob_start();
@session_start();
$_SESSION['progress'] = $filesize;
session_write_close();
ob_end_clean();
exit;
?>

I have a second script which then reads the values out of $_SESSION which is workig fine. If I remove the @s before session_start(); in the above script I get multitudes of the following error:

[Fri Aug 22 08:59:59.109375 2014] [:error] [pid 4100:tid 1248] [client 127.0.0.1:2577] PHP Warning:  session_start(): Cannot send session cookie - headers already sent in C:\\server\\Apache24\\htdocs\\localhost\\SRI\\progress.php on line 36, referer: http://localhost/SRI/progress.php
[Fri Aug 22 08:59:59.109375 2014] [:error] [pid 4100:tid 1248] [client 127.0.0.1:2577] PHP Stack trace:, referer: http://localhost/SRI/progress.php
[Fri Aug 22 08:59:59.109375 2014] [:error] [pid 4100:tid 1248] [client 127.0.0.1:2577] PHP   1. {main}() C:\\server\\Apache24\\htdocs\\localhost\\SRI\\progress.php:0, referer: http://localhost/SRI/progress.php
[Fri Aug 22 08:59:59.109375 2014] [:error] [pid 4100:tid 1248] [client 127.0.0.1:2577] PHP   2. session_start() C:\\server\\Apache24\\htdocs\\localhost\\SRI\\progress.php:36, referer: http://localhost/SRI/progress.php
[Fri Aug 22 08:59:59.109375 2014] [:error] [pid 4100:tid 1248] [client 127.0.0.1:2577] PHP Warning:  session_start(): Cannot send session cache limiter - headers already sent in C:\\server\\Apache24\\htdocs\\localhost\\SRI\\progress.php on line 36, referer: http://localhost/SRI/progress.php
[Fri Aug 22 08:59:59.109375 2014] [:error] [pid 4100:tid 1248] [client 127.0.0.1:2577] PHP Stack trace:, referer: http://localhost/SRI/progress.php
[Fri Aug 22 08:59:59.109375 2014] [:error] [pid 4100:tid 1248] [client 127.0.0.1:2577] PHP   1. {main}() C:\\server\\Apache24\\htdocs\\localhost\\SRI\\progress.php:0, referer: http://localhost/SRI/progress.php
[Fri Aug 22 08:59:59.109375 2014] [:error] [pid 4100:tid 1248] [client 127.0.0.1:2577] PHP   2. session_start() C:\\server\\Apache24\\htdocs\\localhost\\SRI\\progress.php:36, referer: http://localhost/SRI/progress.php

I really don't like using @ as it only suppresses the warnings; I'd much rather fix them. Is there a cleaner way of getting this working?

I have already eliminated the following:

  • UTF-8 BOM
  • Output between flush() and the next session_start()
  • Using output buffering (ob) for every session_start()
  • Using pcntl_fork() (doesn't work under Apache)

The second lot of ob_start()s have helped with getting a timely response to the browser but my error log is still filling up! I suspect that since the flush() has sent headers and I'm still in the same script that this is why the warnings keep happening. I have a WAMP server for development but will be transferring the working program to a LAMP server.

CJ Dennis
  • 4,226
  • 2
  • 40
  • 69
  • Seems like you did a bit of research already. However, see ["Output buffering as workaround"](http://stackoverflow.com/questions/8028957/how-to-fix-headers-already-sent-error-in-php/8028987#8028987) again. Adding `ob_start` somewhere within the code is not going to help. It needs to be the very first call. -- Also show the actual error message (including **"output started at ..."**), not the log entry. – mario Aug 22 '14 at 00:24
  • Thanks mario. However, I need to call `session_start()` `session_write_close()` multiple times to save the progress without locking the session. I'm up for completely rewriting the code if that's what it takes. – CJ Dennis Aug 22 '14 at 00:37
  • You can open and close the session as often as you want. As long as you remove the premature output and `ob_start`, of course. Alternatively use temporary status files for storing the filesize instead, if your scripts are too unwieldy to find and fix it the output issue. – mario Aug 22 '14 at 00:56

2 Answers2

0

Once you send any output in PHP, you cannot call session_start() or header(). This is because you are creating an HTTP response with your script which will end up looking something like this:

HTTP/1.1 200 OK
Server: nginx/1.6.1
Cookie: ...

// Content goes here

Once you start outputting content, you cannot output headers as that would create malformed response. Every time you call ob_end_flush() you are sending the contents of the output buffer. This means that you can't do a session_start() after it.

In your code above, you should be able to remove everything and just do:

<?php

  session_name('test');
  session_start();

  $filesize = 10000000;

  // whatever else you want to do

  $_SESSION['progress'] = $filesize;

Also make sure that there is no whitespace before the opening php bracket. This is often the culprit in getting errors when calling session_start(). Sometimes it can also happen via trailing whitespace on an included file.

Tyler Marien
  • 752
  • 4
  • 11
  • Thanks for your answer! However, your suggestion would be pointless as `$_SESSION['progress']` would always contain the filesize rather than the actual progress. It would also lock the session preventing another script from reading from it (which is the whole point of the script - to display a progress indicator). – CJ Dennis Aug 22 '14 at 00:34
  • Sorry, I wasn't too concerned with what your script actually did. I was just trying to solve your session_start issues. I think you are thinking of PHP incorrectly. You send the web server one request, it does some processing and then returns one response. You generally do not have long running requests like this. To do a progress indicator you would do multiple requests with each one returning the current progress. If you are trying to create a file uploader might I suggest just using one that is already build such as [jQuery FileUpload](https://github.com/blueimp/jQuery-File-Upload) – Tyler Marien Aug 22 '14 at 00:43
0

OK, so I've fixed the problem by splitting my two scripts (process & progress) into three scripts (prepare, process & progress) called by three different parts of the same web page:

  1. Send "prepare" request. Server initialises some variables and saves them in the $_SESSION. On success go to step 2
  2. Send "process" request. Server starts running the long process using the variables previously stored in the $_SESSION during step 1. On success load content/redirect to next page
  3. Send "progress" requests at regular intervals while step 2 is running. On success update progress indicator on the page

Since I'm using XHR/AJAX anyway this solution seems to do the trick! I no longer need the "send output but keep running" solution.

CJ Dennis
  • 4,226
  • 2
  • 40
  • 69