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

Set raw_body to None if no HTTP body was sent #506

Merged
merged 3 commits into from
Sep 1, 2017
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
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ Next Release (TBD)
* Fix issue where ``api_gateway_stage`` was being
ignored when set in the ``config.json`` file
(`#495 <https://github.com/aws/chalice/issues/495>`__)
* Fix bug where ``raw_body`` would raise an exception if no HTTP
body was provided
(`#503 <https://github.com/aws/chalice/issues/503>`__)


1.0.1
Expand Down
8 changes: 5 additions & 3 deletions chalice/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ def __init__(self, query_params, headers, uri_params, method, body,
#: only be set if the Content-Type header is application/json,
#: which is the default content type value in chalice.
self._json_body = None
self._raw_body = None
self._raw_body = b''
self.context = context
self.stage_vars = stage_vars

Expand All @@ -277,7 +277,7 @@ def _base64decode(self, encoded):

@property
def raw_body(self):
if self._raw_body is None:
if not self._raw_body and self._body is not None:
if self._is_base64_encoded:
self._raw_body = self._base64decode(self._body)
elif not isinstance(self._body, bytes):
Expand All @@ -294,7 +294,9 @@ def json_body(self):
return self._json_body

def to_dict(self):
copied = self.__dict__.copy()
# Don't copy internal attributes.
copied = {k: v for k, v in self.__dict__.items()
if not k.startswith('_')}
# We want the output of `to_dict()` to be
# JSON serializable, so we need to remove the CaseInsensitive dict.
copied['headers'] = dict(copied['headers'])
Expand Down
4 changes: 1 addition & 3 deletions chalice/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,7 @@ def create_lambda_event(self, method, path, headers, body=None):
'pathParameters': view_route.captured,
'stageVariables': {},
}
if body is None:
event['body'] = '{}'
elif self._is_binary(headers):
if self._is_binary(headers) and body is not None:
event['body'] = base64.b64encode(body).decode('ascii')
event['isBase64Encoded'] = True
else:
Expand Down
7 changes: 7 additions & 0 deletions tests/integration/test_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,13 @@ def test_can_use_shared_auth(smoke_test_app):
assert context['authorizer']['foo'] == 'bar'


def test_empty_raw_body(smoke_test_app):
url = smoke_test_app.url + '/repr-raw-body'
response = requests.post(url)
response.raise_for_status()
assert response.json() == {'repr-raw-body': ''}


@pytest.mark.on_redeploy
def test_redeploy_no_change_view(smoke_test_app):
smoke_test_app.redeploy_once()
Expand Down
5 changes: 5 additions & 0 deletions tests/integration/testapp/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,8 @@ def fake_profile_read_only():
methods=['POST'])
def fake_profile_post():
return {'success': True, 'context': app.current_request.context}


@app.route('/repr-raw-body', methods=['POST'])
def repr_raw_body():
return {'repr-raw-body': app.current_request.raw_body.decode('utf-8')}
26 changes: 25 additions & 1 deletion tests/unit/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def create_event(uri, method, path, content_type='application/json'):
},
'pathParameters': path,
'queryStringParameters': {},
'body': "",
'body': None,
'stageVariables': {},
}

Expand Down Expand Up @@ -243,6 +243,16 @@ def todict():
assert isinstance(json.loads(json.dumps(response)), dict)


def test_request_to_dict_does_not_contain_internal_attrs(sample_app):
@sample_app.route('/todict')
def todict():
return sample_app.current_request.to_dict()
event = create_event('/todict', 'GET', {})
response = json_response_body(sample_app(event, context=None))
internal_attrs = [key for key in response if key.startswith('_')]
assert not internal_attrs


def test_will_pass_captured_params_to_view(sample_app):
event = create_event('/name/{name}', 'GET', {'name': 'james'})
response = sample_app(event, context=None)
Expand Down Expand Up @@ -1278,3 +1288,17 @@ def test_debug_mode_changes_log_level():
test_app.debug = True
assert test_app.debug is True
assert test_app.log.getEffectiveLevel() == logging.DEBUG


def test_raw_body_is_none_if_body_is_none():
misc_kwargs = {
'query_params': {},
'headers': {},
'uri_params': {},
'method': 'GET',
'context': {},
'stage_vars': {},
'is_base64_encoded': False,
}
request = app.Request(body=None, **misc_kwargs)
assert request.raw_body == b''
2 changes: 1 addition & 1 deletion tests/unit/test_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ def test_can_create_lambda_event():
'headers': {'content-type': 'application/json'},
'pathParameters': {'capture': 'other'},
'queryStringParameters': {},
'body': '{}',
'body': None,
'stageVariables': {},
}

Expand Down