Provide for HTTP Range requests in Glance API, and return correct 206
Partial Content.
See community bugs:
https://bugs.launchpad.net/glance/+bug/1399851
https://bugs.launchpad.net/glance/+bug/1417069
--- glance-2015.1.2/glance/api/v2/image_data.py.~1~ 2015-10-13 09:38:23.000000000 -0700
+++ glance-2015.1.2/glance/api/v2/image_data.py 2016-01-19 12:23:11.296863244 -0800
@@ -211,6 +211,8 @@ class ResponseSerializer(wsgi.JSONRespon
def download(self, response, image):
offset, chunk_size = 0, None
+
+ # Initially attempt to get "Content-Range" request
range_val = response.request.get_content_range()
if range_val:
@@ -222,6 +224,21 @@ class ResponseSerializer(wsgi.JSONRespon
if range_val.stop is not None:
chunk_size = range_val.stop - offset
+ # Return 206 Partial Content
+ response.status_int = 206
+ else:
+ # Try for "Range" request header if ContentRange not present
+ range_obj = response.request.get_range()
+ if range_obj:
+ if range_obj.start is not None:
+ offset = range_obj.start
+
+ if range_obj.end is not None:
+ chunk_size = range_obj.end - offset
+
+ # Return 206 Partial Content
+ response.status_int = 206
+
response.headers['Content-Type'] = 'application/octet-stream'
try:
@@ -246,7 +263,9 @@ class ResponseSerializer(wsgi.JSONRespon
response.headers['Content-MD5'] = image.checksum
# NOTE(markwash): "response.app_iter = ..." also erroneously resets the
# content-length
- response.headers['Content-Length'] = str(image.size)
+ # NOTE(mattk): Should be set to chunk_size or image.size
+ response.headers['Content-Length'] = \
+ str(chunk_size) if chunk_size != 0 else str(image.size)
def upload(self, response, result):
response.status_int = 204
--- glance-2015.1.2/glance/common/wsgi.py.~1~ 2015-10-13 09:38:23.000000000 -0700
+++ glance-2015.1.2/glance/common/wsgi.py 2016-01-19 12:23:11.297682604 -0800
@@ -752,7 +752,7 @@ class Request(webob.Request):
return self.accept_language.best_match(langs)
def get_content_range(self):
- """Return the `Range` in a request."""
+ """Return the `Content-Range` in a request."""
range_str = self.headers.get('Content-Range')
if range_str is not None:
range_ = webob.byterange.ContentRange.parse(range_str)
@@ -761,6 +761,16 @@ class Request(webob.Request):
raise webob.exc.HTTPBadRequest(explanation=msg)
return range_
+ def get_range(self):
+ """Return the 'Range' in a reqyest."""
+ range_str = self.headers.get('Range')
+ if range_str is not None:
+ range_ = webob.byterange.Range.parse(range_str)
+ if range_ is None:
+ msg = _('Malformed Range header: %s') % range_str
+ raise webob.exc.HTTPBadRequest(explanation=msg)
+ return range_
+
class JSONRequestDeserializer(object):
valid_transfer_encoding = frozenset(['chunked', 'compress', 'deflate',