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

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