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

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