bug #32141 [HttpClient] fix dealing with 1xx informational responses (nicolas-grekas)
This PR was merged into the 4.3 branch.
Discussion
----------
[HttpClient] fix dealing with 1xx informational responses
| Q | A
| ------------- | ---
| Branch? | 4.3
| Bug fix? | yes
| New feature? | no
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | -
| License | MIT
| Doc PR | -
I never had a look at 1xx status codes until today.
This PR fixes reading them when using curl.
If one wonders:
- `NativeHttpClient` uses `fopen()`, which skips informational parts as allowed by the HTTP spec and doesn't give any way to access their response headers.
- `CurlHttpClient` allows reading informational responses using the progress callback or via the getInfo() method. That's the way if you need to implement e.g. HTTP 103 early hints.
Commits
-------
412411d795
[HttpClient] fix dealing with 1xx informational responses
This commit is contained in:
commit
0219834a2d
@ -287,7 +287,19 @@ final class CurlResponse implements ResponseInterface
|
|||||||
// Regular header line: add it to the list
|
// Regular header line: add it to the list
|
||||||
self::addResponseHeaders([substr($data, 0, -2)], $info, $headers);
|
self::addResponseHeaders([substr($data, 0, -2)], $info, $headers);
|
||||||
|
|
||||||
if (0 === strpos($data, 'HTTP') && 300 <= $info['http_code'] && $info['http_code'] < 400) {
|
if (0 !== strpos($data, 'HTTP/')) {
|
||||||
|
if (0 === stripos($data, 'Location:')) {
|
||||||
|
$location = trim(substr($data, 9, -2));
|
||||||
|
}
|
||||||
|
|
||||||
|
return \strlen($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\function_exists('openssl_x509_read') && $certinfo = curl_getinfo($ch, CURLINFO_CERTINFO)) {
|
||||||
|
$info['peer_certificate_chain'] = array_map('openssl_x509_read', array_column($certinfo, 'Cert'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (300 <= $info['http_code'] && $info['http_code'] < 400) {
|
||||||
if (curl_getinfo($ch, CURLINFO_REDIRECT_COUNT) === $options['max_redirects']) {
|
if (curl_getinfo($ch, CURLINFO_REDIRECT_COUNT) === $options['max_redirects']) {
|
||||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
|
||||||
} elseif (303 === $info['http_code'] || ('POST' === $info['http_method'] && \in_array($info['http_code'], [301, 302], true))) {
|
} elseif (303 === $info['http_code'] || ('POST' === $info['http_method'] && \in_array($info['http_code'], [301, 302], true))) {
|
||||||
@ -296,15 +308,14 @@ final class CurlResponse implements ResponseInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (0 === stripos($data, 'Location:')) {
|
|
||||||
$location = trim(substr($data, 9, -2));
|
|
||||||
}
|
|
||||||
|
|
||||||
return \strlen($data);
|
return \strlen($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// End of headers: handle redirects and add to the activity list
|
// End of headers: handle redirects and add to the activity list
|
||||||
$statusCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
|
if (200 > $statusCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE)) {
|
||||||
|
return \strlen($data);
|
||||||
|
}
|
||||||
|
|
||||||
$info['redirect_url'] = null;
|
$info['redirect_url'] = null;
|
||||||
|
|
||||||
if (300 <= $statusCode && $statusCode < 400 && null !== $location) {
|
if (300 <= $statusCode && $statusCode < 400 && null !== $location) {
|
||||||
@ -334,10 +345,6 @@ final class CurlResponse implements ResponseInterface
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\function_exists('openssl_x509_read') && $certinfo = curl_getinfo($ch, CURLINFO_CERTINFO)) {
|
|
||||||
$info['peer_certificate_chain'] = array_map('openssl_x509_read', array_column($certinfo, 'Cert'));
|
|
||||||
}
|
|
||||||
|
|
||||||
curl_setopt($ch, CURLOPT_PRIVATE, 'content');
|
curl_setopt($ch, CURLOPT_PRIVATE, 'content');
|
||||||
} elseif (null !== $info['redirect_url'] && $logger) {
|
} elseif (null !== $info['redirect_url'] && $logger) {
|
||||||
$logger->info(sprintf('Redirecting: "%s %s"', $info['http_code'], $info['redirect_url']));
|
$logger->info(sprintf('Redirecting: "%s %s"', $info['http_code'], $info['redirect_url']));
|
||||||
|
@ -41,6 +41,17 @@ switch ($vars['REQUEST_URI']) {
|
|||||||
ob_start('ob_gzhandler');
|
ob_start('ob_gzhandler');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case '/103':
|
||||||
|
header('HTTP/1.1 103 Early Hints');
|
||||||
|
header('Link: </style.css>; rel=preload; as=style', false);
|
||||||
|
header('Link: </script.js>; rel=preload; as=script', false);
|
||||||
|
echo "HTTP/1.1 200 OK\r\n";
|
||||||
|
echo "Date: Fri, 26 May 2017 10:02:11 GMT\r\n";
|
||||||
|
echo "Content-Length: 13\r\n";
|
||||||
|
echo "\r\n";
|
||||||
|
echo 'Here the body';
|
||||||
|
exit;
|
||||||
|
|
||||||
case '/404':
|
case '/404':
|
||||||
header('Content-Type: application/json', true, 404);
|
header('Content-Type: application/json', true, 404);
|
||||||
break;
|
break;
|
||||||
|
@ -721,6 +721,15 @@ abstract class HttpClientTestCase extends TestCase
|
|||||||
$this->assertSame('/?a=a&b=b', $body['REQUEST_URI']);
|
$this->assertSame('/?a=a&b=b', $body['REQUEST_URI']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testInformationalResponse()
|
||||||
|
{
|
||||||
|
$client = $this->getHttpClient(__FUNCTION__);
|
||||||
|
$response = $client->request('GET', 'http://localhost:8057/103');
|
||||||
|
|
||||||
|
$this->assertSame('Here the body', $response->getContent());
|
||||||
|
$this->assertSame(200, $response->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @requires extension zlib
|
* @requires extension zlib
|
||||||
*/
|
*/
|
||||||
|
Reference in New Issue
Block a user