Merge branch '4.4' into 5.1
* 4.4: [HttpClient] fix reading the body after a ClientException
This commit is contained in:
commit
b9e76b2bd9
40
src/Symfony/Component/HttpClient/Internal/Canary.php
Normal file
40
src/Symfony/Component/HttpClient/Internal/Canary.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?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\Internal;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class Canary
|
||||
{
|
||||
private $canceller;
|
||||
|
||||
public function __construct(\Closure $canceller)
|
||||
{
|
||||
$this->canceller = $canceller;
|
||||
}
|
||||
|
||||
public function cancel()
|
||||
{
|
||||
if (($canceller = $this->canceller) instanceof \Closure) {
|
||||
$this->canceller = null;
|
||||
$canceller();
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->cancel();
|
||||
}
|
||||
}
|
@ -24,8 +24,8 @@ use Symfony\Component\HttpClient\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use Symfony\Component\HttpClient\HttpClientTrait;
|
||||
use Symfony\Component\HttpClient\Internal\AmpBody;
|
||||
use Symfony\Component\HttpClient\Internal\AmpCanary;
|
||||
use Symfony\Component\HttpClient\Internal\AmpClientState;
|
||||
use Symfony\Component\HttpClient\Internal\Canary;
|
||||
use Symfony\Component\HttpClient\Internal\ClientState;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
@ -94,6 +94,11 @@ final class AmpResponse implements ResponseInterface
|
||||
|
||||
$multi->openHandles[$id] = $id;
|
||||
++$multi->responseCount;
|
||||
|
||||
$this->canary = new Canary(static function () use ($canceller, $multi, $id) {
|
||||
$canceller->cancel();
|
||||
unset($multi->openHandles[$id], $multi->handlesActivity[$id]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -109,29 +114,14 @@ final class AmpResponse implements ResponseInterface
|
||||
try {
|
||||
$this->doDestruct();
|
||||
} finally {
|
||||
$multi = clone $this->multi;
|
||||
|
||||
$this->close();
|
||||
|
||||
// Clear the DNS cache when all requests completed
|
||||
if (0 >= --$this->multi->responseCount) {
|
||||
$this->multi->responseCount = 0;
|
||||
$this->multi->dnsCache = [];
|
||||
}
|
||||
|
||||
$this->multi = $multi;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
private function close(): void
|
||||
{
|
||||
$this->canceller->cancel();
|
||||
unset($this->multi->openHandles[$this->id], $this->multi->handlesActivity[$this->id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -15,6 +15,7 @@ use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpClient\Chunk\FirstChunk;
|
||||
use Symfony\Component\HttpClient\Chunk\InformationalChunk;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use Symfony\Component\HttpClient\Internal\Canary;
|
||||
use Symfony\Component\HttpClient\Internal\ClientState;
|
||||
use Symfony\Component\HttpClient\Internal\CurlClientState;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
@ -149,6 +150,31 @@ final class CurlResponse implements ResponseInterface
|
||||
// Schedule the request in a non-blocking way
|
||||
$multi->openHandles[$id] = [$ch, $options];
|
||||
curl_multi_add_handle($multi->handle, $ch);
|
||||
|
||||
$this->canary = new Canary(static function () use ($ch, $multi, $id) {
|
||||
unset($multi->openHandles[$id], $multi->handlesActivity[$id]);
|
||||
curl_setopt($ch, \CURLOPT_PRIVATE, '_0');
|
||||
|
||||
if (self::$performing) {
|
||||
return;
|
||||
}
|
||||
|
||||
curl_multi_remove_handle($multi->handle, $ch);
|
||||
curl_setopt_array($ch, [
|
||||
\CURLOPT_NOPROGRESS => true,
|
||||
\CURLOPT_PROGRESSFUNCTION => null,
|
||||
\CURLOPT_HEADERFUNCTION => null,
|
||||
\CURLOPT_WRITEFUNCTION => null,
|
||||
\CURLOPT_READFUNCTION => null,
|
||||
\CURLOPT_INFILE => null,
|
||||
]);
|
||||
|
||||
if (!$multi->openHandles) {
|
||||
// Schedule DNS cache eviction for the next request
|
||||
$multi->dnsCache->evictions = $multi->dnsCache->evictions ?: $multi->dnsCache->removals;
|
||||
$multi->dnsCache->removals = $multi->dnsCache->hostnames = [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -199,48 +225,11 @@ final class CurlResponse implements ResponseInterface
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
try {
|
||||
if (null === $this->timeout) {
|
||||
return; // Unused pushed response
|
||||
}
|
||||
|
||||
$this->doDestruct();
|
||||
} finally {
|
||||
$multi = clone $this->multi;
|
||||
|
||||
$this->close();
|
||||
|
||||
if (!$this->multi->openHandles) {
|
||||
// Schedule DNS cache eviction for the next request
|
||||
$this->multi->dnsCache->evictions = $this->multi->dnsCache->evictions ?: $this->multi->dnsCache->removals;
|
||||
$this->multi->dnsCache->removals = $this->multi->dnsCache->hostnames = [];
|
||||
}
|
||||
|
||||
$this->multi = $multi;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
private function close(): void
|
||||
{
|
||||
unset($this->multi->openHandles[$this->id], $this->multi->handlesActivity[$this->id]);
|
||||
curl_setopt($this->handle, \CURLOPT_PRIVATE, '_0');
|
||||
|
||||
if (self::$performing) {
|
||||
return;
|
||||
if (null === $this->timeout) {
|
||||
return; // Unused pushed response
|
||||
}
|
||||
|
||||
curl_multi_remove_handle($this->multi->handle, $this->handle);
|
||||
curl_setopt_array($this->handle, [
|
||||
\CURLOPT_NOPROGRESS => true,
|
||||
\CURLOPT_PROGRESSFUNCTION => null,
|
||||
\CURLOPT_HEADERFUNCTION => null,
|
||||
\CURLOPT_WRITEFUNCTION => null,
|
||||
\CURLOPT_READFUNCTION => null,
|
||||
\CURLOPT_INFILE => null,
|
||||
]);
|
||||
$this->doDestruct();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -14,6 +14,7 @@ namespace Symfony\Component\HttpClient\Response;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpClient\Chunk\FirstChunk;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use Symfony\Component\HttpClient\Internal\Canary;
|
||||
use Symfony\Component\HttpClient\Internal\ClientState;
|
||||
use Symfony\Component\HttpClient\Internal\NativeClientState;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
@ -43,7 +44,7 @@ final class NativeResponse implements ResponseInterface
|
||||
public function __construct(NativeClientState $multi, $context, string $url, array $options, array &$info, callable $resolveRedirect, ?callable $onProgress, ?LoggerInterface $logger)
|
||||
{
|
||||
$this->multi = $multi;
|
||||
$this->id = (int) $context;
|
||||
$this->id = $id = (int) $context;
|
||||
$this->context = $context;
|
||||
$this->url = $url;
|
||||
$this->logger = $logger;
|
||||
@ -63,6 +64,10 @@ final class NativeResponse implements ResponseInterface
|
||||
$this->initializer = static function (self $response) {
|
||||
return null === $response->remaining;
|
||||
};
|
||||
|
||||
$this->canary = new Canary(static function () use ($multi, $id) {
|
||||
unset($multi->openHandles[$id], $multi->handlesActivity[$id]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -88,17 +93,11 @@ final class NativeResponse implements ResponseInterface
|
||||
try {
|
||||
$this->doDestruct();
|
||||
} finally {
|
||||
$multi = clone $this->multi;
|
||||
|
||||
$this->close();
|
||||
|
||||
// Clear the DNS cache when all requests completed
|
||||
if (0 >= --$this->multi->responseCount) {
|
||||
$this->multi->responseCount = 0;
|
||||
$this->multi->dnsCache = [];
|
||||
}
|
||||
|
||||
$this->multi = $multi;
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,8 +191,8 @@ final class NativeResponse implements ResponseInterface
|
||||
*/
|
||||
private function close(): void
|
||||
{
|
||||
unset($this->multi->openHandles[$this->id], $this->multi->handlesActivity[$this->id]);
|
||||
$this->handle = $this->buffer = $this->onProgress = null;
|
||||
$this->canary->cancel();
|
||||
$this->handle = $this->buffer = $this->inflate = $this->onProgress = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -37,6 +37,7 @@ trait ResponseTrait
|
||||
{
|
||||
private $logger;
|
||||
private $headers = [];
|
||||
private $canary;
|
||||
|
||||
/**
|
||||
* @var callable|null A callback that initializes the two previous properties
|
||||
@ -207,7 +208,11 @@ trait ResponseTrait
|
||||
/**
|
||||
* Closes the response and all its network handles.
|
||||
*/
|
||||
abstract protected function close(): void;
|
||||
private function close(): void
|
||||
{
|
||||
$this->canary->cancel();
|
||||
$this->inflate = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds pending responses to the activity list.
|
||||
|
@ -260,6 +260,7 @@ abstract class HttpClientTestCase extends BaseHttpClientTestCase
|
||||
// The response content-type mustn't be json as that calls getContent
|
||||
// @see src/Symfony/Component/HttpClient/Exception/HttpExceptionTrait.php:58
|
||||
$this->assertStringNotContainsString('json', $e->getResponse()->getHeaders(false)['content-type'][0] ?? '');
|
||||
unset($e);
|
||||
|
||||
$r = new \ReflectionProperty($client, 'multi');
|
||||
$r->setAccessible(true);
|
||||
|
@ -66,6 +66,9 @@ switch ($vars['REQUEST_URI']) {
|
||||
case '/404-gzipped':
|
||||
header('Content-Type: text/plain', true, 404);
|
||||
ob_start('ob_gzhandler');
|
||||
@ob_flush();
|
||||
flush();
|
||||
usleep(300000);
|
||||
echo 'some text';
|
||||
exit;
|
||||
|
||||
|
Reference in New Issue
Block a user