Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 60f771b

Browse filesBrowse files
[HttpClient] fix support for 103 Early Hints and other informational status codes
1 parent 92ac848 commit 60f771b
Copy full SHA for 60f771b

File tree

9 files changed

+111
-5
lines changed
Filter options

9 files changed

+111
-5
lines changed

‎src/Symfony/Component/HttpClient/Chunk/DataChunk.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/Chunk/DataChunk.php
+12-2Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
*/
2121
class DataChunk implements ChunkInterface
2222
{
23-
private $offset;
24-
private $content;
23+
private $offset = 0;
24+
private $content = '';
2525

2626
public function __construct(int $offset = 0, string $content = '')
2727
{
@@ -53,6 +53,16 @@ public function isLast(): bool
5353
return false;
5454
}
5555

56+
/**
57+
* {@inheritdoc}
58+
*/
59+
public function isInformational(?array &$headers = []): int
60+
{
61+
$headers = [];
62+
63+
return 0;
64+
}
65+
5666
/**
5767
* {@inheritdoc}
5868
*/

‎src/Symfony/Component/HttpClient/Chunk/ErrorChunk.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/Chunk/ErrorChunk.php
+9Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,15 @@ public function isLast(): bool
6565
throw new TransportException($this->errorMessage, 0, $this->error);
6666
}
6767

68+
/**
69+
* {@inheritdoc}
70+
*/
71+
public function isInformational(?array &$headers = []): int
72+
{
73+
$this->didThrow = true;
74+
throw new TransportException($this->errorMessage, 0, $this->error);
75+
}
76+
6877
/**
6978
* {@inheritdoc}
7079
*/
+39Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpClient\Chunk;
13+
14+
/**
15+
* @author Nicolas Grekas <p@tchwork.com>
16+
*
17+
* @internal
18+
*/
19+
class InformationalChunk extends DataChunk
20+
{
21+
private $status = 0;
22+
private $headers = [];
23+
24+
public function __construct(int $status, array $headers)
25+
{
26+
$this->status = $status;
27+
$this->headers = $headers;
28+
}
29+
30+
/**
31+
* {@inheritdoc}
32+
*/
33+
public function isInformational(?array &$headers = []): int
34+
{
35+
$headers = $this->headers;
36+
37+
return $this->status;
38+
}
39+
}

‎src/Symfony/Component/HttpClient/Response/CurlResponse.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/Response/CurlResponse.php
+6-2Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Psr\Log\LoggerInterface;
1515
use Symfony\Component\HttpClient\Chunk\FirstChunk;
16+
use Symfony\Component\HttpClient\Chunk\InformationalChunk;
1617
use Symfony\Component\HttpClient\Exception\TransportException;
1718
use Symfony\Component\HttpClient\Internal\CurlClientState;
1819
use Symfony\Contracts\HttpClient\ResponseInterface;
@@ -314,8 +315,11 @@ private static function parseHeaderLine($ch, string $data, array &$info, array &
314315
return \strlen($data);
315316
}
316317

317-
// End of headers: handle redirects and add to the activity list
318+
// End of headers: handle informational responses, redirects, etc.
319+
318320
if (200 > $statusCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE)) {
321+
$multi->handlesActivity[$id][] = new InformationalChunk($statusCode, $headers);
322+
319323
return \strlen($data);
320324
}
321325

@@ -342,7 +346,7 @@ private static function parseHeaderLine($ch, string $data, array &$info, array &
342346

343347
if ($statusCode < 300 || 400 <= $statusCode || curl_getinfo($ch, CURLINFO_REDIRECT_COUNT) === $options['max_redirects']) {
344348
// Headers and redirects completed, time to get the response's body
345-
$multi->handlesActivity[$id] = [new FirstChunk()];
349+
$multi->handlesActivity[$id][] = new FirstChunk();
346350

347351
if ('destruct' === $waitFor) {
348352
return 0;

‎src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ protected function getHttpClient(string $testCase): HttpClientInterface
7070
$this->markTestSkipped("MockHttpClient doesn't timeout on destruct");
7171
break;
7272

73+
case 'testInformationalResponseStream':
74+
$this->markTestSkipped("MockHttpClient doesn't allow mocking informational chunks (yet)");
75+
break;
76+
7377
case 'testGetRequest':
7478
array_unshift($headers, 'HTTP/1.1 200 OK');
7579
$responses[] = new MockResponse($body, ['response_headers' => $headers]);

‎src/Symfony/Component/HttpClient/Tests/NativeHttpClientTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/Tests/NativeHttpClientTest.php
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,9 @@ protected function getHttpClient(string $testCase): HttpClientInterface
2020
{
2121
return new NativeHttpClient();
2222
}
23+
24+
public function testInformationalResponseStream()
25+
{
26+
$this->markTestSkipped('NativeHttpClient doesn\'t support informational status codes.');
27+
}
2328
}

‎src/Symfony/Component/HttpClient/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/composer.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"require": {
2222
"php": "^7.1.3",
2323
"psr/log": "^1.0",
24-
"symfony/http-client-contracts": "^1.1.6",
24+
"symfony/http-client-contracts": "^1.1.7",
2525
"symfony/polyfill-php73": "^1.11"
2626
},
2727
"require-dev": {

‎src/Symfony/Contracts/HttpClient/ChunkInterface.php

Copy file name to clipboardExpand all lines: src/Symfony/Contracts/HttpClient/ChunkInterface.php
+11Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,17 @@ public function isFirst(): bool;
4747
*/
4848
public function isLast(): bool;
4949

50+
/**
51+
* Tells when an 1xx status code was just received.
52+
*
53+
* @param array|null &$headers Set by reference to the headers of the informational response
54+
*
55+
* @return int The status code of the informational response or 0 if the response is not informational
56+
*
57+
* @throws TransportExceptionInterface on a network error or when the idle timeout is reached
58+
*/
59+
public function isInformational(?array &$headers = []): int;
60+
5061
/**
5162
* Returns the content of the response chunk.
5263
*

‎src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php

Copy file name to clipboardExpand all lines: src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php
+24Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,30 @@ public function testInformationalResponse()
754754
$this->assertSame(200, $response->getStatusCode());
755755
}
756756

757+
public function testInformationalResponseStream()
758+
{
759+
$client = $this->getHttpClient(__FUNCTION__);
760+
$response = $client->request('GET', 'http://localhost:8057/103');
761+
762+
$chunks = [];
763+
foreach ($client->stream($response) as $chunk) {
764+
$chunks[] = $chunk;
765+
}
766+
767+
$this->assertSame(103, $chunks[0]->isInformational($headers));
768+
$this->assertSame(['</style.css>; rel=preload; as=style', '</script.js>; rel=preload; as=script'], $headers['link']);
769+
$this->assertTrue($chunks[1]->isFirst());
770+
$this->assertSame('Here the body', $chunks[2]->getContent());
771+
$this->assertTrue($chunks[3]->isLast());
772+
773+
$this->assertSame(['date', 'content-length'], array_keys($response->getHeaders()));
774+
$this->assertContains('Link: </style.css>; rel=preload; as=style', $response->getInfo('response_headers'));
775+
776+
$headers = null;
777+
$this->assertSame(0, $chunks[3]->isInformational($headers));
778+
$this->assertSame([], $headers);
779+
}
780+
757781
/**
758782
* @requires extension zlib
759783
*/

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.