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

Handle the case where we receive a generic html error response #850

Merged
merged 2 commits into from
Mar 24, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion botocore/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,10 @@ def parse(self, response, shape):
LOG.debug('Response headers: %s', response['headers'])
LOG.debug('Response body:\n%s', response['body'])
if response['status_code'] >= 301:
parsed = self._do_error_parse(response, shape)
if self._is_generic_error_response(response):
parsed = self._do_generic_error_parse(response)
else:
parsed = self._do_error_parse(response, shape)
else:
parsed = self._do_parse(response, shape)
# Inject HTTPStatusCode key in the response metadata if the
Expand All @@ -213,6 +216,33 @@ def parse(self, response, shape):
response['status_code'])
return parsed

def _is_generic_error_response(self, response):
# There are times when a service will respond with a generic
# error response such as:
# '<html><body><b>Http/1.1 Service Unavailable</b></body></html>'
#
# This can also happen if you're going through a proxy.
# In this case the protocol specific _do_error_parse will either
# fail to parse the response (in the best case) or silently succeed
# and treat the HTML above as an XML response and return
# non sensical parsed data.
# To prevent this case from happening we first need to check
# whether or not this response looks like the generic response.
if response['status_code'] >= 500:
return response['body'].strip().startswith(b'<html>')

def _do_generic_error_parse(self, response):
# There's not really much we can do when we get a generic
# html response.
LOG.debug("Received a non protocol specific error response from the "
"service, unable to populate error code and message.")
return {
'Error': {'Code': str(response['status_code']),
'Message': six.moves.http_client.responses.get(
response['status_code'], '')},
'ResponseMetadata': {},
}

def _do_parse(self, response, shape):
raise NotImplementedError("%s._do_parse" % self.__class__.__name__)

Expand Down
4 changes: 2 additions & 2 deletions tests/functional/test_s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def test_multiple_transitions_returns_one(self):
' </NoncurrentVersionTransition>'
' </Rule>'
'</LifecycleConfiguration>'
)
).encode('utf-8')
http_response.headers = {}
self.http_session_send_mock.return_value = http_response
s3 = self.session.create_client('s3')
Expand Down Expand Up @@ -115,7 +115,7 @@ def test_500_error_with_non_xml_body(self):
'ETag: "a6d856bc171fc6aa1b236680856094e2"\r\n'
'Content-Length: 0\r\n'
'Server: AmazonS3\r\n'
)
).encode('utf-8')
http_500_response = mock.Mock()
http_500_response.status_code = 500
http_500_response.content = non_xml_content
Expand Down
23 changes: 23 additions & 0 deletions tests/unit/test_parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import datetime

from dateutil.tz import tzutc
from nose.tools import assert_equal

from botocore import parsers
from botocore import model
Expand Down Expand Up @@ -635,3 +636,25 @@ def test_can_parse_route53_with_missing_message(self):
# Even though there's no <Message /> we should
# still populate an empty string.
self.assertEqual(error['Message'], '')


def test_can_handle_generic_error_message():
# There are times when you can get a service to respond with a generic
# html error page. We should be able to handle this case.
for parser_cls in parsers.PROTOCOL_PARSERS.values():
yield _assert_parses_generic_error, parser_cls()


def _assert_parses_generic_error(parser):
# There are times when you can get a service to respond with a generic
# html error page. We should be able to handle this case.
body = (
'<html><body><b>Http/1.1 Service Unavailable</b></body></html>'
).encode('utf-8')
parsed = parser.parse({
'body': body, 'headers': {}, 'status_code': 503}, None)
assert_equal(
parsed,
{'Error': {'Code': '503', 'Message': 'Service Unavailable'},
'ResponseMetadata': {'HTTPStatusCode': 503}}
)