Dagroa
Dagroa

Reputation: 53

Send a Post via Ajax to run a PHP script in the background

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!

Upvotes: 3

Views: 1837

Answers (2)

Will B.
Will B.

Reputation: 18416

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.

Upvotes: 0

Adis Azhar
Adis Azhar

Reputation: 1022

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

Upvotes: 1

Related Questions