Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@
"dragonmantank/cron-expression": "^3.4",
"egulias/email-validator": "^4.0",
"fruitcake/php-cors": "^1.3",
"guzzlehttp/guzzle": "^7.8.2",
"guzzlehttp/promises": "^2.0.3",
"guzzlehttp/uri-template": "^1.0",
"guzzlehttp/guzzle": "^7.8.2 || ^8.0",
"guzzlehttp/promises": "^2.0.3 || ^3.0",
"guzzlehttp/psr7": "^2.9.1 || ^3.0",
"guzzlehttp/uri-template": "^1.0 || ^2.0",
"laravel/prompts": "^0.3.0",
"laravel/serializable-closure": "^2.0.10",
"league/commonmark": "^2.8.1",
Expand All @@ -45,6 +46,7 @@
"nesbot/carbon": "^3.8.4",
"nunomaduro/termwind": "^2.0",
"psr/container": "^1.1.1 || ^2.0.1",
"psr/http-message": "^1.0 || ^2.0",
"psr/log": "^1.0 || ^2.0 || ^3.0",
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0",
"ramsey/uuid": "^4.7",
Expand All @@ -71,7 +73,6 @@
"ably/ably-php": "^1.0",
"aws/aws-sdk-php": "^3.322.9",
"fakerphp/faker": "^1.24",
"guzzlehttp/psr7": "^2.9",
"laravel/pint": "^1.18",
"league/flysystem-aws-s3-v3": "^3.25.1",
"league/flysystem-ftp": "^3.25.1",
Expand Down Expand Up @@ -166,7 +167,6 @@
"php-http/discovery": "Required to use PSR-7 bridging features (^1.15).",
"phpunit/phpunit": "Required to use assertions and run tests (^11.5.50 || ^12.5.8 || ^13.0.3).",
"predis/predis": "Required to use the predis connector (^2.3 || ^3.0).",
"psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).",
"pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0 || ^7.0).",
"resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0 || ^1.0).",
"spatie/fork": "Required to use the 'fork' concurrency driver (^1.2).",
Expand Down
2 changes: 1 addition & 1 deletion src/Illuminate/Console/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"suggest": {
"ext-pcntl": "Required to use signal trapping.",
"dragonmantank/cron-expression": "Required to use scheduler (^3.3.2).",
"guzzlehttp/guzzle": "Required to use the ping methods on schedules (^7.8).",
"guzzlehttp/guzzle": "Required to use the ping methods on schedules (^7.8.2 || ^8.0).",
"illuminate/bus": "Required to use the scheduled job dispatcher (^13.0).",
"illuminate/container": "Required to use the scheduler (^13.0).",
"illuminate/filesystem": "Required to use the generator command (^13.0).",
Expand Down
2 changes: 1 addition & 1 deletion src/Illuminate/Filesystem/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).",
"league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.25.1).",
"league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.25.1).",
"psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).",
"psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0 || ^2.0).",
"symfony/filesystem": "Required to enable support for relative symbolic links (^7.4 || ^8.0).",
"symfony/mime": "Required to enable support for guessing extensions (^7.4 || ^8.0)."
},
Expand Down
24 changes: 23 additions & 1 deletion src/Illuminate/Http/Client/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ class Factory
*/
protected $globalOptions = [];

/**
* The persistent transport (connection sharing) mode to apply to every request.
*
* @var \Illuminate\Http\Client\PersistentTransport
*/
protected PersistentTransport $globalPersistentTransport = PersistentTransport::None;

/**
* The stub callables that will handle requests.
*
Expand Down Expand Up @@ -153,6 +160,19 @@ public function globalOptions($options)
return $this;
}

/**
* Set the persistent transport (connection sharing) mode to apply to every request.
*
* @param \Illuminate\Http\Client\PersistentTransport $mode
* @return $this
*/
public function globalPersistentTransport(PersistentTransport $mode)
{
$this->globalPersistentTransport = $mode;

return $this;
}

/**
* Create a new response instance for use during stubbing.
*
Expand Down Expand Up @@ -559,7 +579,9 @@ public function createPendingRequest()
*/
protected function newPendingRequest()
{
return (new PendingRequest($this, $this->globalMiddleware))->withOptions(value($this->globalOptions));
return (new PendingRequest($this, $this->globalMiddleware))
->withOptions(value($this->globalOptions))
->persistentTransport($this->globalPersistentTransport);
}

