Guzzle 7: Envíar solicitudes concurrentes

[vc_row el_class=”blog-info”][vc_column][vc_single_image source=”featured_image” img_size=”full” style=”vc_box_rounded”][vc_empty_space height=”40px”][vc_row_inner][vc_column_inner width=”1/6″][/vc_column_inner][vc_column_inner width=”2/3″][vc_column_text el_class=”font-weight-bold”]

Laravel 8 – envía solicitudes concurrentes usando Guzzle 7

El día de hoy trabajaremos en un proyecto en el que estaba enviando ~ 15 solicitudes a API externas, cada una de las cuales tomó ~ 1 s, lo que significa que el tiempo total de solicitud hasta que el usuario recibió una respuesta fue de alrededor de 15 segundos.

[/vc_column_text][vc_empty_space height=”40px”][/vc_column_inner][vc_column_inner width=”1/6″][/vc_column_inner][/vc_row_inner][vc_row_inner][vc_column_inner width=”1/6″][/vc_column_inner][vc_column_inner width=”2/3″][vc_column_text]


Queremos hacer esto más rápido, así que investigamos el envío de estas solicitudes externas al mismo tiempo y pudimos reducir el tiempo de respuesta total a ~ 1 segundo. Este es un ejemplo sobre cómo hacerlo:

  • Crearemos un proyecto nuevo:
laravel new concurrent_requests_example
  • Usaremos Guzzle 7 para enviar las solicitudes:
composer require guzzlehttp/guzzle
  • Haremos un controlador:
php artisan make:Controller ExampleController
  • Para comparar la diferencia entre solicitudes secuenciales y concurrentes, haremos un punto final para cada una de ellas:
class ExampleController extends Controller
{
    /**
     * Sends requests sequentially
     */
    public function sequential()
    {    }    /**
     * Sends requests concurrently
     */
    public function concurrent()
    {    }
}
  • Y las rutas que pondremos en web.php:
/* laravel 8 syntax */
Route::get('/sequential', [ExampleController::class, 'sequential']);
Route::get('/concurrent', [ExampleController::class, 'concurrent']);/* pre-laravel 8 syntax */
Route::get('/sequential', 'ExampleController@sequential');
Route::get('/concurrent', 'ExampleController@concurrent');
  • Ahora buscamos 10 apis públicas al azar y ponemos solicitudes secuenciales en nuestro método secuencial ():
/**
 * Sends requests sequentially
 */
public function sequential()
{
    $time_start = microtime(true);    $client = new Client();    $responses = [];    $responses['archive'] = $client->get('<https://archive.org/advancedsearch.php?q=subject:google+sheets&output=json>');
    $responses['cat_facts'] = $client->get('<https://cat-fact.herokuapp.com/facts>');
    $responses['coin_gecko'] = $client->get('<https://api.coingecko.com/api/v3/exchange_rates>');
    $responses['universities'] = $client->get('<http://universities.hipolabs.com/search?country=United+Kingdom>');
    $responses['countries'] = $client->get('<https://restcountries.eu/rest/v2/all>');
    $responses['randomuser'] = $client->get('<https://randomuser.me/api/>');
    $responses['punkapi'] = $client->get('<https://api.punkapi.com/v2/beers>');
    $responses['publicapis'] = $client->get('<https://api.publicapis.org/entries>');
    $responses['openlibrary'] = $client->get('<https://openlibrary.org/api/volumes/brief/isbn/9780525440987.json>');
    $responses['food_facts'] = $client->get('<https://world.openfoodfacts.org/api/v0/product/737628064502.json>');    foreach ($responses as $key => $result)
    {
        echo $key . "<br>";        //$result is a Guzzle Response object
        //do stuff with $result
        //eg. $result->getBody()
    }    echo 'Sequential execution time in seconds: ' . (microtime(true) - $time_start);
}
  • Y para el método concurrente () utilizamos los mismos puntos finales:
/**
 * Sends requests concurrently
 */
