vendor/symfony/http-client/Response/TransportResponseTrait.php line 167

  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\Response;
  11. use Psr\Log\LoggerInterface;
  12. use Symfony\Component\HttpClient\Chunk\DataChunk;
  13. use Symfony\Component\HttpClient\Chunk\ErrorChunk;
  14. use Symfony\Component\HttpClient\Chunk\FirstChunk;
  15. use Symfony\Component\HttpClient\Chunk\LastChunk;
  16. use Symfony\Component\HttpClient\Exception\TransportException;
  17. use Symfony\Component\HttpClient\Internal\Canary;
  18. use Symfony\Component\HttpClient\Internal\ClientState;
  19. /**
  20.  * Implements common logic for transport-level response classes.
  21.  *
  22.  * @author Nicolas Grekas <p@tchwork.com>
  23.  *
  24.  * @internal
  25.  */
  26. trait TransportResponseTrait
  27. {
  28.     private Canary $canary;
  29.     private array $headers = [];
  30.     private array $info = [
  31.         'response_headers' => [],
  32.         'http_code' => 0,
  33.         'error' => null,
  34.         'canceled' => false,
  35.     ];
  36.     /** @var object|resource */
  37.     private $handle;
  38.     private int|string $id;
  39.     private ?float $timeout 0;
  40.     private \InflateContext|bool|null $inflate null;
  41.     private ?array $finalInfo null;
  42.     private ?LoggerInterface $logger null;
  43.     public function getStatusCode(): int
  44.     {
  45.         if ($this->initializer) {
  46.             self::initialize($this);
  47.         }
  48.         return $this->info['http_code'];
  49.     }
  50.     public function getHeaders(bool $throw true): array
  51.     {
  52.         if ($this->initializer) {
  53.             self::initialize($this);
  54.         }
  55.         if ($throw) {
  56.             $this->checkStatusCode();
  57.         }
  58.         return $this->headers;
  59.     }
  60.     public function cancel(): void
  61.     {
  62.         $this->info['canceled'] = true;
  63.         $this->info['error'] = 'Response has been canceled.';
  64.         $this->close();
  65.     }
  66.     /**
  67.      * Closes the response and all its network handles.
  68.      */
  69.     protected function close(): void
  70.     {
  71.         $this->canary->cancel();
  72.         $this->inflate null;
  73.     }
  74.     /**
  75.      * Adds pending responses to the activity list.
  76.      */
  77.     abstract protected static function schedule(self $response, array &$runningResponses): void;
  78.     /**
  79.      * Performs all pending non-blocking operations.
  80.      */
  81.     abstract protected static function perform(ClientState $multi, array &$responses): void;
  82.     /**
  83.      * Waits for network activity.
  84.      */
  85.     abstract protected static function select(ClientState $multifloat $timeout): int;
  86.     private static function addResponseHeaders(array $responseHeaders, array &$info, array &$headersstring &$debug ''): void
  87.     {
  88.         foreach ($responseHeaders as $h) {
  89.             if (11 <= \strlen($h) && '/' === $h[4] && preg_match('#^HTTP/\d+(?:\.\d+)? (\d\d\d)(?: |$)#'$h$m)) {
  90.                 if ($headers) {
  91.                     $debug .= "< \r\n";
  92.                     $headers = [];
  93.                 }
  94.                 $info['http_code'] = (int) $m[1];
  95.             } elseif (=== \count($m explode(':'$h2))) {
  96.                 $headers[strtolower($m[0])][] = ltrim($m[1]);
  97.             }
  98.             $debug .= "< {$h}\r\n";
  99.             $info['response_headers'][] = $h;
  100.         }
  101.         $debug .= "< \r\n";
  102.     }
  103.     /**
  104.      * Ensures the request is always sent and that the response code was checked.
  105.      */
  106.     private function doDestruct(): void
  107.     {
  108.         $this->shouldBuffer true;
  109.         if ($this->initializer && null === $this->info['error']) {
  110.             self::initialize($this);
  111.             $this->checkStatusCode();
  112.         }
  113.     }
  114.     /**
  115.      * Implements an event loop based on a buffer activity queue.
  116.      *
  117.      * @param iterable<array-key, self> $responses
  118.      *
  119.      * @internal
  120.      */
  121.     public static function stream(iterable $responsesfloat $timeout null): \Generator
  122.     {
  123.         $runningResponses = [];
  124.         foreach ($responses as $response) {
  125.             self::schedule($response$runningResponses);
  126.         }
  127.         $lastActivity microtime(true);
  128.         $elapsedTimeout 0;
  129.         if ($fromLastTimeout 0.0 === $timeout && '-0' === (string) $timeout) {
  130.             $timeout null;
  131.         } elseif ($fromLastTimeout $timeout) {
  132.             $timeout = -$timeout;
  133.         }
  134.         while (true) {
  135.             $hasActivity false;
  136.             $timeoutMax 0;
  137.             $timeoutMin $timeout ?? \INF;
  138.             /** @var ClientState $multi */
  139.             foreach ($runningResponses as $i => [$multi]) {
  140.                 $responses = &$runningResponses[$i][1];
  141.                 self::perform($multi$responses);
  142.                 foreach ($responses as $j => $response) {
  143.                     $timeoutMax $timeout ?? max($timeoutMax$response->timeout);
  144.                     $timeoutMin min($timeoutMin$response->timeout1);
  145.                     $chunk false;
  146.                     if ($fromLastTimeout && null !== $multi->lastTimeout) {
  147.                         $elapsedTimeout microtime(true) - $multi->lastTimeout;
  148.                     }
  149.                     if (isset($multi->handlesActivity[$j])) {
  150.                         $multi->lastTimeout null;
  151.                     } elseif (!isset($multi->openHandles[$j])) {
  152.                         unset($responses[$j]);
  153.                         continue;
  154.                     } elseif ($elapsedTimeout >= $timeoutMax) {
  155.                         $multi->handlesActivity[$j] = [new ErrorChunk($response->offsetsprintf('Idle timeout reached for "%s".'$response->getInfo('url')))];
  156.                         $multi->lastTimeout ??= $lastActivity;
  157.                     } else {
  158.                         continue;
  159.                     }
  160.                     while ($multi->handlesActivity[$j] ?? false) {
  161.                         $hasActivity true;
  162.                         $elapsedTimeout 0;
  163.                         if (\is_string($chunk array_shift($multi->handlesActivity[$j]))) {
  164.                             if (null !== $response->inflate && false === $chunk = @inflate_add($response->inflate$chunk)) {
  165.                                 $multi->handlesActivity[$j] = [null, new TransportException(sprintf('Error while processing content unencoding for "%s".'$response->getInfo('url')))];
  166.                                 continue;
  167.                             }
  168.                             if ('' !== $chunk && null !== $response->content && \strlen($chunk) !== fwrite($response->content$chunk)) {
  169.                                 $multi->handlesActivity[$j] = [null, new TransportException(sprintf('Failed writing %d bytes to the response buffer.'\strlen($chunk)))];
  170.                                 continue;
  171.                             }
  172.                             $chunkLen \strlen($chunk);
  173.                             $chunk = new DataChunk($response->offset$chunk);
  174.                             $response->offset += $chunkLen;
  175.                         } elseif (null === $chunk) {
  176.                             $e $multi->handlesActivity[$j][0];
  177.                             unset($responses[$j], $multi->handlesActivity[$j]);
  178.                             $response->close();
  179.                             if (null !== $e) {
  180.                                 $response->info['error'] = $e->getMessage();
  181.                                 if ($e instanceof \Error) {
  182.                                     throw $e;
  183.                                 }
  184.                                 $chunk = new ErrorChunk($response->offset$e);
  185.                             } else {
  186.                                 if (=== $response->offset && null === $response->content) {
  187.                                     $response->content fopen('php://memory''w+');
  188.                                 }
  189.                                 $chunk = new LastChunk($response->offset);
  190.                             }
  191.                         } elseif ($chunk instanceof ErrorChunk) {
  192.                             unset($responses[$j]);
  193.                             $elapsedTimeout $timeoutMax;
  194.                         } elseif ($chunk instanceof FirstChunk) {
  195.                             if ($response->logger) {
  196.                                 $info $response->getInfo();
  197.                                 $response->logger->info(sprintf('Response: "%s %s"'$info['http_code'], $info['url']));
  198.                             }
  199.                             $response->inflate \extension_loaded('zlib') && $response->inflate && 'gzip' === ($response->headers['content-encoding'][0] ?? null) ? inflate_init(\ZLIB_ENCODING_GZIP) : null;
  200.                             if ($response->shouldBuffer instanceof \Closure) {
  201.                                 try {
  202.                                     $response->shouldBuffer = ($response->shouldBuffer)($response->headers);
  203.                                     if (null !== $response->info['error']) {
  204.                                         throw new TransportException($response->info['error']);
  205.                                     }
  206.                                 } catch (\Throwable $e) {
  207.                                     $response->close();
  208.                                     $multi->handlesActivity[$j] = [null$e];
  209.                                 }
  210.                             }
  211.                             if (true === $response->shouldBuffer) {
  212.                                 $response->content fopen('php://temp''w+');
  213.                             } elseif (\is_resource($response->shouldBuffer)) {
  214.                                 $response->content $response->shouldBuffer;
  215.                             }
  216.                             $response->shouldBuffer null;
  217.                             yield $response => $chunk;
  218.                             if ($response->initializer && null === $response->info['error']) {
  219.                                 // Ensure the HTTP status code is always checked
  220.                                 $response->getHeaders(true);
  221.                             }
  222.                             continue;
  223.                         }
  224.                         yield $response => $chunk;
  225.                     }
  226.                     unset($multi->handlesActivity[$j]);
  227.                     if ($chunk instanceof ErrorChunk && !$chunk->didThrow()) {
  228.                         // Ensure transport exceptions are always thrown
  229.                         $chunk->getContent();
  230.                     }
  231.                 }
  232.                 if (!$responses) {
  233.                     unset($runningResponses[$i]);
  234.                 }
  235.                 // Prevent memory leaks
  236.                 $multi->handlesActivity $multi->handlesActivity ?: [];
  237.                 $multi->openHandles $multi->openHandles ?: [];
  238.             }
  239.             if (!$runningResponses) {
  240.                 break;
  241.             }
  242.             if ($hasActivity) {
  243.                 $lastActivity microtime(true);
  244.                 continue;
  245.             }
  246.             if (-=== self::select($multimin($timeoutMin$timeoutMax $elapsedTimeout))) {
  247.                 usleep(min(5001E6 $timeoutMin));
  248.             }
  249.             $elapsedTimeout microtime(true) - $lastActivity;
  250.         }
  251.     }
  252. }