Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: attempt to only remember received content when the header is within success range by http spec #7970

Merged
merged 9 commits into from
Jan 10, 2025
5 changes: 4 additions & 1 deletion Storage/src/Connection/Rest.php
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,10 @@ public function downloadObject(array $args = [])
&$attempt,
) {
// if the exception has a response for us to use
if ($e instanceof RequestException && $e->hasResponse()) {
if ($e instanceof RequestException
&& $e->hasResponse()
&& $e->getResponse()->getStatusCode() >= 200
&& $e->getResponse()->getStatusCode() < 300) {
$msg = (string) $e->getResponse()->getBody();

$fetchedStream = Utils::streamFor($msg);
Expand Down
90 changes: 90 additions & 0 deletions Storage/tests/Unit/Connection/RestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

namespace Google\Cloud\Storage\Tests\Unit\Connection;

use Google\Auth\HttpHandler\Guzzle7HttpHandler;
use Google\Cloud\Core\RequestBuilder;
use Google\Cloud\Core\RequestWrapper;
use Google\Cloud\Core\Retry;
Expand All @@ -25,6 +26,9 @@
use Google\Cloud\Core\Upload\ResumableUploader;
use Google\Cloud\Core\Upload\StreamableUploader;
use Google\Cloud\Storage\Connection\Rest;
use Google\Cloud\Storage\Connection\RetryTrait;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Promise\Create;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Psr7\Request;
Expand Down Expand Up @@ -273,6 +277,92 @@ function ($args) use (&$actualRequest, $response) {
);
}

public function downloadObjectWithResumeProvider()
{
$part1 = 'first part';
$part2 = 'second part';
$full = $part1 . $part2;
/** @see RetryTrait::$httpRetryCodes for list of status codes that are allowed to retry */
return [
[0, '', 200, $full, $full, []],
[200, $part1, 200, $part2, $full, ['Range' => 'bytes=10-']],
[408, 'Server Message', 200, $full, $full, []],
[429, 'Server Message', 200, $full, $full, []],
[500, 'Server Error', 200, $full, $full, []],
[502, 'Server Error', 200, $full, $full, []],
[503, 'Server Error', 200, $full, $full, []],
[504, 'Server Error', 200, $full, $full, []],
bshaffer marked this conversation as resolved.
Show resolved Hide resolved
];
}

/**
* @dataProvider downloadObjectWithResumeProvider
*/
public function testDownloadObjectWithResume(int $status1,
string $body1,
int $status2,
string $body2,
string $expectedResult,
array $expectedSecondRequestHeaders)
{
indreka marked this conversation as resolved.
Show resolved Hide resolved
/** @var Request[] $actualRequests */
$actualRequests = [];
$requestHeaders = [];

$mockClient = $this->prophesize(Client::class);
$requestIndex = 0;
$mockClient->send(
Argument::type(RequestInterface::class),
Argument::type('array')
)->will(
function ($args) use (&$actualRequests,
indreka marked this conversation as resolved.
Show resolved Hide resolved
&$requestHeaders,
$status1,
$body1,
$status2,
$body2,
&$requestIndex) {
indreka marked this conversation as resolved.
Show resolved Hide resolved
$actualRequests[$requestIndex] = $args[0];
$requestHeaders[$requestIndex] = $args[1]['headers'] ?? [];
if ($requestIndex++ === 0) {
throw new RequestException("Server error", $args[0], new Response($status1, [], $body1));
}
return new Response($status2, [], $body2);
}
);
$requestWrapper = new RequestWrapper([
'httpHandler' => new Guzzle7HttpHandler($mockClient->reveal()),
'accessToken' => 'Fake token',
'retries' => 3,
]);

$rest = new Rest();
$rest->setRequestWrapper($requestWrapper);

$options = self::$downloadOptions;
$options['retries'] = 3;
$actualBody = (string) $rest->downloadObject($options);
$actualUri1 = (string) $actualRequests[0]->getUri();
$actualUri2 = (string) $actualRequests[1]->getUri();

$expectedUri = sprintf(
'%s/storage/v1/b/bigbucket/o/myfile.txt?generation=100&alt=media&userProject=myProject',
Rest::DEFAULT_API_ENDPOINT
);

$this->assertEquals(
$expectedUri,
$actualUri1
);
$this->assertEquals([], $requestHeaders[0]);
$this->assertEquals(
$expectedUri,
$actualUri2
);
$this->assertEquals($expectedSecondRequestHeaders, $requestHeaders[1]);
$this->assertEquals($expectedResult, $actualBody);
}

/**
* @dataProvider insertObjectProvider
*/
Expand Down