public function concurrent()
{
    $time_start = microtime(true);    $client = new Client();    $promises = [
        'archive' => $client->getAsync('<https://archive.org/advancedsearch.php?q=subject:google+sheets&output=json>'),
        'cat_facts'   => $client->getAsync('<https://cat-fact.herokuapp.com/facts>'),
        'coin_gecko'  => $client->getAsync('<https://api.coingecko.com/api/v3/exchange_rates>'),
        'universities'  => $client->getAsync('<http://universities.hipolabs.com/search?country=United+Kingdom>'),
        'countries'  => $client->getAsync('<https://restcountries.eu/rest/v2/all>'),
        'randomuser'  => $client->getAsync('<https://randomuser.me/api/>'),
        'punkapi'  => $client->getAsync('<https://api.punkapi.com/v2/beers>'),
        'publicapis'  => $client->getAsync('<https://api.publicapis.org/entries>'),
        'openlibrary'  => $client->getAsync('<https://openlibrary.org/api/volumes/brief/isbn/9780525440987.json>'),
        'food_facts'  => $client->getAsync('<https://world.openfoodfacts.org/api/v0/product/737628064502.json>'),
    ];    $responses = Promise\\Utils::settle($promises)->wait();    foreach ($responses as $key => $response)
    {
        echo $key . "<br>";        //response state is either 'fulfilled' or 'rejected'
        if($response['state'] === 'rejected')
        {
            //handle rejected
            continue;
        }        //$result is a Guzzle Response object
        $result = $response['value'];        //do stuff with $result
        //eg. $result->getBody()
    }    echo 'Concurrent execution time in seconds: ' . (microtime(true) - $time_start);
}
  • Salida secuencial:
archive
cat_facts
coin_gecko
universities
countries
randomuser
punkapi
publicapis
openlibrary
food_facts
Sequential execution time in seconds: 19.31148982048
  • Salida concurrente:
archive
cat_facts
coin_gecko
countries
food_facts
openlibrary
publicapis
punkapi
randomuser
universities
Concurrent execution time in seconds: 4.8118050098419

Como puede ver, hay un ahorro de tiempo significativo de unos 15 segundos. Cuantas más solicitudes tenga, más significativa será la brecha.

¡Ojalá esto ayude a alguien!

[/vc_column_text][/vc_column_inner][vc_column_inner width=”1/6″][/vc_column_inner][/vc_row_inner][/vc_column][/vc_row][vc_row el_class=”social-info”][vc_column width=”1/6″][/vc_column][vc_column width=”2/3″][vc_row_inner][vc_column_inner width=”1/2″][vc_column_text][social_share_button themes=’theme1′][/vc_column_text][/vc_column_inner][vc_column_inner el_class=”youtube-inner-col” width=”1/2″][vc_column_text][likebtn theme=”youtube” lang=”auto” show_like_label=”0″ white_label=”1″ alignment=”right”][/vc_column_text][/vc_column_inner][/vc_row_inner][vc_row_inner el_class=”social-info-inner”][vc_column_inner width=”1/4″][vc_single_image image=”921″][/vc_column_inner][vc_column_inner width=”3/4″][vc_column_text]

Diego Pacheco

Ingeniero en Sitemas, MBA (Babson College). Desarrollador PHP/Java/JavaScript. Fundador & CEO de EpicStudio. Entusiasta de las tecnologías web (JavaScript, Vue, Laravel, AWS, Docker) Viajes, Negocios, Surf y Growth.[/vc_column_text][asvc_list_item icon_fontawesome=”fa fa-calendar-o” icon_size=”14px”]Programar una reunión[/asvc_list_item][/vc_column_inner][/vc_row_inner][/vc_column][vc_column width=”1/6″][/vc_column][/vc_row][vc_row][vc_column][vc_column_text]

Recent Post

[/vc_column_text][lvca_posts_carousel posts_query=”size:3|order_by:rand|post_type:post” image_linkable=”true” image_size=”full” taxonomy_chosen=”post_tag” display_title=”true” display_post_date=”true” display_summary=”true” autoplay_speed=”3000″ animation_speed=”300″ display_columns=”3″ scroll_columns=”3″ gutter=”3″ tablet_display_columns=”2″ tablet_scroll_columns=”2″ tablet_gutter=”3″ tablet_width=”800″ mobile_display_columns=”1″ mobile_scroll_columns=”1″ mobile_gutter=”3″ mobile_width=”480″][vc_empty_space height=”20px”][/vc_column][/vc_row]