diff --git a/moto/s3/responses.py b/moto/s3/responses.py index 1b19ef154..143ad0214 100644 --- a/moto/s3/responses.py +++ b/moto/s3/responses.py @@ -215,6 +215,28 @@ class ResponseObject(_TemplateEnvironmentMixin): return 200, headers, template.render(deleted=deleted_names, delete_errors=error_names) + def _handle_range_header(self, request, headers, response_content): + length = len(response_content) + last = length - 1 + _, rspec = request.headers.get('range').split('=') + if ',' in rspec: + raise NotImplementedError( + "Multiple range specifiers not supported") + toint = lambda i: int(i) if i else None + begin, end = map(toint, rspec.split('-')) + if begin is not None: # byte range + end = last if end is None else end + elif end is not None: # suffix byte range + begin = length - end + end = last + else: + return 400, headers, "" + if begin < 0 or end > length or begin > min(end, last): + return 416, headers, "" + headers['content-range'] = "bytes {0}-{1}/{2}".format( + begin, end, length) + return 206, headers, response_content[begin:end + 1] + def key_response(self, request, full_url, headers): try: response = self._key_response(request, full_url, headers) @@ -222,10 +244,14 @@ class ResponseObject(_TemplateEnvironmentMixin): response = s3error.code, headers, s3error.description if isinstance(response, six.string_types): - return 200, headers, response + status_code = 200 + response_content = response else: status_code, headers, response_content = response - return status_code, headers, response_content + + if status_code == 200 and 'range' in request.headers: + return self._handle_range_header(request, headers, response_content) + return status_code, headers, response_content def _key_response(self, request, full_url, headers): parsed_url = urlparse(full_url) diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py index 3e59a939a..2a14650e3 100644 --- a/tests/test_s3/test_s3.py +++ b/tests/test_s3/test_s3.py @@ -753,3 +753,21 @@ def test_bucket_location(): conn = boto.s3.connect_to_region("us-west-2") bucket = conn.create_bucket('mybucket') bucket.get_location().should.equal("us-west-2") + + +@mock_s3 +def test_ranged_get(): + conn = boto.connect_s3() + bucket = conn.create_bucket('mybucket') + key = Key(bucket) + key.key = 'bigkey' + rep = b"0123456789" + key.set_contents_from_string(rep * 10) + key.get_contents_as_string(headers={'Range': 'bytes=0-'}).should.equal(rep * 10) + key.get_contents_as_string(headers={'Range': 'bytes=0-99'}).should.equal(rep * 10) + key.get_contents_as_string(headers={'Range': 'bytes=0-0'}).should.equal(b'0') + key.get_contents_as_string(headers={'Range': 'bytes=99-99'}).should.equal(b'9') + key.get_contents_as_string(headers={'Range': 'bytes=50-54'}).should.equal(rep[:5]) + key.get_contents_as_string(headers={'Range': 'bytes=50-'}).should.equal(rep * 5) + key.get_contents_as_string(headers={'Range': 'bytes=-60'}).should.equal(rep * 6) + key.size.should.equal(100)