Psr18Client.php 9.0 KB

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