HttpBrowser.php 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  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\BrowserKit;
  11. use Symfony\Component\HttpClient\HttpClient;
  12. use Symfony\Component\Mime\Part\AbstractPart;
  13. use Symfony\Component\Mime\Part\DataPart;
  14. use Symfony\Component\Mime\Part\Multipart\FormDataPart;
  15. use Symfony\Component\Mime\Part\TextPart;
  16. use Symfony\Contracts\HttpClient\HttpClientInterface;
  17. /**
  18. * An implementation of a browser using the HttpClient component
  19. * to make real HTTP requests.
  20. *
  21. * @author Fabien Potencier <fabien@symfony.com>
  22. */
  23. class HttpBrowser extends AbstractBrowser
  24. {
  25. private $client;
  26. public function __construct(HttpClientInterface $client = null, History $history = null, CookieJar $cookieJar = null)
  27. {
  28. if (!$client && !class_exists(HttpClient::class)) {
  29. throw new \LogicException(sprintf('You cannot use "%s" as the HttpClient component is not installed. Try running "composer require symfony/http-client".', __CLASS__));
  30. }
  31. $this->client = $client ?? HttpClient::create();
  32. parent::__construct([], $history, $cookieJar);
  33. }
  34. /**
  35. * @param Request $request
  36. */
  37. protected function doRequest($request): Response
  38. {
  39. $headers = $this->getHeaders($request);
  40. [$body, $extraHeaders] = $this->getBodyAndExtraHeaders($request, $headers);
  41. $response = $this->client->request($request->getMethod(), $request->getUri(), [
  42. 'headers' => array_merge($headers, $extraHeaders),
  43. 'body' => $body,
  44. 'max_redirects' => 0,
  45. ]);
  46. return new Response($response->getContent(false), $response->getStatusCode(), $response->getHeaders(false));
  47. }
  48. /**
  49. * @return array [$body, $headers]
  50. */
  51. private function getBodyAndExtraHeaders(Request $request, array $headers): array
  52. {
  53. if (\in_array($request->getMethod(), ['GET', 'HEAD'])) {
  54. return ['', []];
  55. }
  56. if (!class_exists(AbstractPart::class)) {
  57. throw new \LogicException('You cannot pass non-empty bodies as the Mime component is not installed. Try running "composer require symfony/mime".');
  58. }
  59. if (null !== $content = $request->getContent()) {
  60. if (isset($headers['content-type'])) {
  61. return [$content, []];
  62. }
  63. $part = new TextPart($content, 'utf-8', 'plain', '8bit');
  64. return [$part->bodyToString(), $part->getPreparedHeaders()->toArray()];
  65. }
  66. $fields = $request->getParameters();
  67. if ($uploadedFiles = $this->getUploadedFiles($request->getFiles())) {
  68. $part = new FormDataPart(array_merge($fields, $uploadedFiles));
  69. return [$part->bodyToIterable(), $part->getPreparedHeaders()->toArray()];
  70. }
  71. if (empty($fields)) {
  72. return ['', []];
  73. }
  74. return [http_build_query($fields, '', '&', \PHP_QUERY_RFC1738), ['Content-Type' => 'application/x-www-form-urlencoded']];
  75. }
  76. protected function getHeaders(Request $request): array
  77. {
  78. $headers = [];
  79. foreach ($request->getServer() as $key => $value) {
  80. $key = strtolower(str_replace('_', '-', $key));
  81. $contentHeaders = ['content-length' => true, 'content-md5' => true, 'content-type' => true];
  82. if (0 === strpos($key, 'http-')) {
  83. $headers[substr($key, 5)] = $value;
  84. } elseif (isset($contentHeaders[$key])) {
  85. // CONTENT_* are not prefixed with HTTP_
  86. $headers[$key] = $value;
  87. }
  88. }
  89. $cookies = [];
  90. foreach ($this->getCookieJar()->allRawValues($request->getUri()) as $name => $value) {
  91. $cookies[] = $name.'='.$value;
  92. }
  93. if ($cookies) {
  94. $headers['cookie'] = implode('; ', $cookies);
  95. }
  96. return $headers;
  97. }
  98. /**
  99. * Recursively go through the list. If the file has a tmp_name, convert it to a DataPart.
  100. * Keep the original hierarchy.
  101. */
  102. private function getUploadedFiles(array $files): array
  103. {
  104. $uploadedFiles = [];
  105. foreach ($files as $name => $file) {
  106. if (!\is_array($file)) {
  107. return $uploadedFiles;
  108. }
  109. if (!isset($file['tmp_name'])) {
  110. $uploadedFiles[$name] = $this->getUploadedFiles($file);
  111. }
  112. if (isset($file['tmp_name'])) {
  113. $uploadedFiles[$name] = DataPart::fromPath($file['tmp_name'], $file['name']);
  114. }
  115. }
  116. return $uploadedFiles;
  117. }
  118. }