How to continue PHP script after sending HTTP response - javascript

I was wondering if it was possible to send HTTP response immediately and continue the script.
Background: Among all the petition I make to the server there's one that creates an Excel file (using PHPSpreadSheet), since creating this files can take a little longer I was thinking of responding a HTTP 202 status code to the Client, something like:
header("HTTP/1.1 202 Excel file in process");
and program in JavaScript a listener on the main js file for every time that status code arrives to activate an interval (setInterval()) and ask every certain amount of seconds whether the Excel file is ready or not
$(document).ajaxSuccess(function ( event, xhr, settings) {
if(xhr.status === 202) {
//Activate the setInterval function to ask the server every 10 seconds whether the file is ready or not
}
});
(I know I have to be activating/deactivating the interval)
So whenever I receive a petition on my createExcel.php file I would like to respond the user immediately that the petition has being received and start the making of such Excel file(s)
Something like:
<?php
//Tell user that the petition has being received
header("HTTP/1.1 202 Excel file in process");
//Kill this process or close this connection but continue executing
//Call createExcel.php
the createExcel.php file would update some table in the database the confirm the file has been created, same table that the interval petition will be consulting every 10 seconds
That's what I'm attempting to do, I would just like you guys to tell me how to call another another file without waiting for such called file to finish to respond the user.
I was thinking of using exec() but I have never used it (I'm testing it right after I post this), and most importantly any experience or tips would be greatly appreciated (like optimization tips and the like)
I saw this question here on Stack Overflow, but the answer suggests to create a cron service which is not a solution for me.
Thank you!
Edit---
Hey in case someone sees this I found two solutions to my question:
The first one I tried but gave a lot of trouble with permissions is this: https://code-boxx.com/php-background-process/
But this one would work beautifully if you run it from cmd, but when you run it thought the browser, Apache forbids you from using executing commands; so exec(), popen(), and similar commandss won't work unless you change your permissions in your folders, which I consider a security issue, so I found out this very beautiful function fastcgi_finish_request()
Edit 2 - solution
https://qastack.mx/programming/15273570/continue-processing-php-after-sending-http-response
this works we flush all content in buffer and close the connection and then we just continue the execution of the script.

In JS:
$(document).ajaxSuccess(function ( event, xhr, settings) {
if(xhr.status === 202) {
//console.log("Your file is in process");
//console.log(xhr.responseJSON);
//Activate interval to check if file is ready
}
});
//Make petition
$.post("petitionsHandler.php", {"texto":"Hello world, greeting from México!"}, function (resp){
//Handle responses
}, "json").fail(function () {
//Handle errors
});
In PHP
<?php
$text = $_POST["text"];
//If you are using sessions don't forget to close the session file
//Or such will be blocked until long script finishes
session_write_close();
header("HTTP/1.1 202 Solicitud recibida"); #This is the code I wanted to send
header("Content-Type: application/json"); #Depends the kind of data you're sending to the client
// Buffer all upcoming output...
ob_start();
// Send your response.
echo '{"success":true, "message":"Your request has been received."}';
// Get the size of the output.
$size = ob_get_length();
// Disable compression (in case content length is compressed).
//header("Content-Encoding: none"); #I didn't need this but check your situation
// Set the content length of the response.
header("Content-Length: {$size}");
// Close the connection.
header("Connection: close");
// Flush all output.
ob_end_flush();
ob_flush();
flush();
//All outputs have now been sent to the client
//You can continue executing your long tasks
include_once('createExcel.php');
createExcel.php
<?php
sleep(10); #You can use to checkthat the code works
//The above code will respond the client immediately and after ten seconds the Excel file will be created
require "PHPSpreadSheet/vendor/autoload.php";
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setCellValue('A1', $text);
$writer = new Xlsx($spreadsheet);
$writer->save('MyFile.xlsx');
Of course you have to make validations, so you don't have lots of running process in background but I hope the general idea has been shown in this example.
This is not exactly the code I use, but this is all it takes to continue executing code after responding the client.
In my own opinion, I thinks this is better than using exec() or similar functions which invoke the command terminal as that could be a potential vulnerability , so you don't have to change permissions or anything.
Note: If you're using sessions, Please remember to use session_write_close() because on heavy tasks will block the file until such task is finished.
I hope it helps :)
My answer was based on this blog: https://qastack.mx/programming/15273570/continue-processing-php-after-sending-http-response

Related

Using EventSource with Ajax

I am trying to work out how EventSource might help solve an update problem with a web applications.
I would like to be able to notify visitors to a web page when one or another visitor has performed an update. The idea is:
One visitor makes some changes to a text area and submits.
The data is submitted using Ajax.
At the server, the changes are saved.
I would like the server to send a notification that changes have been made
I would like all other visitors to get this notification.
On the server in PHP, I have something like this:
if(isset($_POST['save'])) {
// process changes
$data = sprintf('data: {"time": "%s","type": "save"}',$date);
header("Cache-Control: no-store");
header("Content-Type: text/event-stream");
print "event: ping\n$data\n\n";
ob_flush();
flush();
exit;
}
In JavaScript I have something like this:
var eventSource = new EventSource("https://example.net/ajax.php", { withCredentials: true } );
eventSource.addEventListener("ping", (event) => {
const time = JSON.parse(event.data).time;
const type = JSON.parse(event.data).type;
console.log(`Ping at ${time} for ${type}`);
});
That doesn’t do the job at all. At the client end I get get an error message about the wrong headers (I think that it’s because there’s no save yet, so the PHP falls through to an empty string).
All the samples I have seen include a while(true) loop in the PHP, but non have a one-off event.
Is this possible to implement, and, if so, how?

Real Time Refresh / Update

I have a MVC 3 project that publish in a server.
Scenario
For example I have a function for saving a data from (PC1) to (PC2).
It is possible that the viewing of data(data in jqgrid) in (PC2) is open(open in page) by a user and it will auto refresh or update the page or the jqgrid after the (PC1) save a data?
My jqgrid version is 4.3.3.
Hope you guys understand what I mean in my post. Post feedback if down votes. Thanks.
Any help will be accepted.
You might wonna use ajax to accomplish such a job, please read below
if I understand what you mean, is that you wonna poll a server to realtime updates on either intervals or something else...
option 1
Issue a normal stateless ajax call to the server, then force the server to hold the request for a limited time [to overcome server overhead]
This can also be reffered to as reverse ajax or comet.
Unless you are planning to use websocket technology, I hardly stress that you try this.
if(isset($_GET['finite'])){
#declare time for a session
$_SESSION['typing']=$reduce_browser_overhead=time();
#remember to close the session before entering the loop;
#if u dont close, then the browser will not reload the same website untill the connection is close or satisfied
session_write_close();
function loop(){
#do this to access external variables ===> $reduce_browser_overhead;
global $con,$reduce_browser_overhead;
#explicitly check 2exit
#please do this to release mysql connection since they are in a loop
if($reduce_browser_overhead+4<time()){
echo ' ';ob_flush();flush();
if(connection_aborted()){
#do some work here before you finally exit the connection
exit;
}$reduce_browser_overhead=time();
}
#php prepare statement...
$looper=$con->prepare("SELECT ROW FROM TABLE WHERE ID=SOMETHING AND $_SESSION[typing]=SOME_ROW");
#The statement above willcause the loop to work
#If a table had been update and table has not yet updated, this sql will detemin by the current time
#meaning that if the time[integer] of SOME_ROW is not equal to the time in the session variable,
#then it will let go to the client and then again it will continue looping untill the time in the
#SOME TABLE ROW changes....
$looper->execute();
$looper->store_result();
if($looper->num_rows>0){
sleep(2);
#do some work before looping again
loop();
#you have to explicitly return to this loop to work as expected.
return;
}else{
#send back data to the user or the client listening on the connection
session_start();$_SESSION['typing']=time();session_write_close();
#update the session before finishing the request so that the next time the request comes, the time will be equal to the DBserver time in the row and hence causing the loop again and again => more like a cycle
echo 'After some time the server has received new data which is =>> '.$newdata;
}
}loop();
exit;
//In another file on in the same document as the php / your server file ==> do javascript below
//first issue a normal / stateless ajax request to the target server
$.ajax({
//All optional but url required!
url:'abc.php?var_one=blabla',
cache:true,//whether to cache the requests
timeout:(1000*60)*20,//timeoutthe request
success:function(data){
//if the server successfully completed the request
//do some work here with data returned
},
error:function(){
//if the server return an error
//do more work around
//or call the function again
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Constantly read JSON database

I want to constantly read a JSON-formatted js file so my page shows the changes of that file.
I want some content in my page to change everytime I change the database file within the directory.
My files are:
objectoJSON.js:
var rightFencer;
rightFencer = {"name":"Jorge ANZOLA","nacionality":"VEN","points":10};
var leftFencer;
leftFencer = {"name":"John DOE","nacionality":"USA","points":5};
index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Test</title>
</head>
<body>
<center><p id="rightFencerName"></p><p id="rightFencerPoints"></p> - <p id="leftFencerName"></p> <p id="leftFencerPoints"></p></center>
<script src="objetoJSON.js"></script>
<script>
document.getElementById("rightFencerName").innerHTML = rightFencer.name;
document.getElementById("leftFencerName").innerHTML = leftFencer.name;
document.getElementById("rightFencerPoints").innerHTML = rightFencer.points;
document.getElementById("leftFencerPoints").innerHTML = leftFencer.points;
</script>
</body>
</html>
I thought about putting those two scripts into an infinite while loop so by the time I change the file in the directory, it'd change. But it didn't work.
Also, I thought about using setInterval() to run the scripts every few seconds, but I didn't know how to make it work.
As you can see, I'm a complete noob, so ANY idea would be very appreciated.
Your "objectoJSON.js" is not a JSON file... it's a simple javascript object.
A JSON file would be something like this.
{
"rightFencer":{
"name":"Jorge ANZOLA",
"nacionality":"VEN",
"points":10
},
"leftFencer":{
"name":"John DOE",
"nacionality":"USA",
"points":5
}
}
What you are searching for is
Ajax, Server Sent Events or webSockets
Those update the pagecontent without the need to refresh the page or clicking something.
The following codes shows how to interact with each technology.
They have many advantages and disadvantages... to many to write right now.
ask specific and i can add that to the answer.
All the following examples are pure javascript and so don't need any type of library.They work with almost all new browsers... ios,android,windows also.
All the following examples could be adapted to work with a non properly formatted json file like that you posted. Look at the bottom.
Ajax:
Client asks for data
This updates the client every 30seconds.
function $(a){
return document.getElementById(a)
}
function ajax(a,b,c){ // Url, Callback, just a placeholder
c=new XMLHttpRequest;
c.open('GET',a);
c.onload=b;
c.send()
}
function reloadData(){
ajax('database.js',updateText)
};
function updateText(){
var db=JSON.parse(this.response);
$("rightFencerName").innerHTML=db.rightFencer.name;
$("leftFencerName").innerHTML=db.leftFencer.name;
$("rightFencerPoints").innerHTML=db.rightFencer.points;
$("leftFencerPoints").innerHTML=db.leftFencer.points;
}
window.setInterval(reloadData,30000);//30 seconds
/*setinterval is a very bad way to update stuff ,
especially with ajax.. there are many other ways to do that.*/
Ajax does not need any type of server if you read the JS file locally.
Also appendding it... but both examples are time based... and that is not good if you have many users online. WS & SSE allow you to update each user individually depending on the necessity.
SSE:
Server sends data when needed
This uses php to create a Server Sent Events Server
Also this updates the client every 30 seconds, but in this case the server updates the client. Using Ajax the client asks the server to update.
The php file "sse.php"
header('Content-Type: text/event-stream'); // specific sse mimetype
header('Cache-Control: no-cache'); // no cache
while(true) {
if(/*something changes*/){
echo "id: ".time().PHP_EOL;
echo "data: ".$data.PHP_EOL;
echo PHP_EOL;
}
ob_flush(); // clear memory
flush(); // clear memory
sleep(30);// seconds
}
The javascript file
function $(a){
return document.getElementById(a)
}
function updateText(e){
var db=JSON.parse(e.data);
$("rightFencerName").innerHTML=db.rightFencer.name;
$("leftFencerName").innerHTML=db.leftFencer.name;
$("rightFencerPoints").innerHTML=db.rightFencer.points;
$("leftFencerPoints").innerHTML=db.leftFencer.points;
}
var sse=new EventSource("sse.php");
sse.onmessage=updateText;
WebSockets:
Server sends data when needed, Client asks for data when needed
webSockets is cool ... comunication is bidirectional. it is fast. but you need something like a nodejs server to be able to handle it properly.
function $(a){
return document.getElementById(a)
}
function updateText(e){
var db=JSON.parse(e.data);
$("rightFencerName").innerHTML=db.rightFencer.name;
$("leftFencerName").innerHTML=db.leftFencer.name;
$("rightFencerPoints").innerHTML=db.rightFencer.points;
$("leftFencerPoints").innerHTML=db.leftFencer.points;
}
var ws=new WebSocket('ws://YOURIP:YOURPORT');
/*ws.onopen=function(){ //those events are also aviable with sse
ws.send('WS open!');//sending data to the server
};
ws.onclose=function(){
console.log('WS closed!');
};*/
ws.onmessage=updateText;
Adapting the js
Ajax..
load the "objectoJSON.js" with ajax and evulate it ... but not using eval(). eval is evil. use new Function()
function updateText(){
document.getElementById("rightFencerName").innerHTML = rightFencer.name;
document.getElementById("leftFencerName").innerHTML = leftFencer.name;
document.getElementById("rightFencerPoints").innerHTML = rightFencer.points;
document.getElementById("leftFencerPoints").innerHTML = leftFencer.points;
}
(new Function(this.response+'\n updateText()'))();
or append the script every 30 seconds or whatever....
I don't write that example as it is the worst approach.
With 30 clients it means that you have to read the file from server evey second.
With SSE or WS you read it once and broadcast it to hundreds of clients.
I suggest to fix your json file.
if you have any other questions ask.
I guess you are working with framework which supports websockets.
You could listen for a change in file using websocket.it may return change in data set like new record or update on any record, or using javascript/ajax call get latest content from server and update your HTML.
https://www.websocket.org/demos.html, see foreign exchange dashboard to see how websockets can be used for constantly updating data.
The way you are doing it now isn't scalable, testable or manageable.
You really don't want to save data on the server using plaintext json files.
If you want a more robust framework for handling your use case, I suggest using web sockets on both the client side and server side (socket.io is a great choice), and using RethinkDB on your server as the DB.

Asynchronous API and callback

I have to use one asynchronous service. Everything I can do is send data to this service (I'm doing it with PHP and CURL) and send data to some url from this service. How can I react/wait for a response from this service?
Now I have two pages: first is sending data to service and the second takes a response from this service and inserts it to database. On a first page I'm checking some table while there isn't the response. But selecting from database few times per second is bad idea. But what I need to have: Send data from one page and get the response at the same page. I guess I can use some Ajax and make the async service sending data to the same page and wait for the response on this page.
I guess I wrote very hard because I can't fully explain what I need, so feel free to correct me.
As #Steve noted, PHP has no concept of asynchronousity. However there is a hack which allows to implement something similar to long-polling in PHP. The main point is to use a file ready to read in Javascript, i.e. JSON.
Here is a general flow:
Your single web page does AJAX request to your php script which send
appropriate request to the external service and immediately return
some response (e.g. empty) to the web page.
The web page starts to repeatedly request server for the same static
JSON file (by doing AJAX requests) until it appears (created by
callback script).
The external service passes response to your callback script which
save the response into the JSON file.
The web page get the response from the JSON file and outputs it.
Your easiest option is going to be ajax polling - send the request to the webservice, then poll every x seconds. The response handler (the script called by the webservice when it completes) need to save the data somewhere, eg database or session, and the poll script will check for this data.
Although this will add a little to server load, if you set the polling interval high enough it should be fine
session_start();
if(isset($_GET['sendrequest'])){
WebService:sendRequest(['callback_url'=>'thispage?receiveresponse=1'])
$_SESSION['response']=false;
die();
}elseif(isset($_GET['receiveresponse'])){
$response = WebService:receive();
$_SESSION['response'] = $response;
die();
}elseif(isset($_GET['checkresponse'])){
$data=[];
if($_SESSION['response']){
$data['success']=true;
$data['response']=$_SESSION['response'];
}else{
$data['success']=false;
}
header('Content-Type: application/json');
die(json_encode($data);
}
<html>
<head>....</head>
<body>
<a id="send" href="#">Send Request</a>
<div id="response"></div>
<script>
var poll;
$('#send').click(function(ev){
ev.preventDefault();
$post('?sendrequest=1', {...}, function(){
poll = setInterval(function(){
$get('?checkresponse=1', function(response){
if(response.success){
clearInterval(poll);
$('#response').html(response.response);
}
});
}), 3000);
});
});
</script>
</body>
</html>

check script status in PHP using ajax

I have a file upload page in my application. I need to show "Uploading" while file is uploading then show "Processing" while file is processing. Then after completion of script my page got redirected to some url.
I have tried to use PHP SESSIONS in the script. As in code below:
$_SESSION['uploaded']=0;
if (!empty($_FILES)) {
$tempFile = $_FILES['file']['tmp_name'];
$targetPath = dirname( __FILE__ ) . $ds. $storeFolder . $ds;
$_FILES['file']['name']=date('Ymdhis').$_FILES['file']['name'];
$targetFile = $targetPath. $_FILES['file']['name'];
if(move_uploaded_file($tempFile,$targetFile)){
$_SESSION['uploaded']=1;
//some processing here which takes some 4-5second to complete
}
}
After file upload complete I update session. I am checking session every second by calling following function in javascript:
function countdown(seconds){
console.log(<?php echo $_SESSION['uploaded']; ?>);
if(<?php echo $_SESSION['uploaded']; ?>==0){
setTimeout(function() {
//uploading
seconds--;
countdown(seconds);
}, 1000);
}
else{
//processing
}
}
After searching from google for long time I came to know that in a single script SESSION is locked till script execution completed. Then I used session_write_close(); But it also not works. I am always getting 0 value of SESSION.
Please help me figuring out solution in simplest way. Thanks.
UPDATE
Unable to make it work with Ajax request also. So further tried using the MySQL table.
What I do is create table when upload script is called. Then insert value of status=0 in it using following code:
$session=session_id();
$stmt=$conn->prepare("DROP TABLE IF EXISTS $session");
$stmt->execute();
$stmt=$conn->prepare("CREATE TABLE $session (id INT(11), status INT(11))");
$stmt->execute();
$stmt=$conn->prepare("INSERT INTO $session VALUES(1,0)");
$stmt->execute();
Then after upload completion I update the status to 1 and do the processing on file.
Then after successful completion of script I redirect to result page and drop table using session_id().
But My Ajax script which is checking status every second doesn't respond till the upload.php script ends. I have tried closing connection after every query but in vain. Code on getstatus.php
<?php
session_start();
$session=session_id();
require_once('connect.php');
$stmt=$conn->prepare("SELECT * FROM $session WHERE id=1");
$stmt->execute();
$res=$stmt->fetch(PDO::FETCH_ASSOC);
echo $res['status'];
?>
Unable to find solution for it till now. Help is greatly appreciated. Thanks.
Instead of invoking a PHP process on the server side every second, you could use a static file to check the upload state.
When generating the upload form for the client:
Create a tempnam for a directory that is accessible for the
client.
Write 'uploading' to the temporary file
Store the filename in the session. (Be aware: The user might open multiple upload forms. Store the filenames in an array)
Send the filename to the client as a hidden field.
On the server side after user submitted the form:
Check if filename sent from the client matches a filename stored in the session.
Write 'processing' to the state file
At the end of your upload script write 'finished' to the state file
On the client side after user submits the form, check the upload state by doing ajax requests on the state file.
Remarks
Disable caching for the state file with .htaccess. If this is no option you can achieve the same behavior with a php state script and the upload state saved to a session variable instead of a state file.
To make sure all generated files are deleted register a destroy handler that deletes files generated in the session: http://php.net/manual/en/function.session-set-save-handler.php
<?php echo $_SESSION['uploaded']; ?> is preprocessed by PHP only once, just before this javascript is sent to client. That said, the javascript on client looks like:
function countdown(seconds){
console.log(0);
if(0==0){
setTimeout(function() {
//uploading
seconds--;
countdown(seconds);
}, 1000);
}
else{
//processing
}
}
You should find other way (ajax?) to update information on the client side.
This became too long for a comment.
I'm unsure how you'd respond with progress information with PHP. I tried once and failed.
Socket.io is awesome in Node.js and there is a PHP server emitter. I would potentially give that a go. It should offer near instantaneous communication without waiting for scripts to complete.
Alternatively I would check out Jquery upload, it has a PHP server script. Supports progress bars Jquery Upload. Either implement it directly or check out the source code for how display progress info. I tried having a quick look but couldn't identify how they do it easily.
Why use DATABASE if you can do it on server ?
To save your bandwidth and database traffic you can seperate your process into 2 file
Create upload.php to serve uploading process
$_SESSION['uploaded']=0;
if (!empty($_FILES)) {
$tempFile = $_FILES['file']['tmp_name'];
$targetPath = dirname( __FILE__ ) . $ds. $storeFolder . $ds;
$_FILES['file']['name']=date('Ymdhis').$_FILES['file']['name'];
$targetFile = $targetPath. $_FILES['file']['name'];
if(move_uploaded_file($tempFile,$targetFile)){
// Save path to session var
$_SESSION['uploaded']=$targetFile;
//You can tell client if the uploading process were done and show 'Processing ...'
// Place some code
exit;
}
}
Next, create a file called progress.php
// check
if(!empty($_SESSION['uploaded'])){
// Do your processing code here
// Remove session
unset($_SESSION['uploaded']);
// Then send response to client after your processing were done
echo 'Done';
exit;
}
You can redirect client using jquery as you tagged it. Good luck

Categories

Resources