vendor/symfony/http-client/HttplugClient.php line 134

  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\HttpClient;
  11. use GuzzleHttp\Promise\Promise as GuzzlePromise;
  12. use GuzzleHttp\Promise\RejectedPromise;
  13. use GuzzleHttp\Promise\Utils;
  14. use Http\Client\Exception\NetworkException;
  15. use Http\Client\Exception\RequestException;
  16. use Http\Client\HttpAsyncClient;
  17. use Http\Client\HttpClient as HttplugInterface;
  18. use Http\Discovery\Exception\NotFoundException;
  19. use Http\Discovery\Psr17FactoryDiscovery;
  20. use Http\Message\RequestFactory;
  21. use Http\Message\StreamFactory;
  22. use Http\Message\UriFactory;
  23. use Nyholm\Psr7\Factory\Psr17Factory;
  24. use Nyholm\Psr7\Request;
  25. use Nyholm\Psr7\Uri;
  26. use Psr\Http\Message\RequestFactoryInterface;
  27. use Psr\Http\Message\RequestInterface;
  28. use Psr\Http\Message\ResponseFactoryInterface;
  29. use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface;
  30. use Psr\Http\Message\StreamFactoryInterface;
  31. use Psr\Http\Message\StreamInterface;
  32. use Psr\Http\Message\UriFactoryInterface;
  33. use Psr\Http\Message\UriInterface;
  34. use Symfony\Component\HttpClient\Internal\HttplugWaitLoop;
  35. use Symfony\Component\HttpClient\Response\HttplugPromise;
  36. use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
  37. use Symfony\Contracts\HttpClient\HttpClientInterface;
  38. use Symfony\Contracts\HttpClient\ResponseInterface;
  39. use Symfony\Contracts\Service\ResetInterface;
  40. if (!interface_exists(HttplugInterface::class)) {
  41.     throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "php-http/httplug" package is not installed. Try running "composer require php-http/httplug".');
  42. }
  43. if (!interface_exists(RequestFactory::class)) {
  44.     throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "php-http/message-factory" package is not installed. Try running "composer require php-http/message-factory".');
  45. }
  46. if (!interface_exists(RequestFactoryInterface::class)) {
  47.     throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as the "psr/http-factory" package is not installed. Try running "composer require nyholm/psr7".');
  48. }
  49. /**
  50.  * An adapter to turn a Symfony HttpClientInterface into an Httplug client.
  51.  *
  52.  * Run "composer require nyholm/psr7" to install an efficient implementation of response
  53.  * and stream factories with flex-provided autowiring aliases.
  54.  *
  55.  * @author Nicolas Grekas <p@tchwork.com>
  56.  */
  57. final class HttplugClient implements HttplugInterfaceHttpAsyncClientRequestFactoryInterfaceStreamFactoryInterfaceUriFactoryInterfaceRequestFactoryStreamFactoryUriFactoryResetInterface
  58. {
  59.     private HttpClientInterface $client;
  60.     private ResponseFactoryInterface $responseFactory;
  61.     private StreamFactoryInterface $streamFactory;
  62.     /**
  63.      * @var \SplObjectStorage<ResponseInterface, array{RequestInterface, Promise}>|null
  64.      */
  65.     private ?\SplObjectStorage $promisePool;
  66.     private HttplugWaitLoop $waitLoop;
  67.     public function __construct(HttpClientInterface $client nullResponseFactoryInterface $responseFactory nullStreamFactoryInterface $streamFactory null)
  68.     {
  69.         $this->client $client ?? HttpClient::create();
  70.         $streamFactory ??= $responseFactory instanceof StreamFactoryInterface $responseFactory null;
  71.         $this->promisePool class_exists(Utils::class) ? new \SplObjectStorage() : null;
  72.         if (null === $responseFactory || null === $streamFactory) {
  73.             if (!class_exists(Psr17Factory::class) && !class_exists(Psr17FactoryDiscovery::class)) {
  74.                 throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as no PSR-17 factories have been provided. Try running "composer require nyholm/psr7".');
  75.             }
  76.             try {
  77.                 $psr17Factory class_exists(Psr17Factory::class, false) ? new Psr17Factory() : null;
  78.                 $responseFactory ??= $psr17Factory ?? Psr17FactoryDiscovery::findResponseFactory();
  79.                 $streamFactory ??= $psr17Factory ?? Psr17FactoryDiscovery::findStreamFactory();
  80.             } catch (NotFoundException $e) {
  81.                 throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as no PSR-17 factories have been found. Try running "composer require nyholm/psr7".'0$e);
  82.             }
  83.         }
  84.         $this->responseFactory $responseFactory;
  85.         $this->streamFactory $streamFactory;
  86.         $this->waitLoop = new HttplugWaitLoop($this->client$this->promisePool$this->responseFactory$this->streamFactory);
  87.     }
  88.     public function withOptions(array $options): static
  89.     {
  90.         $clone = clone $this;
  91.         $clone->client $clone->client->withOptions($options);
  92.         return $clone;
  93.     }
  94.     public function sendRequest(RequestInterface $request): Psr7ResponseInterface
  95.     {
  96.         try {
  97.             return $this->waitLoop->createPsr7Response($this->sendPsr7Request($request));
  98.         } catch (TransportExceptionInterface $e) {
  99.             throw new NetworkException($e->getMessage(), $request$e);
  100.         }
  101.     }
  102.     public function sendAsyncRequest(RequestInterface $request): HttplugPromise
  103.     {
  104.         if (!$promisePool $this->promisePool) {
  105.             throw new \LogicException(sprintf('You cannot use "%s()" as the "guzzlehttp/promises" package is not installed. Try running "composer require guzzlehttp/promises".'__METHOD__));
  106.         }
  107.         try {
  108.             $response $this->sendPsr7Request($requesttrue);
  109.         } catch (NetworkException $e) {
  110.             return new HttplugPromise(new RejectedPromise($e));
  111.         }
  112.         $waitLoop $this->waitLoop;
  113.         $promise = new GuzzlePromise(static function () use ($response$waitLoop) {
  114.             $waitLoop->wait($response);
  115.         }, static function () use ($response$promisePool) {
  116.             $response->cancel();
  117.             unset($promisePool[$response]);
  118.         });
  119.         $promisePool[$response] = [$request$promise];
  120.         return new HttplugPromise($promise);
  121.     }
  122.     /**
  123.      * Resolves pending promises that complete before the timeouts are reached.
  124.      *
  125.      * When $maxDuration is null and $idleTimeout is reached, promises are rejected.
  126.      *
  127.      * @return int The number of remaining pending promises
  128.      */
  129.     public function wait(float $maxDuration nullfloat $idleTimeout null): int
  130.     {
  131.         return $this->waitLoop->wait(null$maxDuration$idleTimeout);
  132.     }
  133.     /**
  134.      * @param string              $method
  135.      * @param UriInterface|string $uri
  136.      */
  137.     public function createRequest($method$uri, array $headers = [], $body null$protocolVersion '1.1'): RequestInterface
  138.     {
  139.         if (\func_num_args()) {
  140.             trigger_deprecation('symfony/http-client''6.2''Passing more than 2 arguments to "%s()" is deprecated.'__METHOD__);
  141.         }
  142.         if ($this->responseFactory instanceof RequestFactoryInterface) {
  143.             $request $this->responseFactory->createRequest($method$uri);
  144.         } elseif (class_exists(Request::class)) {
  145.             $request = new Request($method$uri);
  146.         } elseif (class_exists(Psr17FactoryDiscovery::class)) {
  147.             $request Psr17FactoryDiscovery::findRequestFactory()->createRequest($method$uri);
  148.         } else {
  149.             throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".'__METHOD__));
  150.         }
  151.         $request $request
  152.             ->withProtocolVersion($protocolVersion)
  153.             ->withBody($this->createStream($body ?? ''))
  154.         ;
  155.         foreach ($headers as $name => $value) {
  156.             $request $request->withAddedHeader($name$value);
  157.         }
  158.         return $request;
  159.     }
  160.     /**
  161.      * @param string $content
  162.      */
  163.     public function createStream($content ''): StreamInterface
  164.     {
  165.         if (!\is_string($content)) {
  166.             trigger_deprecation('symfony/http-client''6.2''Passing a "%s" to "%s()" is deprecated, use "createStreamFrom*()" instead.'get_debug_type($content), __METHOD__);
  167.         }
  168.         if ($content instanceof StreamInterface) {
  169.             return $content;
  170.         }
  171.         if (\is_string($content ?? '')) {
  172.             $stream $this->streamFactory->createStream($content ?? '');
  173.         } elseif (\is_resource($content)) {
  174.             $stream $this->streamFactory->createStreamFromResource($content);
  175.         } else {
  176.             throw new \InvalidArgumentException(sprintf('"%s()" expects string, resource or StreamInterface, "%s" given.'__METHOD__get_debug_type($content)));
  177.         }
  178.         if ($stream->isSeekable()) {
  179.             $stream->seek(0);
  180.         }
  181.         return $stream;
  182.     }
  183.     public function createStreamFromFile(string $filenamestring $mode 'r'): StreamInterface
  184.     {
  185.         return $this->streamFactory->createStreamFromFile($filename$mode);
  186.     }
  187.     public function createStreamFromResource($resource): StreamInterface
  188.     {
  189.         return $this->streamFactory->createStreamFromResource($resource);
  190.     }
  191.     /**
  192.      * @param string $uri
  193.      */
  194.     public function createUri($uri ''): UriInterface
  195.     {
  196.         if (!\is_string($uri)) {
  197.             trigger_deprecation('symfony/http-client''6.2''Passing a "%s" to "%s()" is deprecated, pass a string instead.'get_debug_type($uri), __METHOD__);
  198.         }
  199.         if ($uri instanceof UriInterface) {
  200.             return $uri;
  201.         }
  202.         if ($this->responseFactory instanceof UriFactoryInterface) {
  203.             return $this->responseFactory->createUri($uri);
  204.         }
  205.         if (class_exists(Uri::class)) {
  206.             return new Uri($uri);
  207.         }
  208.         if (class_exists(Psr17FactoryDiscovery::class)) {
  209.             return Psr17FactoryDiscovery::findUrlFactory()->createUri($uri);
  210.         }
  211.         throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".'__METHOD__));
  212.     }
  213.     public function __sleep(): array
  214.     {
  215.         throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
  216.     }
  217.     public function __wakeup()
  218.     {
  219.         throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
  220.     }
  221.     public function __destruct()
  222.     {
  223.         $this->wait();
  224.     }
  225.     public function reset()
  226.     {
  227.         if ($this->client instanceof ResetInterface) {
  228.             $this->client->reset();
  229.         }
  230.     }
  231.     private function sendPsr7Request(RequestInterface $requestbool $buffer null): ResponseInterface
  232.     {
  233.         try {
  234.             $body $request->getBody();
  235.             if ($body->isSeekable()) {
  236.                 $body->seek(0);
  237.             }
  238.             $options = [
  239.                 'headers' => $request->getHeaders(),
  240.                 'body' => $body->getContents(),
  241.                 'buffer' => $buffer,
  242.             ];
  243.             if ('1.0' === $request->getProtocolVersion()) {
  244.                 $options['http_version'] = '1.0';
  245.             }
  246.             return $this->client->request($request->getMethod(), (string) $request->getUri(), $options);
  247.         } catch (\InvalidArgumentException $e) {
  248.             throw new RequestException($e->getMessage(), $request$e);
  249.         } catch (TransportExceptionInterface $e) {
  250.             throw new NetworkException($e->getMessage(), $request$e);
  251.         }
  252.     }
  253. }