-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- This change includes the following: -- Refactor the s3 200 response error handling logic so it can be opened for all s3 operations. -- Migrate some S3 parsers to a result mutator implementation so the code can be a bit cleaner.
- Loading branch information
1 parent
4f59484
commit b91a09c
Showing
9 changed files
with
784 additions
and
178 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?php | ||
|
||
namespace Aws\S3\Parser; | ||
|
||
use Aws\CommandInterface; | ||
use Aws\ResultInterface; | ||
use Psr\Http\Message\ResponseInterface; | ||
|
||
class GetBucketLocationResultMutator implements S3ResultMutator | ||
{ | ||
|
||
public function __invoke(ResultInterface $result, CommandInterface $command, ResponseInterface $response): ResultInterface | ||
{ | ||
if ($command->getName() !== 'GetBucketLocation') { | ||
return $result; | ||
} | ||
|
||
$location = 'us-east-1'; | ||
if (preg_match('/>(.+?)<\/LocationConstraint>/', $response->getBody(), $matches)) { | ||
$location = $matches[1] === 'EU' ? 'eu-west-1' : $matches[1]; | ||
} | ||
$result['LocationConstraint'] = $location; | ||
|
||
return $result; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
<?php | ||
|
||
namespace Aws\S3\Parser; | ||
|
||
use Aws\Api\ErrorParser\XmlErrorParser; | ||
use Aws\Api\Parser\AbstractParser; | ||
use Aws\Api\Parser\Exception\ParserException; | ||
use Aws\Api\Service; | ||
use Aws\Api\StructureShape; | ||
use Aws\CommandInterface; | ||
use Aws\Exception\AwsException; | ||
use Aws\ResultInterface; | ||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\StreamInterface; | ||
|
||
class S3Parser extends AbstractParser | ||
{ | ||
/** | ||
* @var AbstractParser | ||
*/ | ||
private $protocolParser; | ||
/** | ||
* @var XmlErrorParser | ||
*/ | ||
private $errorParser; | ||
/** | ||
* @var string | ||
*/ | ||
private $exceptionClass; | ||
/** | ||
* @var array | ||
*/ | ||
private $s3ResultMutators; | ||
|
||
/** | ||
* @param AbstractParser $protocolParser | ||
* @param XmlErrorParser $errorParser | ||
* @param Service $api | ||
* @param string $exceptionClass | ||
*/ | ||
public function __construct( | ||
AbstractParser $protocolParser, | ||
XmlErrorParser $errorParser, | ||
Service $api, | ||
string $exceptionClass = AwsException::class | ||
) | ||
{ | ||
parent::__construct($api); | ||
$this->protocolParser = $protocolParser; | ||
$this->errorParser = $errorParser; | ||
$this->exceptionClass = $exceptionClass; | ||
$this->s3ResultMutators = []; | ||
} | ||
|
||
/** | ||
* Parses a S3 response. | ||
* | ||
* @param CommandInterface $command The command that originated the request. | ||
* @param ResponseInterface $response The response gotten from the service. | ||
* | ||
* @return ResultInterface|null | ||
*/ | ||
public function __invoke(CommandInterface $command, ResponseInterface $response):? ResultInterface | ||
{ | ||
// Check first if the response is an error | ||
$this->parse200Error($command, $response); | ||
|
||
try { | ||
$parseFn = $this->protocolParser; | ||
$result = $parseFn($command, $response); | ||
} catch (ParserException $e) { | ||
throw new $this->exceptionClass( | ||
"Error parsing response for {$command->getName()}:" | ||
. " AWS parsing error: {$e->getMessage()}", | ||
$command, | ||
['connection_error' => true, 'exception' => $e], | ||
$e | ||
); | ||
} | ||
|
||
return $this->executeS3ResultMutators($result, $command, $response); | ||
} | ||
|
||
/** | ||
* Tries to parse a 200 response as an error from S3. | ||
* If the parsed result contains a code and message then that means an error | ||
* was found, and hence an exception is thrown with that error. | ||
* | ||
* @param CommandInterface $command | ||
* @param ResponseInterface $response | ||
* @return void | ||
*/ | ||
private function parse200Error(CommandInterface $command, ResponseInterface $response) | ||
{ | ||
// This error parsing should be just for 200 error responses and operations where its output shape | ||
// does not have a streaming member. | ||
if (200 !== $response->getStatusCode() || $this->hasStreamingTrait($command->getName())) { | ||
return; | ||
} | ||
|
||
// To guarantee we try the error parsing just for an Error xml response. | ||
if (!$this->isFirstRootElementError($response->getBody())) { | ||
return; | ||
} | ||
|
||
try { | ||
$errorParserFn = $this->errorParser; | ||
$parsedError = $errorParserFn($response, $command); | ||
} catch (ParserException $e) { | ||
$parsedError = [ | ||
'code' => 'ConnectionError', | ||
'message' => "An error connecting to the service occurred" | ||
. " while performing the " . $command->getName() | ||
. " operation." | ||
]; | ||
} | ||
|
||
if (isset($parsedError['code']) && isset($parsedError['message'])) { | ||
throw new $this->exceptionClass( | ||
$parsedError['message'], | ||
$command, | ||
[ | ||
'connection_error' => true, | ||
'code' => $parsedError['code'], | ||
'message' => $parsedError['message'] | ||
] | ||
); | ||
} | ||
} | ||
|
||
/** | ||
* Checks if a specific operation has a streaming trait. | ||
* | ||
* @param $commandName | ||
* @return bool | ||
*/ | ||
private function hasStreamingTrait($commandName): bool | ||
{ | ||
$operation = $this->api->getOperation($commandName); | ||
$output = $operation->getOutput(); | ||
foreach ($output->getMembers() as $_ => $memberProps) { | ||
if (!empty($memberProps['eventstream'])) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
private function isFirstRootElementError(StreamInterface $responseBody): bool | ||
{ | ||
$pattern = '/<\?xml version="1\.0" encoding="UTF-8"\?>\s*<Error>/'; | ||
|
||
return preg_match($pattern, $responseBody); | ||
} | ||
|
||
/** | ||
* Execute mutator implementations over a result. | ||
* Mutators are logics that modifies a result. | ||
* | ||
* @param ResultInterface $result | ||
* @param CommandInterface $command | ||
* @param ResponseInterface $response | ||
* @return ResultInterface | ||
*/ | ||
private function executeS3ResultMutators( | ||
ResultInterface $result, | ||
CommandInterface $command, | ||
ResponseInterface $response | ||
): ResultInterface | ||
{ | ||
foreach ($this->s3ResultMutators as $mutator) { | ||
$result = $mutator($result, $command, $response); | ||
} | ||
|
||
return $result; | ||
} | ||
|
||
/** | ||
* Adds a mutator into the list of mutators. | ||
* | ||
* @param $mutatorName | ||
* @param S3ResultMutator $s3ResultMutator | ||
* @return void | ||
*/ | ||
public function addS3ResultMutator($mutatorName, S3ResultMutator $s3ResultMutator) | ||
{ | ||
if (isset($this->s3ResultMutators[$mutatorName])) { | ||
trigger_error("The S3 Result Mutator $mutatorName already exists!", E_USER_WARNING); | ||
|
||
return; | ||
} | ||
|
||
$this->s3ResultMutators[$mutatorName] = $s3ResultMutator; | ||
} | ||
|
||
/** | ||
* Removes a mutator from the mutator list. | ||
* | ||
* @param $mutatorName | ||
* @return void | ||
*/ | ||
public function removeS3ResultMutator($mutatorName) | ||
{ | ||
if (!isset($this->s3ResultMutators[$mutatorName])) { | ||
trigger_error("The S3 Result Mutator $mutatorName does not exist!", E_USER_WARNING); | ||
|
||
return; | ||
} | ||
|
||
unset($this->s3ResultMutators[$mutatorName]); | ||
} | ||
|
||
/** | ||
* Returns the list of result mutators available. | ||
* | ||
* @return array | ||
*/ | ||
public function getS3ResultMutators(): array | ||
{ | ||
return $this->s3ResultMutators; | ||
} | ||
|
||
public function parseMemberFromStream(StreamInterface $stream, StructureShape $member, $response) | ||
{ | ||
return $this->protocolParser->parseMemberFromStream($stream, $member, $response); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<?php | ||
|
||
namespace Aws\S3\Parser; | ||
|
||
use Aws\CommandInterface; | ||
use Aws\ResultInterface; | ||
use Psr\Http\Message\ResponseInterface; | ||
|
||
interface S3ResultMutator | ||
{ | ||
public function __invoke( | ||
ResultInterface $result, | ||
CommandInterface $command, | ||
ResponseInterface $response | ||
): ResultInterface; | ||
} |
Oops, something went wrong.