3

My goal is to create a function in Ajax to send post data to a PHP script on a different server. The intended result is to have the PHP script disconnect from the client and run the entire PHP scrip in the background on the server, to allow the user to navigate freely, and not have them wait for the PHP script to finish. I have successfully posted the data to the PHP script, but I cannot get the PHP script to continue running after disconnecting from the client. I do not want or need the PHP script to output anything to any other scripts. I just need it to run in the background.

I have tried using ignore_user_abort(true), header("Connection: close\r\n") and other connection handling in PHP, but have been unsuccessful. This is my latest attempt at achieving the desired functionality.

Sending script

<script>
    function sendingRequest()
    {
        var form = document.getElementById("submit_change");
        var var1 = document.getElementById('var1').value;
        var var2 = document.getElementById('var2').value;
        var var3 = document.getElementById('var3').value;
        var dataString = 'var1='+ var1 + '&var2=' + var2 + '&var3=' + var3;
        $.ajax({
            type:"post",
            url:"recievingScript.php",
            data:dataString,
            cache:false
        });
        form.submit();
        return false;
    }
</script>

PHP script

<?php
ignore_user_abort(true);
//includes, uses, requires, establish db connection
$var1 = $_POST['var1'];
$var2 = $_POST['var2'];
$var3 = $_POST['var3'];

header("Access-Control-Allow-Origin: *");
header("Connection: close");

//Code to be run

//end script
?>

With a success: in the Ajax post the PHP script successfully runs and sends a success message to the script that called the PHP script. But the script takes about 10 seconds to run, so it is not a good idea to make the user wait for the script to finish. Any advice is appreciated, thanks!

6
  • 1
    Sadly AJAX/PHP is not stateless, so the client waits for the response. It sounds a lot like your looking for a Message Broker system. I suggest looking into a framework like RabbitMQ or ZeroMQ. For a non-php approach, check into node.js + socket.io or WebSocket solutions. The concept is a client can send a message request and simply disconnect, while a separate system can listen for a response. Commented Jan 15, 2019 at 5:14
  • Really? dang, no wonder I spent like 5 hours trying to make it work yesterday. The issue is that I am basically handcuffed to using PHP for this, Ill have to look into RabbitMQ or ZeroMQ. Thanks! Commented Jan 15, 2019 at 12:33
  • What version of PHP are you using, and what environment is PHP being hosted under (Windows, Mac, Linux, Apache, IIS, NGINX)? Are you using a Database Management System like MySQL, PostgreSQL or MongoDB? Also what exactly is happening that lasts 10 seconds? Commented Jan 15, 2019 at 16:46
  • The issue is that AJAX needs to know that a connection was established and that the full request was received by the recipient. Otherwise you run the risk of losing data and corrupted information being received. You could use xhr.abort() once the xhr.readystate becomes 2, meaning the request was sent. But you would need to check the progress of your request. Commented Jan 15, 2019 at 17:03
  • Hey, I have PHP v5.4, it runs on a Linux server, I use PostreSQL in the script I uses the phpseclib library to SSH into a remote device and alter its config file. But I have just found a fatal flaw with what I'm trying to do. As before the script finishes, the device reboots, and breaks connection. Cant really go into much more detail as a NDA prevents that. I have decided that I am going to use CRON via Linux to schedule script runs instead of clients triggering script runs, Thanks for the Input! Commented Jan 15, 2019 at 17:43

2 Answers 2

1

You can setup what you call a background worker. The function executed will be done in the 'background', meaning users don't have to wait until the function fully finish. You can then expose a route/ Api to call that function. In laravel they call it Jobs that can be queued.

Personally, I don't have any experience setting up a background worker except in Laravel, which comes out of the box. But you can check these references!

Class Worker PHP

doBackground PHP

Laravel Queues

Sign up to request clarification or add additional context in comments.

3 Comments

It's important to note that both Class Worker (pThreads - last updated 2016) and doBackground (Gearman - last updated 2013) are PECL extensions that appear to no longer be maintained and may be difficult to install under some environments.
Thank you @fyrye . Do you have any recommendations to what may be suitable?
It would depend on the environment the OP is working with. There are multiple alternatives including cron tasks and pcntl_fork to run PHP as background operations. I have used pcntl_fork to listen for UDP packets for days purely in PHP or to run report generations that took 20 minutes to complete. However due to the complexities of pcntl_fork and inherent issues with persistent connectivity, I personally switched to a more stable and better supported non-PHP solution for my requirements.
0

I was curious and decided to attempt the xhr.abort() suggestion from my comment.

Below is just a basic example of accomplishing a long running PHP AJAX request, without the client waiting for the response from the server or rather early termination of the XHR and PHP continuing script execution.