/**
Expand Down
164 changes: 126 additions & 38 deletions src/Illuminate/Http/Client/PendingRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ResponseException;
use GuzzleHttp\Exception\TransferException;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\Promise\EachPromise;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\TransportSharing;
use GuzzleHttp\UriTemplate\UriTemplate;
use GuzzleHttp\Utils;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Http\Client\Events\ConnectionFailed;
use Illuminate\Http\Client\Events\RequestSending;
Expand All @@ -30,6 +32,8 @@
use JsonSerializable;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use RuntimeException;
use Symfony\Component\VarDumper\VarDumper;
use Throwable;

Expand Down Expand Up @@ -61,6 +65,13 @@
*/
protected $handler;

/**
* The persistent transport (connection sharing) mode for the request.
*
* @var \Illuminate\Http\Client\PersistentTransport
*/
protected PersistentTransport $persistentTransport = PersistentTransport::None;

/**
* The base URL for the request.
*
Expand Down Expand Up @@ -1083,16 +1094,10 @@
}
});
} catch (TransferException $e) {
if ($e instanceof ConnectException) {
$this->marshalConnectionException($e);
}

if ($e instanceof RequestException && ! $e->hasResponse()) {
$this->marshalRequestExceptionWithoutResponse($e);
}

if ($e instanceof RequestException && $e->hasResponse()) {
$this->marshalRequestExceptionWithResponse($e);
if (($response = $this->responseFromException($e)) !== null) {
$this->marshalTransportExceptionWithResponse($e, $response);
} else {
$this->marshalTransportException($e);
}

throw $e;
Expand All @@ -1114,6 +1119,10 @@
*/
protected function expandUrlParameters(string $url)
{
if (! str_contains($url, '{')) {
return $url;
}

return UriTemplate::expand($url, $this->urlParameters);
}

Expand Down Expand Up @@ -1198,7 +1207,11 @@
throw $e;
}

