Skip to content

Commit 1c8038d

Browse files
committed
Merge branch 'feature/http-pool' into 8.x
2 parents cf439a0 + 245a712 commit 1c8038d

File tree

5 files changed

+302
-11
lines changed

5 files changed

+302
-11
lines changed

src/Illuminate/Http/Client/Factory.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
* @method \Illuminate\Http\Client\PendingRequest withoutVerifying()
3535
* @method \Illuminate\Http\Client\PendingRequest dump()
3636
* @method \Illuminate\Http\Client\PendingRequest dd()
37+
* @method \Illuminate\Http\Client\PendingRequest async()
38+
* @method \Illuminate\Http\Client\Pool pool()
3739
* @method \Illuminate\Http\Client\Response delete(string $url, array $data = [])
3840
* @method \Illuminate\Http\Client\Response get(string $url, array $query = [])
3941
* @method \Illuminate\Http\Client\Response head(string $url, array $query = [])

src/Illuminate/Http/Client/PendingRequest.php

Lines changed: 146 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
use GuzzleHttp\Client;
66
use GuzzleHttp\Cookie\CookieJar;
77
use GuzzleHttp\Exception\ConnectException;
8+
use GuzzleHttp\Exception\RequestException;
9+
use GuzzleHttp\Exception\TransferException;
810
use GuzzleHttp\HandlerStack;
911
use Illuminate\Support\Collection;
1012
use Illuminate\Support\Str;
1113
use Illuminate\Support\Traits\Macroable;
14+
use Psr\Http\Message\MessageInterface;
1215
use Symfony\Component\VarDumper\VarDumper;
1316

1417
class PendingRequest
@@ -22,6 +25,13 @@ class PendingRequest
2225
*/
2326
protected $factory;
2427

28+
/**
29+
* The Guzzle client instance.
30+
*
31+
* @var \GuzzleHttp\Client
32+
*/
33+
protected $client;
34+
2535
/**
2636
* The base URL for the request.
2737
*
@@ -106,6 +116,20 @@ class PendingRequest
106116
*/
107117
protected $middleware;
108118