This approach can be be adapted to run a local script that issues a cURL or SOAP request to an external host as well.

https://first.domain.com/send.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Send It</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<form method="post" action="https://second.domain.com/recievingScript.php">
    <p><input type="text" name="name"/></p>
    <p>
        <button type="submit">Send</button>
    </p>
</form>
<script type="text/javascript">
    const writeLog = function(msg) {
        let date = new Date();
        window.console.log(date.toISOString() + ' ' + msg);
    };
    jQuery(function($) {
        let f = $('form');
        let xhrOptions = {
            url: f.attr('action'),
            type: f.attr('method'),
            data: {},
            cache: false,
            xhr: function() {
                let xhr = $.ajaxSettings.xhr();
                if ('undefined' === typeof xhr.upload.onload || null === xhr.upload.onload) {
                    //override the upload.onload event, only if it has not already been
                    xhr.upload.onload = function() {
                        //onload is triggered immediately after the POST headers are sent to the recipient
                        writeLog('Upload Completed - Ending Request');
                        xhr.abort();
                    };
                }
                return xhr;
            },
        };
        f.on('submit', function(e) {
            e.preventDefault();
            let formData = f.serialize();
            writeLog(formData);
            $.ajax($.extend(true, xhrOptions, {
                data: formData
            })).done(function(responseText) {
                //this is never triggered since the request is aborted
                writeLog('Success');
                writeLog(responseText);
            }).fail(function(jqxhr) {
                writeLog('Request ' + (jqxhr.readyState !== 4 ? 'Aborted' : 'Failed'));
            });
            return false;
        });
    });
</script>
</body>
</html>

Response:

send.html:18 2019-01-15T21:19:11.445Z name=Hello%20Dagroa
send.html:18 2019-01-15T21:19:11.558Z Upload Completed - Ending Request
send.html:18 2019-01-15T21:19:11.558Z Request Aborted

https://second.domain.com/recievingScript.php

\date_default_timezone_set('UTC');
\ignore_user_abort(true);
\header('Access-Control-Allow-Origin: https://first.domain.com');
if (!empty($_POST)) {
    $dateStart = new \DateTimeImmutable();
    //create a placeholder file
    if ($file = \tempnam(__DIR__, 'tmp_')) {
        $i = 1;
        //PHP >= 7 required for random_int - PHP < 7 use mt_rand() instead
        $rand = \random_int(5, 30);
        //add the number of seconds to create a stop date
        $dateStop = $dateStart->add(new \DateInterval(\sprintf('PT' . $rand . 'S')));
        while (new \DateTime() < $dateStop) {
            //loop until the stop date is reached
            $i++;
        }
        $dateEnd = new \DateTime();
        $dateDiff = $dateEnd->diff($dateStart);
        //write result to the temporary file
        \file_put_contents($file, \json_encode([
            'file' => \basename($file),
            'scriptFile' => \basename($_SERVER['SCRIPT_FILENAME']),
            'iterations' => $i,
            'start' => $dateStart->format(\DATE_RFC3339_EXTENDED),
            'end' => $dateEnd->format(\DATE_RFC3339_EXTENDED),
            'stop' => $dateStop->format(\DATE_RFC3339_EXTENDED),
            'elapsed' => $dateDiff->format('%i minutes, %s seconds, %f microseconds'),
            'slept' => $rand,
            'post' => $_POST,
        ], \JSON_PRETTY_PRINT));
    }
}

Response:

{
    "file": "tmp_hjLk4y",
    "scriptFile": "recievingScript.php",
    "iterations": 9653192,
    "start": "2019-01-15T21:19:12.171+00:00",
    "end": "2019-01-15T21:19:36.171+00:00",
    "stop": "2019-01-15T21:19:36.171+00:00",
    "elapsed": "0 minutes, 24 seconds, 3 microseconds",
    "slept": 24,
    "post": {
        "name": "Hello Dagroa"
    }
}

Notes: I use php-fpm 7.2 as an fcgi proxy using Apache 2.4, which should not matter other than with the random_int function call and DATE_RFC3339_EXTENDED.

Additionally the const and let usage in the JavaScript is an ECMAScript 6 specification, which is supported by all of the current major browser versions.

PHP output buffering should be disabled, otherwise you may need to force it to be flushed using flush() and/or ob_end_flush(). Certain other conditions from your web server configuration may also affect output buffering, such as gzip encoding.

jQuery will issue an OPTIONS request method just prior to the POST request method. Ensure to check that $_POST is not empty or check $_SERVER['REQUEST_METHOD'] for the desired type, to prevent double execution.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.