Asynchronous PHP calls

PHP has no built-in support for making Asynchronous calls similar to Ajax style requests. You can use functions such as fopen, file_get_contents, and stream_get_contents to open URLs and process them, but you would need to sit there and wait for the entire page to get done before you can move on with your life. Say you wanted to copy file from one folder to another, you would do something like this:

$source = "original file";
$dest = "destination file";
copy($source, $dest);

If you’re writing a webapp, or a webpage with PHP and want to copy a 2 or 3 MB file, that’s perfectly fine. It’ll be done within a few seconds and you can be on your way, but what about copying an 800MB file? You wouldn’t want your client sitting there for 3 or 4 minutes with a “processing” cursor, trying to figure out if his computer crashed. This is where you need to copy the file asynchronously.

To do this, you could make a page, say “processor.php” which accepts 2 parameters: source, dest.
processor.php:

$source = urldecode($_GET["source"]);
$dest = urldecode($_GET["dest"]);
copy($source, $dest);
echo "Done!";

Then you need to make something to call that page. The best approach is to use cURL for a pseudo-asynchronous call.
Set CURLOPT_TIMEOUT to be 1 second. This way, you will run this request, and it will timeout within 1 second, while continuing to process the requested page in the background.

$source = urlencode("original file");
$dest = urlencode("destination file");
$request = "fullURL/processor.php?source=$source&dest=$dest";$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $request);
curl_setopt($ch, CURLOPT_FRESH_CONNECT, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 1);
curl_exec($ch);
curl_close($ch);

Now you have an Asynchronous calling system for which you can keep making pages to make more and more processes asynchronous. Or, you can use a single page for all processes, and encrypt it while you’re at it (using any 2-way encryption algorithm you want, I’m using base64 for simplicity purposes).

Make a library.
Crypt.lib.php:

function EncryptRequest($func, $params) {
    $uri = urlencode($func);
    foreach ($params as $name => $data) {
        $uri .= "&&" . urlencode($name) . "=" . urlencode($data);

    }

    $enc = Encrypt($uri);
    return urlencode($enc);
}

function DecryptRequest($str) {
    $uri = Decrypt($str);
    $opts = explode("&&", $uri);
    # Add error checking here
    $func = array_shift($opts);
    $params = array();

    foreach ($opts as $opt) {
        $param = split("=", $opt);
        # Add error checking here
        $params[urldecode($param[0])] = urldecode($param[1]);
    }

    return array($func, $params);
}

# Any 2-way encryption algorithm works here
function Encrypt($str) {
    return base64_encode($str);
}

function Decrypt($str) {
    return base64_decode($str);
}

So, now your request looks something like this:

require_once("Crypt.lib.php");

function RequestAsync($func, $params) {
    $request = "fullURL/processor.php?request=" . EncryptRequest($func, $params);

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $request);
    curl_setopt($ch, CURLOPT_FRESH_CONNECT, true);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 1);
    curl_exec($ch);
    curl_close($ch);
}

RequestAsync("CopyFile", array("source" => "original file", "dest" => "destination file"));

Now you have to do the opposite on the processor.php page:

require_once("Crypt.lib.php");

list($func, $params) = DecryptRequest(urldecode($_GET["request"]));

# Make sure the parameters are ordered properly
$rf = new ReflectionFunction($func);
$rps = $rf->getParameters();

$paramsOrdered = array();

if (is_array($params))
foreach ($rps as $rp) {
    $rpname = $rp->getName();
    $paramsOrdered[] = isset($params[$rpname]) ? $params[$rpname] : false;
}

call_user_func_array($func, $paramsOrdered);

function CopyFile($source, $dest) {
    copy($source, $dest);
}

echo "Done!";

I’ve left out a lot of the error checking for simplicity’s sake. When you implement this, you may want to error check the function names, parameter counts, parameter value types, files existing, etc…

I used base64 for the encryption because it’s simple, PHP has native support for it, and it’s fast. However, it is not secure. If you want to ensure that no one else can sniff out your request and forge other requests, you’ll need to change the encryption algorithm. Another idea, is to store these requests in the database, and simply pass an integer id for executing requests. When a request is picked up for execution, that database row is invalidated or deleted forcing a uniqueness to requests. This way, if someone sniffs out your request, and re-executes the same one, it won’t work. It’s up to you how secure you want the requests to be.

Leave a Reply

You must be logged in to post a comment.



View Nick Yaitsky's profile on LinkedIn
2 girls 1 cupadipex without prescription