119+
/**
120+
* Whether the requests should be asynchronous.
121+
*
122+
* @var bool
123+
*/
124+
protected $async = false;
125+
126+
/**
127+
* The pending request promise.
128+
*
129+
* @var \GuzzleHttp\Promise\PromiseInterface
130+
*/
131+
protected $promise;
132+
109133
/**
110134
* Create a new HTTP Client instance.
111135
*
@@ -571,6 +595,27 @@ public function delete($url, $data = [])
571595
]);
572596
}
573597

598+
/**
599+
* Send a pool of asynchronous requests concurrently.
600+
*
601+
* @param callable $callback
602+
* @return array
603+
*/
604+
public function pool(callable $callback)
605+
{
606+
$results = [];
607+
608+
$requests = tap(new Pool($this->factory), $callback)->getRequests();
609+
610+
foreach ($requests as $key => $item) {
611+
$results[$key] = $item instanceof static ? $item->getPromise()->wait() : $item->wait();
612+
}
613+
614+
ksort($results);
615+
616+
return $results;
617+
}
618+
574619
/**
575620
* Send the request to the given URL.
576621
*
@@ -601,18 +646,14 @@ public function send(string $method, string $url, array $options = [])
601646

602647
[$this->pendingBody, $this->pendingFiles] = [null, []];
603648

649+
if ($this->async) {
650+
return $this->makePromise($method, $url, $options);
651+
}
652+
604653
return retry($this->tries ?? 1, function () use ($method, $url, $options) {
605654
try {
606-
$laravelData = $this->parseRequestData($method, $url, $options);
607-
608-
return tap(new Response($this->buildClient()->request($method, $url, $this->mergeOptions([
609-
'laravel_data' => $laravelData,
610-
'on_stats' => function ($transferStats) {
611-
$this->transferStats = $transferStats;
612-
},
613-
], $options))), function ($response) {
614-
$response->cookies = $this->cookies;
615-
$response->transferStats = $this->transferStats;
655+
return tap(new Response($this->sendRequest($method, $url, $options)), function ($response) {
656+
$this->populateResponse($response);
616657

617658
if ($this->tries > 1 && ! $response->successful()) {
618659
$response->throw();
@@ -637,6 +678,49 @@ protected function parseMultipartBodyFormat(array $data)
637678
})->values()->all();
638679
}
639680

681+
/**
682+
* Send an asynchronous request to the given URL.
683+
*
684+
* @param string $method
685+
* @param string $url
686+
* @param array $options
687+
* @return \GuzzleHttp\Promise\PromiseInterface
688+
*/
689+
protected function makePromise(string $method, string $url, array $options = [])
690+
{
691+
return $this->promise = $this->sendRequest($method, $url, $options)
692+
->then(function (MessageInterface $message) {
693+
return $this->populateResponse(new Response($message));
694+
})
695+
->otherwise(function (TransferException $e) {
696+
return $e instanceof RequestException ? $this->populateResponse(new Response($e->getResponse())) : $e;
697+
});
698+
}
699+
700+
/**
701+
* Send a request either synchronously or asynchronously.
702+
*
703+
* @param string $method
704+
* @param string $url
705+
* @param array $options
706+
* @return \Psr\Http\Message\MessageInterface|\GuzzleHttp\Promise\PromiseInterface
707+
*
708+
* @throws \Exception
709+
*/
710+
protected function sendRequest(string $method, string $url, array $options = [])
711+
{
712+
$clientMethod = $this->async ? 'requestAsync' : 'request';
713+
714+
$laravelData = $this->parseRequestData($method, $url, $options);
715+
716+
return $this->buildClient()->$clientMethod($method, $url, $this->mergeOptions([
717+
'laravel_data' => $laravelData,
718+
'on_stats' => function ($transferStats) {
719+
$this->transferStats = $transferStats;
720+
},
721+
], $options));
722+
}
723+
640724
/**
641725
* Get the request data as an array so that we can attach it to the request for convenient assertions.
642726
*
@@ -664,14 +748,29 @@ protected function parseRequestData($method, $url, array $options)
664748
return $laravelData;
665749
}
666750

751+
/**
752+
* Populate the given response with additional data.
753+
*
754+
* @param \Illuminate\Http\Client\Response $response
755+
* @return \Illuminate\Http\Client\Response
756+
*/
757+
protected function populateResponse(Response $response)
758+
{
759+
$response->cookies = $this->cookies;
760+
761+
$response->transferStats = $this->transferStats;
762+
763+
return $response;
764+
}
765+
667766
/**
668767
* Build the Guzzle client.
669768
*
670769
* @return \GuzzleHttp\Client
671770
*/
672771
public function buildClient()
673772
{
674-
return new Client([
773+
return $this->client = $this->client ?: new Client([
675774
'handler' => $this->buildHandlerStack(),
676775
'cookies' => true,
677776
]);
@@ -826,4 +925,40 @@ public function stub($callback)
826925

827926
return $this;
828927
}
928+
929+
/**
930+
* Toggle asynchronicity in requests.
931+
*
932+
* @param bool $async
933+
* @return $this
934+
*/
935+
public function async(bool $async = true)
936+
{
937+
$this->async = $async;
938+
939+
return $this;
940+
}
941+
942+
/**
943+
* Retrieve the pending request promise.
944+
*
945+
* @return \GuzzleHttp\Promise\PromiseInterface|null
946+
*/
947+
public function getPromise()
948+
{
949+
return $this->promise;
950+
}
951+
952+
/**
953+
* Set the client instance.
954+
*
955+
* @param \GuzzleHttp\Client $client
956+
* @return $this
957+
*/
958+
public function setClient(Client $client)
959+
{
960+
$this->client = $client;
961+
962+
return $this;
963+
}
829964
}

src/Illuminate/Http/Client/Pool.php

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
3+
namespace Illuminate\Http\Client;
4+
5+
class Pool
6+
{
7+
/**
8+
* The factory instance.
9+
*
10+
* @var \Illuminate\Http\Client\Factory
11+
*/
12+
protected $factory;
13+
14+
/**
15+
* The client instance.
16+
*
17+
* @var \GuzzleHttp\Client
18+
*/
19+
protected $client;
20+
21+
/**
22+
* The pool of requests.
23+
*
24+
* @var array
25+
*/
26+
protected $pool = [];
27+
28+
/**
29+
* Create a new requests pool.
30+
*
31+
* @param \Illuminate\Http\Client\Factory|null $factory
32+
* @return void
33+
*/
34+
public function __construct(Factory $factory = null)
35+
{
36+
$this->factory = $factory ?: new Factory();
37+
38+
$this->client = $this->factory->buildClient();
39+
}
40+
41+
/**
42+
* Add a request to the pool with a key.
43+
*
44+
* @param string $key
45+
* @return \Illuminate\Http\Client\PendingRequest
46+
*/
47+
public function as(string $key)
48+
{
49+
return $this->pool[$key] = $this->asyncRequest();
50+
}
51+
52+
/**
53+
* Retrieve a new async pending request.
54+
*
55+
* @return \Illuminate\Http\Client\PendingRequest
56+
*/
57+
protected function asyncRequest()
58+
{
59+
return $this->factory->setClient($this->client)->async();
60+
}
61+
62+
/**
63+
* Retrieve the requests in the pool.
64+
*
65+
* @return array
66+
*/
67+
public function getRequests()
68+
{
69+
return $this->pool;
70+
}
71+
72+
/**
73+
* Add a request to the pool with a numeric index.
74+
*
75+
* @param string $method
76+
* @param array $parameters
77+
* @return \Illuminate\Http\Client\PendingRequest
78+
*/
79+
public function __call($method, $parameters)
80+
{
81+
return $this->pool[] = $this->asyncRequest()->$method(...$parameters);
82+
}
83+
}

src/Illuminate/Support/Facades/Http.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
* @method static \Illuminate\Http\Client\PendingRequest withoutVerifying()
3333
* @method static \Illuminate\Http\Client\PendingRequest dump()
3434
* @method static \Illuminate\Http\Client\PendingRequest dd()
35+
* @method static \Illuminate\Http\Client\PendingRequest async()
36+
* @method static \Illuminate\Http\Client\Pool pool()
3537
* @method static \Illuminate\Http\Client\Response delete(string $url, array $data = [])
3638
* @method static \Illuminate\Http\Client\Response get(string $url, array $query = [])
3739
* @method static \Illuminate\Http\Client\Response head(string $url, array $query = [])

0 commit comments

Comments
 (0)