I am trying to create a page that will generate a set of results from a complex database query and php analysis ... but this is mostly irrelevant ... The main thing is that it takes a minute or two full, and I hope to display a progress bar , not the general graphic animation "loading ...".
The breakdown will be ...
- The user opens page A.
- Page A requests data from page B (most likely AJAX).
- Page B processes 100,000+ records or so in the database and analyzes them.
- Page A shows a progress bar that shows how far the process has progressed.
- Page B returns a result set.
- Page A displays a result set.
I know how to return data to an ajax request, but my problem is that I don’t know how to continuously return data to show the status of the process (for example,% of scanned lines).
I looked at EventSource / Server-Sent-Events, which shows a promise, I'm just not too sure how to make it work correctly, or if there is a better way to do this.
I tried to make a small small mock page using only EventSource, fine, but when I laid it out to call eventSource (the page that controls the session variable to change) and the ajax request (the actual data transfer / return), it falls apart.
I will probably miss something obvious or do something stupid, but that’s most of what I have ... Any help, suggestions, tips or even suggestions on completely different ways to do this would be awesome :)
User Page:
<!DOCTYPE html> <html> <head> <title>Dynamic Progress Bar Example</title> <script src="script.js"></script> </head> <body> <input type="button" value="Submit" onclick="connect()" /> <progress id='progressor' value="0" max='100' style=""></progress> </body> </html>
Javascript
var es; function connect() { startListener(); $.ajax({ url: "server.php", success: function() { alert("Success"); }, error: function() { alert("Error"); } }); } function startListener() { es = new EventSource('monitor.php'); //a message is received es.addEventListener('message', function(e) { var result = JSON.parse(e.data); if (e.lastEventId == 'CLOSE') { alert("Finished!"); es.close(); } else { var pBar = document.getElementById('progressor'); pBar.value = result; } }); es.addEventListener('error', function(e) { alert('Error occurred'); es.close(); }); } function stopListener() { es.close(); alert('Interrupted'); } function addLog(message) { var r = document.getElementById('results'); r.innerHTML += message + '<br>'; r.scrollTop = r.scrollHeight; }
PHP monitoring
<?php SESSION_START(); header('Content-Type: text/event-stream'); // recommended to prevent caching of event data. header('Cache-Control: no-cache'); function send_message($id, $data) { $d = $data; if (!is_array($d)){ $d = array($d); } echo "id: $id" . PHP_EOL; echo "data: " . json_encode($d) . PHP_EOL; echo PHP_EOL; ob_flush(); flush(); } $run = true; $time = time(); $last = -10; while($run){ // Timeout kill checks if (time()-$time > 360){ file_put_contents("test.txt", "DEBUG: Timeout Kill", FILE_APPEND); $run = false; } // Only update if it changed if ($last != $_SESSION['progress']['percent']){ file_put_contents("test.txt", "DEBUG: Changed", FILE_APPEND); $p = $_SESSION['progress']['percent']; send_message(1, $p); $last = $p; } sleep(2); } ?>
EDIT: I tried a different approach, where:
- The AJAX page invokes page B, which launches the request, and saves the progress in the SESSION variable
- The AJAX page calls page C every 2 seconds, which simply returns the value of the session variable. This cycle stops when it reaches 100
However, this also does not quite work. It seems that two AJAX requests or two server servers are not working at the same time.
Looking at the debug output: both AJAX calls are made at about the same time, but then the B script page starts by itself, and then the C script page starts. Is this some PHP restriction that I am missing?
more code!
Server (page B) PHP
<?PHP SESSION_START(); file_put_contents("log.log", "Job Started\n", FILE_APPEND); $job = isset($_POST['job']) ? $_POST['job'] : 'err_unknown'; $_SESSION['progress']['job'] = $job; $_SESSION['progress']['percent'] = 0; $max = 10; for ($i=0; $i<=$max;$i++){ $_SESSION['progress']['percent'] = floor(($i/$max)*100); file_put_contents("log.log", "Progress now at " . floor(($i/$max)*100) . "\n", FILE_APPEND); sleep(2); } file_put_contents("log.log", "Job Finished", FILE_APPEND); echo json_encode("Success. We are done."); ?>
Progress (Page C) PHP
<?php SESSION_START(); file_put_contents("log.log", "PR: Request Made", FILE_APPEND); if (isset($_SESSION['progress'])){ echo json_encode(array("job"=>$_SESSION['progress']['job'],"progress"=>$_SESSION['progress']['percent'])); } else { echo json_encode(array("job"=>"","progress"=>"error")); } ?>
Index (Page A) JS / HTML
<!DOCTYPE html> <html> <head> <title>Progress Bar Test</title> </head> <body> <input type="button" value="Start Process" onclick="start('test', 'pg');"/><br /> <progress id="pg" max="100" value="0"/> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> <script type="text/javascript"> var progress = 0; var job = ""; function start(jobName, barName){ startProgress(jobName, barName); getData(jobName); } function getData(jobName){ console.log("Process Started"); $.ajax({ url: "server.php", data: {job: jobName}, method: "POST", cache: false, dataType: "JSON", timeout: 300, success: function(data){ console.log("SUCCESS: " + data) alert(data); }, error: function(xhr,status,err){ console.log("ERROR: " + err); alert("ERROR"); } }); } function startProgress(jobName, barName){ console.log("PG Process Started"); progressLoop(jobName, barName); } function progressLoop(jobName, barName){ console.log("Progress Called"); $.ajax({ url: "progress.php", cache: false, dataType: "JSON", success: function(data){ console.log("pSUCCESS: " . data); document.getElementById(barName).value = data.progress; if (data.progress < 100 && !isNaN(data.progress)){ setTimeout(progressLoop(jobName, barName), (1000*2)); } }, error: function(xhr,status,err){ console.log("pERROR: " + err); alert("PROGRESS ERROR"); } }); } </script> </body> </html>
Debugging: log.log output
PR: Request Made Job Started Progress now at 0 Progress now at 10 Progress now at 20 Progress now at 30 Progress now at 40 Progress now at 50 Progress now at 60 Progress now at 70 Progress now at 80 Progress now at 90 Progress now at 100 Job Finished PR: Request Made