2

I have a situation where I have a loop, that is going to read chunk of data from a file, send those chunk to a rest api, and continue until the EOF, but I want this to be async inside the loop, so, I don't have to wait until the API respond to read the next chunk. I have been looking at Amphp and ReactPHP for I can't find a solution to this, or maybe I don't understand how are those libraries supposed to be used. here is a pseudo of what I am doing.

<?php

while($file.read()){

   $chunk = getNextChunk();

   sendChunkAsync($chunk);

}

function getNextChunk(){

   echo "reading next chunk";

   // read next chunk of data

}

sample with amphp

function sendChunkAsync($chunk){

Loop::run(function () {

    $uri =  "https://testapi.com/api";

    $client = new DefaultClient;

    try {

            $promises = $client->request($uri);


        $responses = yield $promises;

       echo "chunk processed";

    } catch (Amp\Artax\HttpException $error) {

        // log error

        // $error->getMessage() . PHP_EOL;
    }
});

}

In this case I would expect (if reading chunk is faster than getting response from api) something like this, don't take this literary, I am trying to illustrate it for you.

Reading next chunk

Reading next chunk

chunk processed

Reading next chunk

chunk processed

chunk processed

shyammakwana.me
  • 5,562
  • 2
  • 29
  • 50
Peter
  • 123
  • 1
  • 9
  • I may be wrong but do you have it inside out, the send/revieve is the async bit, not the loop, the loop will just look when it receives data from the async function. Imo you need to call your async function in your loop structure – DarkMukke Dec 03 '18 at 17:31
  • DarkMukke, What do you mean by "inside out"? I want to make the call to the api async, and the code I posted is what I have, that is not working async, can you post here what do you mean with samples. As I said, maybe I don't understand this two libraries. – Peter Dec 04 '18 at 18:20

2 Answers2

1

I am going to use React as I know the library better but they work in similar ways.

EDIT: updated, see comments

This will read in a file and every time it recieves a chunk of data, it will create an api call and send the data off

<?php

require_once __DIR__ . '/vendor/autoload.php';

function async_send($config, $file, callable $proccessor)
{

    $config['ssl'] = true === $config['ssl'] ? 's' : '';
    $client = new \GuzzleHttp\Client([
        'base_uri' => 'http' . $config['ssl'] . '://' . $config['domain'] . '/rest/all/V1/',
        'verify' => false,
        'http_errors' => false
    ]);
    $loop = \React\EventLoop\Factory::create();
    $filesystem = \React\Filesystem\Filesystem::create($loop);
    $filesystem->getContents($file)->then(function($contents) use ($config, $proccessor, $client) {
        $contents = $proccessor($contents);
        $client->post($config['uri'], ['body' => $contents]);
    });
}

$config = [
    'domain' => 'example.com',
    'ssl' => true
];
//somewhere later
$configp['uri'] = 'products';
async_send($configp, __DIR__ . 'my.csv', function ($contents) {
    return json_encode($contents);
});
DarkMukke
  • 2,469
  • 1
  • 23
  • 31
  • DarkMukke, if you run this, it will be run in this order, ob_start(); ob_flush(); ob_end_clean(); and that is not what async should be, I would expect ob_start(); ob_end_clean(); and because the api call will take some time to get back ob_flush(); s – Peter Dec 04 '18 at 19:09
  • Then you are very unclear of what the point is, what else are you trying to do while waiting for the API to respond ? PHP is a blocking language, eventually it will come back to script start -> script end – DarkMukke Dec 04 '18 at 19:48
  • The point is I want to keep processing my file and send data to the api without being block while waiting for the api response. I imagine the server is going to be doing nothing but awaiting for the api response, that is what async is all about, returning the control to the caller while waiting for the operation to complete. I know php is single thread, but this is async not multithreading. – Peter Dec 04 '18 at 19:57
  • Ok Hold on, I will try again – DarkMukke Dec 04 '18 at 19:58
  • Because it point me in the another right direction that I never thought about. Thank you DarkMukke – Peter Dec 05 '18 at 14:27
  • 1
    This does a blocking HTTP request. Side note: You shouldn't disable peer verification. If you do that, you could as well just use plaintext, basically. – kelunik Jan 04 '19 at 10:11
  • @kelunik you are right, i got most of that code because i was working with it when i made that post and my local server doesn't have valid ssl. Although not verifying is not the same as plain text, with plain text, everyone can see it, with no verify only a potential fake endpoint can see it – DarkMukke Jan 04 '19 at 11:26
  • @DarkMukke: Right, the difference is that without authentication only an active attacker can read the traffic, while otherwise any (also passive) attackers can read the content. – kelunik Jan 04 '19 at 12:56
1

In case someone else is trying to solve a similar problem

<?php

require_once __DIR__ . '/../vendor/autoload.php';

use React\HttpClient\Client as ReactClient;

function async_send($loop, $filePath, callable $proccessor)
{
    echo "starting";
    echo "\n\r";

    try {

        $filesystem = \React\Filesystem\Filesystem::create($loop);

        $file = $filesystem->file($filePath);
        $file->open('r')
            ->then(function ($stream) use ($loop, $proccessor){
                $stream->on('data', function ($chunk) use ($loop, $proccessor) {
                   $proccessor($chunk);
                });
            });

    } catch (\Exception $e) {
        echo "failed";
        echo "\n\r";
    }
    echo "ending reading";
    echo "\n\r";
}

function callApiReal($loop, $fileChunk = null)
{
    echo "ready to call api". PHP_EOL;

    $uri = "https://testapi.com/";
    try {
        $client = new ReactClient($loop);
    } catch (\Exception $e) {
        echo "Error";
    }
    echo "ready to call api";

    $request = $client->request('POST', $uri, $fileChunk);

    $request->on('response', function ($response) use ($uri) {

        $response->on('data', function ($data_chunk) {
            echo 'data chunk from api received';
            echo "\n\r";
        });

        // subscribe to listen to the end of the response
        $response->on('end', function () use ($uri) {
            echo "operation has completed";
            echo "\n\r";
        });
    });

    $request->on('error', function ($error) {
        // something went bad in the request
        echo "Damm!";
        echo "\n\r";
    });

    $request->end();

}

// main loop
$loop = React\EventLoop\Factory::create();

//somewhere later
async_send($loop, __DIR__ . '/my.csv', function ($chunk) use ($loop) {
    echo "calling api";
    callApiReal($loop, $chunk);
    echo "\n\r";
});

$loop->run();
Peter
  • 123
  • 1
  • 9