55use GuzzleHttp \Client ;
66use GuzzleHttp \Cookie \CookieJar ;
77use GuzzleHttp \Exception \ConnectException ;
8+ use GuzzleHttp \Exception \RequestException ;
9+ use GuzzleHttp \Exception \TransferException ;
810use GuzzleHttp \HandlerStack ;
911use Illuminate \Support \Collection ;
1012use Illuminate \Support \Str ;
1113use Illuminate \Support \Traits \Macroable ;
14+ use Psr \Http \Message \MessageInterface ;
1215use Symfony \Component \VarDumper \VarDumper ;
1316
1417class 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}
0 commit comments