Skip to content

Commit

Permalink
Fix regression when handling 200 error responses from S3
Browse files Browse the repository at this point in the history
We now do not try to parse the error response unless it's an error
HTTP status code.  This means the handler needs to be updated such
that _it_ is the one that does the Error parsing.  Fortunately, we
don't need to fully parse the XML error response.  We just need to
know that the top level key is an Error node before saying we
need to retry the request.
  • Loading branch information
jamesls committed Oct 7, 2014
1 parent 4bf1208 commit 6bb8f35
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 22 deletions.
23 changes: 17 additions & 6 deletions botocore/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@



def check_for_200_error(response, operation, **kwargs):
def check_for_200_error(response, **kwargs):
# From: http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectCOPY.html
# There are two opportunities for a copy request to return an error. One
# can occur when Amazon S3 receives the copy request and the other can
Expand All @@ -60,12 +60,23 @@ def check_for_200_error(response, operation, **kwargs):
# trying to retrieve the response. See Endpoint._get_response().
return
http_response, parsed = response
if _looks_like_special_case_error(http_response):
logger.debug("Error found for response with 200 status code, "
"errors: %s, changing status code to "
"500.", parsed)
http_response.status_code = 500


def _looks_like_special_case_error(http_response):
if http_response.status_code == 200:
if 'Errors' in parsed:
logger.debug("Error found for response with 200 status code, "
"operation: %s, errors: %s, changing status code to "
"500.", operation, parsed)
http_response.status_code = 500
parser = xml.etree.cElementTree.XMLParser(
target=xml.etree.cElementTree.TreeBuilder(),
encoding='utf-8')
parser.feed(http_response.content)
root = parser.close()
if root.tag == 'Error':
return True
return False


def decode_console_output(parsed, **kwargs):
Expand Down
28 changes: 12 additions & 16 deletions tests/unit/test_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,33 +119,29 @@ def test_destination_region_left_untouched(self):
def test_500_status_code_set_for_200_response(self):
http_response = mock.Mock()
http_response.status_code = 200
parsed = {
'Errors': [{
"HostId": "hostid",
"Message": "An internal error occurred.",
"Code": "InternalError",
"RequestId": "123456789"
}]
}
handlers.check_for_200_error((http_response, parsed), 'MyOperationName')
http_response.content = """
<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<RequestId>id</RequestId>
<HostId>hostid</HostId>
</Error>
"""
handlers.check_for_200_error((http_response, {}))
self.assertEqual(http_response.status_code, 500)

def test_200_response_with_no_error_left_untouched(self):
http_response = mock.Mock()
http_response.status_code = 200
parsed = {
'NotAnError': [{
'foo': 'bar'
}]
}
handlers.check_for_200_error((http_response, parsed), 'MyOperationName')
http_response.content = "<NotAnError></NotAnError>"
handlers.check_for_200_error((http_response, {}))
# We don't touch the status code since there are no errors present.
self.assertEqual(http_response.status_code, 200)

def test_500_response_can_be_none(self):
# A 500 response can raise an exception, which means the response
# object is None. We need to handle this case.
handlers.check_for_200_error(None, mock.Mock())
handlers.check_for_200_error(None)

def test_sse_headers(self):
prefix = 'x-amz-server-side-encryption-customer-'
Expand Down

0 comments on commit 6bb8f35

Please sign in to comment.