Psr18Client.php 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  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 Http\Discovery\Exception\NotFoundException;
  12. use Http\Discovery\Psr17FactoryDiscovery;
  13. use Nyholm\Psr7\Factory\Psr17Factory;
  14. use Nyholm\Psr7\Request;
  15. use Nyholm\Psr7\Uri;
  16. use Psr\Http\Client\ClientInterface;
  17. use Psr\Http\Client\NetworkExceptionInterface;
  18. use Psr\Http\Client\RequestExceptionInterface;
  19. use Psr\Http\Message\RequestFactoryInterface;
  20. use Psr\Http\Message\RequestInterface;
  21. use Psr\Http\Message\ResponseFactoryInterface;
  22. use Psr\Http\Message\ResponseInterface;
  23. use Psr\Http\Message\StreamFactoryInterface;
  24. use Psr\Http\Message\StreamInterface;
  25. use Psr\Http\Message\UriFactoryInterface;
  26. use Psr\Http\Message\UriInterface;
  27. use Symfony\Component\HttpClient\Response\StreamableInterface;
  28. use Symfony\Component\HttpClient\Response\StreamWrapper;
  29. use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
  30. use Symfony\Contracts\HttpClient\HttpClientInterface;
  31. use Symfony\Contracts\Service\ResetInterface;
  32. if (!interface_exists(RequestFactoryInterface::class)) {
  33. throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\Psr18Client" as the "psr/http-factory" package is not installed. Try running "composer require nyholm/psr7".');
  34. }
  35. if (!interface_exists(ClientInterface::class)) {
  36. throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\Psr18Client" as the "psr/http-client" package is not installed. Try running "composer require psr/http-client".');
  37. }
  38. /**
  39. * An adapter to turn a Symfony HttpClientInterface into a PSR-18 ClientInterface.
  40. *
  41. * Run "composer require psr/http-client" to install the base ClientInterface. Run
  42. * "composer require nyholm/psr7" to install an efficient implementation of response
  43. * and stream factories with flex-provided autowiring aliases.
  44. *
  45. * @author Nicolas Grekas <p@tchwork.com>
  46. */
  47. final class Psr18Client implements ClientInterface, RequestFactoryInterface, StreamFactoryInterface, UriFactoryInterface, ResetInterface
  48. {
  49. private $client;
  50. private $responseFactory;
  51. private $streamFactory;
  52. public function __construct(HttpClientInterface $client = null, ResponseFactoryInterface $responseFactory = null, StreamFactoryInterface $streamFactory = null)
  53. {
  54. $this->client = $client ?? HttpClient::create();
  55. $this->responseFactory = $responseFactory;
  56. $this->streamFactory = $streamFactory ?? ($responseFactory instanceof StreamFactoryInterface ? $responseFactory : null);
  57. if (null !== $this->responseFactory && null !== $this->streamFactory) {
  58. return;
  59. }
  60. if (!class_exists(Psr17Factory::class) && !class_exists(Psr17FactoryDiscovery::class)) {
  61. throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\Psr18Client" as no PSR-17 factories have been provided. Try running "composer require nyholm/psr7".');
  62. }
  63. try {
  64. $psr17Factory = class_exists(Psr17Factory::class, false) ? new Psr17Factory() : null;
  65. $this->responseFactory = $this->responseFactory ?? $psr17Factory ?? Psr17FactoryDiscovery::findResponseFactory();
  66. $this->streamFactory = $this->streamFactory ?? $psr17Factory ?? Psr17FactoryDiscovery::findStreamFactory();
  67. } catch (NotFoundException $e) {
  68. 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);
  69. }
  70. }
  71. /**
  72. * {@inheritdoc}
  73. */
  74. public function sendRequest(RequestInterface $request): ResponseInterface
  75. {
  76. try {
  77. $body = $request->getBody();
  78. if ($body->isSeekable()) {
  79. $body->seek(0);
  80. }
  81. $options = [
  82. 'headers' => $request->getHeaders(),
  83. 'body' => $body->getContents(),
  84. ];
  85. if ('1.0' === $request->getProtocolVersion()) {
  86. $options['http_version'] = '1.0';
  87. }
  88. $response = $this->client->request($request->getMethod(), (string) $request->getUri(), $options);
  89. $psrResponse = $this->responseFactory->createResponse($response->getStatusCode());
  90. foreach ($response->getHeaders(false) as $name => $values) {
  91. foreach ($values as $value) {
  92. try {
  93. $psrResponse = $psrResponse->withAddedHeader($name, $value);
  94. } catch (\InvalidArgumentException $e) {
  95. // ignore invalid header
  96. }
  97. }
  98. }
  99. $body = $response instanceof StreamableInterface ? $response->toStream(false) : StreamWrapper::createResource($response, $this->client);
  100. $body = $this->streamFactory->createStreamFromResource($body);
  101. if ($body->isSeekable()) {
  102. $body->seek(0);
  103. }
  104. return $psrResponse->withBody($body);
  105. } catch (TransportExceptionInterface $e) {
  106. if ($e instanceof \InvalidArgumentException) {
  107. throw new Psr18RequestException($e, $request);
  108. }
  109. throw new Psr18NetworkException($e, $request);
  110. }
  111. }
  112. /**
  113. * {@inheritdoc}
  114. */
  115. public function createRequest(string $method, $uri): RequestInterface
  116. {
  117. if ($this->responseFactory instanceof RequestFactoryInterface) {
  118. return $this->responseFactory->createRequest($method, $uri);
  119. }
  120. if (class_exists(Request::class)) {
  121. return new Request($method, $uri);
  122. }
  123. if (class_exists(Psr17FactoryDiscovery::class)) {
  124. return Psr17FactoryDiscovery::findRequestFactory()->createRequest($method, $uri);
  125. }
  126. throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__));
  127. }
  128. /**
  129. * {@inheritdoc}
  130. */
  131. public function createStream(string $content = ''): StreamInterface
  132. {
  133. $stream = $this->streamFactory->createStream($content);
  134. if ($stream->isSeekable()) {
  135. $stream->seek(0);
  136. }
  137. return $stream;
  138. }
  139. /**
  140. * {@inheritdoc}
  141. */
  142. public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface
  143. {
  144. return $this->streamFactory->createStreamFromFile($filename, $mode);
  145. }
  146. /**
  147. * {@inheritdoc}
  148. */
  149. public function createStreamFromResource($resource): StreamInterface
  150. {
  151. return $this->streamFactory->createStreamFromResource($resource);
  152. }
  153. /**
  154. * {@inheritdoc}
  155. */
  156. public function createUri(string $uri = ''): UriInterface
  157. {
  158. if ($this->responseFactory instanceof UriFactoryInterface) {
  159. return $this->responseFactory->createUri($uri);
  160. }
  161. if (class_exists(Uri::class)) {
  162. return new Uri($uri);
  163. }
  164. if (class_exists(Psr17FactoryDiscovery::class)) {
  165. return Psr17FactoryDiscovery::findUrlFactory()->createUri($uri);
  166. }
  167. throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__));
  168. }
  169. public function reset()
  170. {
  171. if ($this->client instanceof ResetInterface) {
  172. $this->client->reset();
  173. }
  174. }
  175. }
  176. /**
  177. * @internal
  178. */
  179. class Psr18NetworkException extends \RuntimeException implements NetworkExceptionInterface
  180. {
  181. private $request;
  182. public function __construct(TransportExceptionInterface $e, RequestInterface $request)
  183. {
  184. parent::__construct($e->getMessage(), 0, $e);
  185. $this->request = $request;
  186. }
  187. public function getRequest(): RequestInterface
  188. {
  189. return $this->request;
  190. }
  191. }
  192. /**
  193. * @internal
  194. */
  195. class Psr18RequestException extends \InvalidArgumentException implements RequestExceptionInterface
  196. {
  197. private $request;
  198. public function __construct(TransportExceptionInterface $e, RequestInterface $request)
  199. {
  200. parent::__construct($e->getMessage(), 0, $e);
  201. $this->request = $request;
  202. }
  203. public function getRequest(): RequestInterface
  204. {
  205. return $this->request;
  206. }
  207. }