Skip to content

Instantly share code, notes, and snippets.

@tonysm
Last active November 1, 2023 13:08
Show Gist options
  • Save tonysm/67a7e9a592aa8883019d8f51ae8ae39e to your computer and use it in GitHub Desktop.
Save tonysm/67a7e9a592aa8883019d8f51ae8ae39e to your computer and use it in GitHub Desktop.
Concurrent Requests
<?php
// 1. Register the routes
Route::get('test/{lorem}', function ($lorem) {
sleep(3);
return response()->json([
'message' => $lorem,
'token' => Str::random(),
]);
});
// 2. Spin up 4 `php artisan serve` processes (they will get assigned from ports 8000..8003)
// 3. Register the macro
class PendingConcurrentPool
{
private int $concurrency = 10;
private Closure $requestsBuilder;
public function __construct(Closure $requestsBuilder)
{
$this->requestsBuilder = $requestsBuilder;
}
public function concurrency(int $amount): self
{
$this->concurrency = $amount;
return $this;
}
public function wait(): Collection
{
$responses = collect();
$pool = new Pool(new Client(), call_user_func($this->requestsBuilder), [
'concurrency' => $this->concurrency,
'fulfilled' => function (Response $response, $index) use ($responses) {
$responses[$index] = new \Illuminate\Http\Client\Response($response);
},
'rejected' => function (RequestException $reason, $index) use ($responses) {
$responses[$index] = new \Illuminate\Http\Client\Response($reason->getResponse());
},
]);
$pool->promise()->wait();
return $responses;
}
}
PendingRequest::macro('pool', function (Closure $requestsBuilder) {
return new PendingConcurrentPool($requestsBuilder);
});
// 4. Paste this in your tests/Feature/ExampleTest.php
public function testSendsConcurrentRequests()
{
$responses = Http::pool(function () {
return yield from [
'req-1' => new Request('GET', 'http://localhost:8000/test/req-1'),
'req-2' => new Request('GET', 'http://localhost:8001/test/req-2'),
'req-3' => new Request('GET', 'http://localhost:8002/test/req-3'),
'req-4' => new Request('GET', 'http://localhost:8003/test/req-4'),
];
})->concurrency(4)->wait();
dump($responses->map->json());
}
// 5. The output should be something like this:
/*
Illuminate\Support\Collection^ {#670
#items: array:4 [
"req-1" => array:2 [
"message" => "req-1"
"token" => "hJXWAcsTzauf0pgM"
]
"req-2" => array:2 [
"message" => "req-2"
"token" => "plkQsQEiHDhLF90i"
]
"req-3" => array:2 [
"message" => "req-3"
"token" => "g1nPymiH4ao1BNSb"
]
"req-4" => array:2 [
"message" => "req-4"
"token" => "VB9UXvu7ekbJ1dEC"
]
]
}
*/
// Each request takes 3 seconds (see route, there is a sleep there), but since we
// are sending them all at once (we are sending 4 requests with a concurrency
// of 4), the total amount of time to send all 4 requests is 3 seconds.
@mateussantana
Copy link

Hello, I have some questions:

  1. Line 37: Pool and Client are GuzzleHttp\Pool and GuzzleHttp\Client classes?
  2. Line 39: Response is a Illuminate\Http\Client\Response class?
  3. Line 42: GuzzleHttp\Exception\RequestException class?
  4. Line 53: Illuminate\Http\Client\PendingRequest class?
  5. Line 61: Illuminate\Support\Facades\Http class?

Thanks for sharing!

@tonysm
Copy link
Author

tonysm commented Jun 11, 2022

Good guesses. Those should be right. However, we now already have concurrent requests built into Laravel's HTTP Client (docs), so this snippet is not needed anymore.

@mateussantana
Copy link

Thank you!

@jrean
Copy link

jrean commented Apr 13, 2023

@tonysm Using the HTTP Client how do you set the maximum concurrent requests value?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment