123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276 |
- <?php
- /*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- namespace Symfony\Component\HttpClient;
- use GuzzleHttp\Promise\Promise as GuzzlePromise;
- use GuzzleHttp\Promise\RejectedPromise;
- use GuzzleHttp\Promise\Utils;
- use Http\Client\Exception\NetworkException;
- use Http\Client\Exception\RequestException;
- use Http\Client\HttpAsyncClient;
- use Http\Client\HttpClient as HttplugInterface;
- use Http\Discovery\Exception\NotFoundException;
- use Http\Discovery\Psr17FactoryDiscovery;
- use Http\Message\RequestFactory;
- use Http\Message\StreamFactory;
- use Http\Message\UriFactory;
- use Http\Promise\Promise;
- use Nyholm\Psr7\Factory\Psr17Factory;
- use Nyholm\Psr7\Request;
- use Nyholm\Psr7\Uri;
- use Psr\Http\Message\RequestFactoryInterface;
- use Psr\Http\Message\RequestInterface;
- use Psr\Http\Message\ResponseFactoryInterface;
- use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface;
- use Psr\Http\Message\StreamFactoryInterface;
- use Psr\Http\Message\StreamInterface;
- use Psr\Http\Message\UriFactoryInterface;
- use Psr\Http\Message\UriInterface;
- use Symfony\Component\HttpClient\Internal\HttplugWaitLoop;
- use Symfony\Component\HttpClient\Response\HttplugPromise;
- use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
- use Symfony\Contracts\HttpClient\HttpClientInterface;
- use Symfony\Contracts\HttpClient\ResponseInterface;
- use Symfony\Contracts\Service\ResetInterface;
- if (!interface_exists(HttplugInterface::class)) {
- 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".');
- }
- if (!interface_exists(RequestFactory::class)) {
- 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".');
- }
- /**
- * An adapter to turn a Symfony HttpClientInterface into an Httplug client.
- *
- * Run "composer require nyholm/psr7" to install an efficient implementation of response
- * and stream factories with flex-provided autowiring aliases.
- *
- * @author Nicolas Grekas <p@tchwork.com>
- */
- final class HttplugClient implements HttplugInterface, HttpAsyncClient, RequestFactory, StreamFactory, UriFactory, ResetInterface
- {
- private $client;
- private $responseFactory;
- private $streamFactory;
- /**
- * @var \SplObjectStorage<ResponseInterface, array{RequestInterface, Promise}>|null
- */
- private $promisePool;
- private $waitLoop;
- public function __construct(HttpClientInterface $client = null, ResponseFactoryInterface $responseFactory = null, StreamFactoryInterface $streamFactory = null)
- {
- $this->client = $client ?? HttpClient::create();
- $this->responseFactory = $responseFactory;
- $this->streamFactory = $streamFactory ?? ($responseFactory instanceof StreamFactoryInterface ? $responseFactory : null);
- $this->promisePool = class_exists(Utils::class) ? new \SplObjectStorage() : null;
- if (null === $this->responseFactory || null === $this->streamFactory) {
- if (!class_exists(Psr17Factory::class) && !class_exists(Psr17FactoryDiscovery::class)) {
- 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".');
- }
- try {
- $psr17Factory = class_exists(Psr17Factory::class, false) ? new Psr17Factory() : null;
- $this->responseFactory = $this->responseFactory ?? $psr17Factory ?? Psr17FactoryDiscovery::findResponseFactory();
- $this->streamFactory = $this->streamFactory ?? $psr17Factory ?? Psr17FactoryDiscovery::findStreamFactory();
- } catch (NotFoundException $e) {
- 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);
- }
- }
- $this->waitLoop = new HttplugWaitLoop($this->client, $this->promisePool, $this->responseFactory, $this->streamFactory);
- }
- /**
- * {@inheritdoc}
- */
- public function sendRequest(RequestInterface $request): Psr7ResponseInterface
- {
- try {
- return $this->waitLoop->createPsr7Response($this->sendPsr7Request($request));
- } catch (TransportExceptionInterface $e) {
- throw new NetworkException($e->getMessage(), $request, $e);
- }
- }
- /**
- * {@inheritdoc}
- *
- * @return HttplugPromise
- */
- public function sendAsyncRequest(RequestInterface $request): Promise
- {
- if (!$promisePool = $this->promisePool) {
- throw new \LogicException(sprintf('You cannot use "%s()" as the "guzzlehttp/promises" package is not installed. Try running "composer require guzzlehttp/promises".', __METHOD__));
- }
- try {
- $response = $this->sendPsr7Request($request, true);
- } catch (NetworkException $e) {
- return new HttplugPromise(new RejectedPromise($e));
- }
- $waitLoop = $this->waitLoop;
- $promise = new GuzzlePromise(static function () use ($response, $waitLoop) {
- $waitLoop->wait($response);
- }, static function () use ($response, $promisePool) {
- $response->cancel();
- unset($promisePool[$response]);
- });
- $promisePool[$response] = [$request, $promise];
- return new HttplugPromise($promise);
- }
- /**
- * Resolves pending promises that complete before the timeouts are reached.
- *
- * When $maxDuration is null and $idleTimeout is reached, promises are rejected.
- *
- * @return int The number of remaining pending promises
- */
- public function wait(float $maxDuration = null, float $idleTimeout = null): int
- {
- return $this->waitLoop->wait(null, $maxDuration, $idleTimeout);
- }
- /**
- * {@inheritdoc}
- */
- public function createRequest($method, $uri, array $headers = [], $body = null, $protocolVersion = '1.1'): RequestInterface
- {
- if ($this->responseFactory instanceof RequestFactoryInterface) {
- $request = $this->responseFactory->createRequest($method, $uri);
- } elseif (class_exists(Request::class)) {
- $request = new Request($method, $uri);
- } elseif (class_exists(Psr17FactoryDiscovery::class)) {
- $request = Psr17FactoryDiscovery::findRequestFactory()->createRequest($method, $uri);
- } else {
- throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__));
- }
- $request = $request
- ->withProtocolVersion($protocolVersion)
- ->withBody($this->createStream($body))
- ;
- foreach ($headers as $name => $value) {
- $request = $request->withAddedHeader($name, $value);
- }
- return $request;
- }
- /**
- * {@inheritdoc}
- */
- public function createStream($body = null): StreamInterface
- {
- if ($body instanceof StreamInterface) {
- return $body;
- }
- if (\is_string($body ?? '')) {
- $stream = $this->streamFactory->createStream($body ?? '');
- } elseif (\is_resource($body)) {
- $stream = $this->streamFactory->createStreamFromResource($body);
- } else {
- throw new \InvalidArgumentException(sprintf('"%s()" expects string, resource or StreamInterface, "%s" given.', __METHOD__, get_debug_type($body)));
- }
- if ($stream->isSeekable()) {
- $stream->seek(0);
- }
- return $stream;
- }
- /**
- * {@inheritdoc}
- */
- public function createUri($uri): UriInterface
- {
- if ($uri instanceof UriInterface) {
- return $uri;
- }
- if ($this->responseFactory instanceof UriFactoryInterface) {
- return $this->responseFactory->createUri($uri);
- }
- if (class_exists(Uri::class)) {
- return new Uri($uri);
- }
- if (class_exists(Psr17FactoryDiscovery::class)) {
- return Psr17FactoryDiscovery::findUrlFactory()->createUri($uri);
- }
- throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__));
- }
- public function __sleep(): array
- {
- throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
- }
- public function __wakeup()
- {
- throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
- }
- public function __destruct()
- {
- $this->wait();
- }
- public function reset()
- {
- if ($this->client instanceof ResetInterface) {
- $this->client->reset();
- }
- }
- private function sendPsr7Request(RequestInterface $request, bool $buffer = null): ResponseInterface
- {
- try {
- $body = $request->getBody();
- if ($body->isSeekable()) {
- $body->seek(0);
- }
- $options = [
- 'headers' => $request->getHeaders(),
- 'body' => $body->getContents(),
- 'buffer' => $buffer,
- ];
- if ('1.0' === $request->getProtocolVersion()) {
- $options['http_version'] = '1.0';
- }
- return $this->client->request($request->getMethod(), (string) $request->getUri(), $options);
- } catch (\InvalidArgumentException $e) {
- throw new RequestException($e->getMessage(), $request, $e);
- } catch (TransportExceptionInterface $e) {
- throw new NetworkException($e->getMessage(), $request, $e);
- }
- }
- }
|