fix: add streaming flag and error handling#3214
fix: add streaming flag and error handling#3214yenfryherrerafeliz wants to merge 25 commits intoaws:masterfrom
Conversation
- Evaluates if a client's operation requires http streaming flag to be set to true, and if so then it pass the option along. - Make error parsers to reused a parsed body for scenario where the body is non seekable and can't be consumed again. Also change the condition: (!$body->isSeekable() || $body->getSize()) To: (!$body->isSeekable() || !$body->getSize()) The reason is that a stream non seekable will most likely not have a size, which means this condition will always be true.
Reverted the condition to what it was: (!$body->isSeekable() || $body->getSize()) Why?, I need to understand better what is the usage of this condition before changing. It was breaking some integ tests.
- When a response body is empty then we dont try to parse it. - When the response body is non seekable then, we read the full body before parsing.
|
- Remove lowercasing parsed in rest json error parser.
Error is parsed when the body of the response is not seekable.
| throw new \RuntimeException('The HTTP handler was rejected without an "exception" key value pair.'); | ||
| } | ||
|
|
||
| $serviceError = "AWS HTTP error: " . $err['exception']->getMessage(); |
There was a problem hiding this comment.
The change in format might affect log parsing. I'd leave this unless it's accounted for later
There was a problem hiding this comment.
I basically changed this because it was duplicating the error message. What I did in specific was to append the message from the exception when a response is not available, but, if we have a response then we just use the error message from the deserialization process.
if (!isset($err['response'])) {
$parts = ['response' => null];
$serviceError .= $err['exception']->getMessage(); // MOVED HERE.
}There was a problem hiding this comment.
Ah, I see. So basically a caller will still see the same thing, just printed from somewhere else?
There was a problem hiding this comment.
Yes, caller will still see the same thing, but with the difference that now error messages will not be duplicated. Previously, if we deserialized a modeled message then we duplicate the error we output.
For example, before we were getting:
PHP Fatal error: Uncaught exception 'Aws\S3\Exception\S3Exception' with message 'Error executing "GetObject" on "https://bucket.s3.us-east-2.amazonaws.com/no-exists"; AWS HTTP error:
Client error: `GET https://bucket.s3.us-east-2.amazonaws.com/no-exists` resulted in a `404 Not Found` response:
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>NoSuchKey</Code><Message>The specified key does not exist.</Message> (truncated...)
NoSuchKey (client): The specified key does not exist.'
GuzzleHttp\Exception\ClientException: Client error: `GET https://bucket.s3.us-east-2.amazonaws.com/no-exists` resulted in a `404 Not Found` response:
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>NoSuchKey</Code><Message>The specified key does not exist.</Message> (truncated...)
But now, after this fix:
PHP Fatal error: Uncaught exception 'Aws\S3\Exception\S3Exception' with message 'Error executing "GetObject" on "https://bucket.s3.us-east-2.amazonaws.com/no-exists"; AWS HTTP error:
NoSuchKey (client): The specified key does not exist.'
GuzzleHttp\Exception\ClientException: Client error: `GET https://bucket.s3.us-east-2.amazonaws.com/no-exists` resulted in a `404 Not Found` response:
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>NoSuchKey</Code><Message>The specified key does not exist.</Message> (truncated...)
When the response body is a non seekable stream, which happens when the stream flag is set in the request, the parser condition to do a parsing is evaluated to true, but in empty payloads, such as when doing head requests, it fails. To remedy this we read the full body content and evaluate whether the returned value is empty before parsing.
To make a body content to be fully available always in implementations a response wrapper class has been created that takes in a response and evaluates if the body is non-seekable then it wrappes it into a CachingStream that will allow the body to rewind.
Overwrites responses on non-seekable streams by wrapping the non-seekable body on a CachingStream to allow rewind.
Avoid consuming original body before it is cached.
|
stobrien89
left a comment
There was a problem hiding this comment.
A few more comments and an old one (log parsing) that needs to be addressed
stobrien89
left a comment
There was a problem hiding this comment.
A few more comments- I just responded to that one question without doing a proper review, but thanks for the quick turnaround
| throw new \RuntimeException('The HTTP handler was rejected without an "exception" key value pair.'); | ||
| } | ||
|
|
||
| $serviceError = "AWS HTTP error: " . $err['exception']->getMessage(); |
There was a problem hiding this comment.
Ah, I see. So basically a caller will still see the same thing, just printed from somewhere else?
| } else { | ||
| $response = AbstractParser::getResponseWithCachingStream($response); | ||
|
|
||
| $rawBody = AbstractParser::getBodyContents($response); |
There was a problem hiding this comment.
Could we prevent the double read here by using getSize() in the seekable case? At least for RestJsonParser, this method is also called in payload()
| //Unions must have at least one member set to a non-null value | ||
| // If the body is empty, we can assume it is unset | ||
| if (!empty($member['union']) && ($body->isSeekable() && !$body->getSize())) { | ||
| $rawBody = AbstractParser::getBodyContents($response); |
There was a problem hiding this comment.
I think this would be a third read if we're using rest-json
| 'StartLiveTail' => true | ||
| ]; | ||
|
|
||
| public function __construct(array $args) |
There was a problem hiding this comment.
We can probably do away with the constructor altogether, but not required
| // Not error occurred. Test successfully. | ||
| // Previously, on non-seekable streams it would have failed. | ||
| // Because, it would have tried to parse an empty string. | ||
| $this->assertTrue(true); |
There was a problem hiding this comment.
Could we strengthen the test here with some assertions on the parsed result?
| public static function getBodyContents(ResponseInterface $response): string | ||
| { | ||
| $body = $response->getBody(); | ||
| if ($body->isSeekable()) { | ||
| $body->rewind(); | ||
| } | ||
|
|
||
| return $body->getContents(); | ||
| } | ||
|
|
||
| public static function getResponseWithCachingStream( | ||
| ResponseInterface $response | ||
| ): ResponseInterface | ||
| { | ||
| if (!$response->getBody()->isSeekable()) { | ||
| return $response->withBody( | ||
| new CachingStream($response->getBody()) | ||
| ); | ||
| } | ||
|
|
||
| return $response; | ||
| } |
There was a problem hiding this comment.
These two methods should have test coverage
Issues #3124, #3018, #3181:
Description of changes:
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.