Skip to content

Commit 698a8b5

Browse files
Merge branch '4.4'
* 4.4: (39 commits) [Console] Fix #33915, Detect dimensions using mode CON if vt100 is supported [PhpUnitBridge] Also search for composer.phar in git root folder [HttpKernel][DataCollectorInterface] Ease compatibility Add tests to ensure defaultLocale is properly passed to the URL generator [DependencyInjection] Fix broken references in tests [VarDumper] display the method we're in when dumping stack traces [HttpClient] Retry safe requests when then fail before the body arrives [Console] Rename some methods related to redraw frequency Avoid using of kernel after shutdown Simplify PHP CS Fixer configuration [PropertyInfo] Fixed type extraction for nullable collections of non-nullable elements [FrameworkBundle] [HttpKernel] fixed correct EOL and EOM month Fix CS [Serializer] Fix property name usage for denormalization Name test accordingly to the tested class Fix MockFileSessionStorageTest::sessionDir being used after it's unset [Security] Fix SwitchUserToken wrongly deauthenticated Supporting Bootstrap 4 custom switches Add new Form WeekType bumped Symfony version to 4.3.7 ...
2 parents a6f6dd3 + 7109135 commit 698a8b5

File tree

6 files changed

+80
-33
lines changed

6 files changed

+80
-33
lines changed

Chunk/ErrorChunk.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,19 @@ class ErrorChunk implements ChunkInterface
2626
private $errorMessage;
2727
private $error;
2828

29-
public function __construct(int $offset, \Throwable $error = null)
29+
/**
30+
* @param \Throwable|string $error
31+
*/
32+
public function __construct(int $offset, $error)
3033
{
3134
$this->offset = $offset;
32-
$this->error = $error;
33-
$this->errorMessage = null !== $error ? $error->getMessage() : 'Reading from the response stream reached the idle timeout.';
35+
36+
if (\is_string($error)) {
37+
$this->errorMessage = $error;
38+
} else {
39+
$this->error = $error;
40+
$this->errorMessage = $error->getMessage();
41+
}
3442
}
3543

3644
/**

CurlHttpClient.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
4949
*/
5050
private $multi;
5151