if ($e instanceof ConnectException || ($e instanceof RequestException && ! $e->hasResponse())) {
if (($response = $this->responseFromException($e)) !== null) {
return $this->populateResponse($this->newResponse($response));
}

if ($e instanceof TransferException) {
$exception = new ConnectionException($e->getMessage(), 0, $e);

$this->dispatchConnectionFailedEvent(
Expand All @@ -1209,7 +1222,7 @@
return $exception;
}

return $e instanceof RequestException && $e->hasResponse() ? $this->populateResponse($this->newResponse($e->getResponse())) : $e;
return $e;
})
->then(function (Response|Throwable $response) use ($method, $url, $options, $attempt) {
return $this->handlePromiseResponse($response, $method, $url, $options, $attempt);
Expand All @@ -1232,8 +1245,9 @@
return $response;
}

if ($response instanceof RequestException) {
$response = $this->populateResponse($this->newResponse($response->getResponse()));
if ($response instanceof RequestException
&& ($psrResponse = $this->responseFromException($response)) !== null) {
$response = $this->populateResponse($this->newResponse($psrResponse));
}

try {
Expand Down Expand Up @@ -1584,7 +1598,69 @@
*/
public function buildHandlerStack()
{
return $this->pushHandlers(HandlerStack::create($this->handler));
return $this->pushHandlers(HandlerStack::create($this->buildDefaultHandler()));
}

/**
* Resolve the base Guzzle handler, applying persistent transport sharing when enabled.
*
* @return callable|null
*/
protected function buildDefaultHandler()
{
// Transport sharing can only be applied when Guzzle builds the handler.
if (! is_null($this->handler)) {
return $this->handler;
}

$mode = $this->resolveTransportSharingMode();

return is_null($mode)
? $this->handler
: Utils::chooseHandler(['transport_sharing' => $mode]);
}

/**
* Resolve the Guzzle "transport_sharing" mode for the configured persistence level.
*
* @return string|null
*
* @throws \RuntimeException
*/
protected function resolveTransportSharingMode()
{
if ($this->persistentTransport === PersistentTransport::None) {
return null;
}

// When faking, the stub handler answers before the base handler runs, so a
// sharing transport is never needed (and "Required" must not throw in tests).
if (($this->stubCallbacks?->isNotEmpty() ?? false) || $this->preventStrayRequests) {
return null;
}

$required = $this->persistentTransport === PersistentTransport::Required;

// Guzzle 8: persistent (cross-request) sharing.
if (defined(TransportSharing::class.'::PERSISTENT_PREFER')) {
return $required
? TransportSharing::PERSISTENT_REQUIRE

Check failure on line 1647 in src/Illuminate/Http/Client/PendingRequest.php

View workflow job for this annotation

GitHub Actions / Source Code

Access to undefined constant GuzzleHttp\TransportSharing::PERSISTENT_REQUIRE.
: TransportSharing::PERSISTENT_PREFER;
}

if ($required) {
throw new RuntimeException(
'Persistent HTTP transport sharing is set to "Required", but persistent cURL '
.'share handles require guzzlehttp/guzzle ^8.0.'
);
}

// Guzzle 7.11: handler-lifetime sharing only, best-effort.
if (class_exists(TransportSharing::class)) {
return TransportSharing::HANDLER_PREFER;
}

return null;
}

/**
Expand Down Expand Up @@ -1945,37 +2021,35 @@
}

/**
* Handle the given connection exception.
* Get the PSR-7 response carried by the given exception, if any.
*
* @param \GuzzleHttp\Exception\ConnectException $e
* @return void
*
* @throws \Illuminate\Http\Client\ConnectionException
* @param \Throwable $e
* @return \Psr\Http\Message\ResponseInterface|null
*/
protected function marshalConnectionException(ConnectException $e)
protected function responseFromException(Throwable $e)
{
$exception = new ConnectionException($e->getMessage(), 0, $e);

$request = (new Request($e->getRequest()))->setRequestAttributes($this->attributes);

$this->factory?->recordRequestResponsePair(
$request, null
);
// Guzzle 8 uses ResponseException
if ($e instanceof ResponseException) {
return $e->getResponse();
}

$this->dispatchConnectionFailedEvent($request, $exception);
// Guzzle 7 uses RequestException with hasResponse() true
if ($e instanceof RequestException && is_callable([$e, 'hasResponse']) && $e->hasResponse()) {
return $e->getResponse();
}

throw $exception;
return null;
}

/**
* Handle the given request exception.
* Handle the given transport exception.
*
* @param \GuzzleHttp\Exception\RequestException $e
* @param \GuzzleHttp\Exception\TransferException $e
* @return void
*
* @throws \Illuminate\Http\Client\ConnectionException
*/
protected function marshalRequestExceptionWithoutResponse(RequestException $e)
protected function marshalTransportException(TransferException $e)
{
$exception = new ConnectionException($e->getMessage(), 0, $e);

Expand All @@ -1991,17 +2065,18 @@
}

/**
* Handle the given request exception.
* Handle the given transport exception that carried a response.
*
* @param \GuzzleHttp\Exception\RequestException $e
* @param \GuzzleHttp\Exception\TransferException $e
* @param \Psr\Http\Message\ResponseInterface $response
* @return void
*
* @throws \Illuminate\Http\Client\RequestException
* @throws \Illuminate\Http\Client\ConnectionException
*/
protected function marshalRequestExceptionWithResponse(RequestException $e)
protected function marshalTransportExceptionWithResponse(TransferException $e, ResponseInterface $response)
{
$response = $this->populateResponse($this->newResponse($e->getResponse()));
$response = $this->populateResponse($this->newResponse($response));

$this->factory?->recordRequestResponsePair(
(new Request($e->getRequest()))->setRequestAttributes($this->attributes),
Expand Down Expand Up @@ -2037,6 +2112,19 @@
return $this;
}

/**
* Set the persistent transport (connection sharing) mode for the request.
*
* @param \Illuminate\Http\Client\PersistentTransport $mode
* @return $this
*/
public function persistentTransport(PersistentTransport $mode)
{
$this->persistentTransport = $mode;

return $this;
}

/**
* Get the pending request options.
*
Expand Down
10 changes: 10 additions & 0 deletions src/Illuminate/Http/Client/PersistentTransport.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Illuminate\Http\Client;

enum PersistentTransport: string
{
case None = 'none';
case Preferred = 'preferred';
case Required = 'required';
}
2 changes: 1 addition & 1 deletion src/Illuminate/Http/Client/Promises/FluentPromise.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function otherwise(callable $onRejected): PromiseInterface
}

#[\Override]
public function resolve($value): void
public function resolve($value = null): void
{
$this->guzzlePromise->resolve($value);
}
Expand Down
Loading
Loading