52+
private static $curlVersion;
53+
5254
/**
5355
* @param array $defaultOptions Default requests' options
5456
* @param int $maxHostConnections The maximum number of connections to a single host
@@ -69,6 +71,7 @@ public function __construct(array $defaultOptions = [], int $maxHostConnections
6971
}
7072

7173
$this->multi = $multi = new CurlClientState();
74+
self::$curlVersion = self::$curlVersion ?? curl_version();
7275

7376
// Don't enable HTTP/1.1 pipelining: it forces responses to be sent in order
7477
if (\defined('CURLPIPE_MULTIPLEX')) {
@@ -87,7 +90,7 @@ public function __construct(array $defaultOptions = [], int $maxHostConnections
8790
}
8891

8992
// HTTP/2 push crashes before curl 7.61
90-
if (!\defined('CURLMOPT_PUSHFUNCTION') || 0x073d00 > ($v = curl_version())['version_number'] || !(CURL_VERSION_HTTP2 & $v['features'])) {
93+
if (!\defined('CURLMOPT_PUSHFUNCTION') || 0x073d00 > self::$curlVersion['version_number'] || !(CURL_VERSION_HTTP2 & self::$curlVersion['features'])) {
9194
return;
9295
}
9396

@@ -192,7 +195,7 @@ public function request(string $method, string $url, array $options = []): Respo
192195
$this->multi->dnsCache->evictions = [];
193196
$port = parse_url($authority, PHP_URL_PORT) ?: ('http:' === $scheme ? 80 : 443);
194197

195-
if ($resolve && 0x072a00 > curl_version()['version_number']) {
198+
if ($resolve && 0x072a00 > self::$curlVersion['version_number']) {
196199
// DNS cache removals require curl 7.42 or higher
197200
// On lower versions, we have to create a new multi handle
198201
curl_multi_close($this->multi->handle);
@@ -212,7 +215,7 @@ public function request(string $method, string $url, array $options = []): Respo
212215
$curlopts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
213216
} elseif (1.1 === (float) $options['http_version'] || 'https:' !== $scheme) {
214217
$curlopts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1;
215-
} elseif (\defined('CURL_VERSION_HTTP2') && CURL_VERSION_HTTP2 & curl_version()['features']) {
218+
} elseif (\defined('CURL_VERSION_HTTP2') && CURL_VERSION_HTTP2 & self::$curlVersion['features']) {
216219
$curlopts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0;
217220
}
218221

Response/CurlResponse.php

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
*/
2626
final class CurlResponse implements ResponseInterface
2727
{
28-
use ResponseTrait;
28+
use ResponseTrait {
29+
getContent as private doGetContent;
30+
}
2931

3032
private static $performing = false;
3133
private $multi;
@@ -60,7 +62,7 @@ public function __construct(CurlClientState $multi, $ch, array $options = null,
6062

6163
if (!$info['response_headers']) {
6264
// Used to keep track of what we're waiting for
63-
curl_setopt($ch, CURLOPT_PRIVATE, 'headers');
65+
curl_setopt($ch, CURLOPT_PRIVATE, \in_array($method, ['GET', 'HEAD', 'OPTIONS', 'TRACE'], true) && 1.0 < (float) ($options['http_version'] ?? 1.1) ? 'H2' : 'H0'); // H = headers + retry counter
6466
}
6567

6668
if (null === $content = &$this->content) {
@@ -139,7 +141,7 @@ public function __construct(CurlClientState $multi, $ch, array $options = null,
139141

140142
$waitFor = curl_getinfo($ch = $response->handle, CURLINFO_PRIVATE);
141143

142-
if (\in_array($waitFor, ['headers', 'destruct'], true)) {
144+
if ('H' === $waitFor[0] || 'D' === $waitFor[0]) {
143145
try {
144146
foreach (self::stream([$response]) as $chunk) {
145147
if ($chunk->isFirst()) {
@@ -153,16 +155,11 @@ public function __construct(CurlClientState $multi, $ch, array $options = null,
153155
throw $e;
154156
}
155157
}
156-
157-
curl_setopt($ch, CURLOPT_HEADERFUNCTION, null);
158-
curl_setopt($ch, CURLOPT_READFUNCTION, null);
159-
curl_setopt($ch, CURLOPT_INFILE, null);
160158
};
161159

162160
// Schedule the request in a non-blocking way
163161
$multi->openHandles[$id] = [$ch, $options];
164162
curl_multi_add_handle($multi->handle, $ch);
165-
self::perform($multi);
166163
}
167164

168165
/**
@@ -171,8 +168,6 @@ public function __construct(CurlClientState $multi, $ch, array $options = null,
171168
public function getInfo(string $type = null)
172169
{
173170
if (!$info = $this->finalInfo) {
174-
self::perform($this->multi);
175-
176171
$info = array_merge($this->info, curl_getinfo($this->handle));
177172
$info['url'] = $this->info['url'] ?? $info['url'];
178173
$info['redirect_url'] = $this->info['redirect_url'] ?? null;
@@ -185,8 +180,9 @@ public function getInfo(string $type = null)
185180

186181
rewind($this->debugBuffer);
187182
$info['debug'] = stream_get_contents($this->debugBuffer);
183+
$waitFor = curl_getinfo($this->handle, CURLINFO_PRIVATE);
188184

189-
if (!\in_array(curl_getinfo($this->handle, CURLINFO_PRIVATE), ['headers', 'content'], true)) {
185+
if ('H' !== $waitFor[0] && 'C' !== $waitFor[0]) {
190186
curl_setopt($this->handle, CURLOPT_VERBOSE, false);
191187
rewind($this->debugBuffer);
192188
ftruncate($this->debugBuffer, 0);
@@ -197,17 +193,35 @@ public function getInfo(string $type = null)
197193
return null !== $type ? $info[$type] ?? null : $info;
198194
}
199195

196+
/**
197+
* {@inheritdoc}
198+
*/
199+
public function getContent(bool $throw = true): string
200+
{
201+
$performing = self::$performing;
202+
self::$performing = $performing || '_0' === curl_getinfo($this->handle, CURLINFO_PRIVATE);
203+
204+
try {
205+
return $this->doGetContent($throw);
206+
} finally {
207+
self::$performing = $performing;
208+
}
209+
}
210+
200211
public function __destruct()
201212
{
202213
try {
203214
if (null === $this->timeout) {
204215
return; // Unused pushed response
205216
}
206217

207-
if ('content' === $waitFor = curl_getinfo($this->handle, CURLINFO_PRIVATE)) {
218+
$waitFor = curl_getinfo($this->handle, CURLINFO_PRIVATE);
219+
220+
if ('C' === $waitFor[0] || '_' === $waitFor[0]) {
208221
$this->close();
209-
} elseif ('headers' === $waitFor) {
210-
curl_setopt($this->handle, CURLOPT_PRIVATE, 'destruct');
222+
} elseif ('H' === $waitFor[0]) {
223+
$waitFor[0] = 'D'; // D = destruct
224+
curl_setopt($this->handle, CURLOPT_PRIVATE, $waitFor);
211225
}
212226

213227
$this->doDestruct();
@@ -245,7 +259,7 @@ private function close(): void
245259
unset($this->multi->openHandles[$this->id], $this->multi->handlesActivity[$this->id]);
246260
curl_multi_remove_handle($this->multi->handle, $this->handle);
247261
curl_setopt_array($this->handle, [
248-
CURLOPT_PRIVATE => '',
262+
CURLOPT_PRIVATE => '_0',
249263
CURLOPT_NOPROGRESS => true,
250264
CURLOPT_PROGRESSFUNCTION => null,
251265
CURLOPT_HEADERFUNCTION => null,
@@ -266,7 +280,7 @@ private static function schedule(self $response, array &$runningResponses): void
266280
$runningResponses[$i] = [$response->multi, [$response->id => $response]];
267281
}
268282

269-
if ('' === curl_getinfo($ch = $response->handle, CURLINFO_PRIVATE)) {
283+
if ('_0' === curl_getinfo($ch = $response->handle, CURLINFO_PRIVATE)) {
270284
// Response already completed
271285
$response->multi->handlesActivity[$response->id][] = null;
272286
$response->multi->handlesActivity[$response->id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null;
@@ -294,8 +308,26 @@ private static function perform(CurlClientState $multi, array &$responses = null
294308
while (CURLM_CALL_MULTI_PERFORM === curl_multi_exec($multi->handle, $active));
295309

296310
while ($info = curl_multi_info_read($multi->handle)) {
297-
$multi->handlesActivity[(int) $info['handle']][] = null;
298-
$multi->handlesActivity[(int) $info['handle']][] = \in_array($info['result'], [\CURLE_OK, \CURLE_TOO_MANY_REDIRECTS], true) || (\CURLE_WRITE_ERROR === $info['result'] && 'destruct' === @curl_getinfo($info['handle'], CURLINFO_PRIVATE)) ? null : new TransportException(sprintf('%s for "%s".', curl_strerror($info['result']), curl_getinfo($info['handle'], CURLINFO_EFFECTIVE_URL)));
311+
$result = $info['result'];
312+
$id = (int) $ch = $info['handle'];
313+
$waitFor = @curl_getinfo($ch, CURLINFO_PRIVATE) ?: '_0';
314+
315+
if (\in_array($result, [\CURLE_SEND_ERROR, \CURLE_RECV_ERROR, /*CURLE_HTTP2*/ 16, /*CURLE_HTTP2_STREAM*/ 92], true) && $waitFor[1] && 'C' !== $waitFor[0]) {
316+
curl_multi_remove_handle($multi->handle, $ch);
317+
$waitFor[1] = (string) ((int) $waitFor[1] - 1); // decrement the retry counter
318+
curl_setopt($ch, CURLOPT_PRIVATE, $waitFor);
319+
320+
if ('1' === $waitFor[1]) {
321+
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
322+
}
323+
324+
if (0 === curl_multi_add_handle($multi->handle, $ch)) {
325+
continue;
326+
}
327+
}
328+
329+
$multi->handlesActivity[$id][] = null;
330+
$multi->handlesActivity[$id][] = \in_array($result, [\CURLE_OK, \CURLE_TOO_MANY_REDIRECTS], true) || '_0' === $waitFor || curl_getinfo($ch, CURLINFO_SIZE_DOWNLOAD) === curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD) ? null : new TransportException(sprintf('%s for "%s".', curl_strerror($result), curl_getinfo($ch, CURLINFO_EFFECTIVE_URL)));
299331
}
300332
} finally {
301333
self::$performing = false;
@@ -320,7 +352,9 @@ private static function select(CurlClientState $multi, float $timeout): int
320352
*/
321353
private static function parseHeaderLine($ch, string $data, array &$info, array &$headers, ?array $options, CurlClientState $multi, int $id, ?string &$location, ?callable $resolveRedirect, ?LoggerInterface $logger, &$content = null): int
322354
{
323-
if (!\in_array($waitFor = @curl_getinfo($ch, CURLINFO_PRIVATE), ['headers', 'destruct'], true)) {
355+
$waitFor = @curl_getinfo($ch, CURLINFO_PRIVATE) ?: '_0';
356+
357+
if ('H' !== $waitFor[0] && 'D' !== $waitFor[0]) {
324358
return \strlen($data); // Ignore HTTP trailers
325359
}
326360

@@ -381,14 +415,18 @@ private static function parseHeaderLine($ch, string $data, array &$info, array &
381415
}
382416

383417
if ($statusCode < 300 || 400 <= $statusCode || null === $location || curl_getinfo($ch, CURLINFO_REDIRECT_COUNT) === $options['max_redirects']) {
384-
// Headers and redirects completed, time to get the response's body
418+
// Headers and redirects completed, time to get the response's content
385419
$multi->handlesActivity[$id][] = new FirstChunk();
386420

387-
if ('destruct' === $waitFor) {
388-
return 0;
421+
if ('D' === $waitFor[0] || 'HEAD' === $info['http_method'] || \in_array($statusCode, [204, 304], true)) {
422+
$waitFor = '_0'; // no content expected
423+
$multi->handlesActivity[$id][] = null;
424+
$multi->handlesActivity[$id][] = null;
425+
} else {
426+
$waitFor[0] = 'C'; // C = content
389427
}
390428

391-
curl_setopt($ch, CURLOPT_PRIVATE, 'content');
429+
curl_setopt($ch, CURLOPT_PRIVATE, $waitFor);
392430

393431
try {
394432
if (!$content && $options['buffer'] instanceof \Closure && $content = $options['buffer']($headers) ?: null) {

Response/MockResponse.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ private static function readResponse(self $response, array $options, ResponseInt
291291
foreach ($body as $chunk) {
292292
if ('' === $chunk = (string) $chunk) {
293293
// simulate an idle timeout
294-
$response->body[] = new ErrorChunk($offset);
294+
$response->body[] = new ErrorChunk($offset, sprintf('Idle timeout reached for "%s".', $response->info['url']));
295295
} else {
296296
$response->body[] = $chunk;
297297
$offset += \strlen($chunk);

Response/NativeResponse.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,6 @@ public function __construct(NativeClientState $multi, $context, string $url, $op
8282
public function getInfo(string $type = null)
8383
{
8484
if (!$info = $this->finalInfo) {
85-
self::perform($this->multi);
86-
8785
$info = $this->info;
8886
$info['url'] = implode('', $info['url']);
8987
unset($info['size_body'], $info['request_header']);

Response/ResponseTrait.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ public static function stream(iterable $responses, float $timeout = null): \Gene
315315
unset($responses[$j]);
316316
continue;
317317
} elseif ($isTimeout) {
318-
$multi->handlesActivity[$j] = [new ErrorChunk($response->offset)];
318+
$multi->handlesActivity[$j] = [new ErrorChunk($response->offset, sprintf('Idle timeout reached for "%s".', $response->getInfo('url')))];
319319
} else {
320320
continue;
321321
}

0 commit comments

Comments
 (0)