Skip to content

Commit

Permalink
Added support for basic (not multipart) range requests for static files.
Browse files Browse the repository at this point in the history
  • Loading branch information
eriq-augustine committed Nov 16, 2024
1 parent 1b2dc38 commit e85181d
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 5 deletions.
64 changes: 60 additions & 4 deletions sharkclipper/api/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def static(handler, path, **kwargs):
# Build the static path skipping the '/static' part of the URL path.
static_path = os.path.join(STATIC_DIR, *parts[2:])

return _serve_file(static_path, "static path not found: '%s'." % (path))
return _serve_file(static_path, "static path not found: '%s'." % (path), **kwargs)

# Get a temp file that we create/work with.
def temp(handler, path, temp_dir = None, **kwargs):
Expand All @@ -51,7 +51,7 @@ def temp(handler, path, temp_dir = None, **kwargs):
# Build the temp path skipping the '/temp' part of the URL path.
temp_path = os.path.join(temp_dir, *parts[2:])

return _serve_file(temp_path, "temp path not found: '%s'." % (path))
return _serve_file(temp_path, "temp path not found: '%s'." % (path), **kwargs)

# Get the server version.
def version(handler, path, **kwargs):
Expand Down Expand Up @@ -163,7 +163,7 @@ def _get_exif_timestamp(time):
# Format.
return time.strftime(EXIF_DATETIME_FORMAT)

def _serve_file(path, not_found_message = None):
def _serve_file(path, not_found_message = None, range_header = None, **kwargs):
if (not os.path.isfile(path)):
if (not_found_message is None):
not_found_message = "path not found: '%s'." % (path)
Expand All @@ -178,4 +178,60 @@ def _serve_file(path, not_found_message = None):
if (mime_info is not None):
headers['Content-Type'] = mime_info[0]

return data, None, headers
code = http.HTTPStatus.OK

data_length = len(data)
range_parts = _parseRange(range_header, data_length)
if (range_parts is not None):
data = data[range_parts[0]:(range_parts[1] + 1)]
code = http.HTTPStatus.PARTIAL_CONTENT
headers['Content-Range'] = "bytes %d-%d/%d" % (range_parts[0], range_parts[1], data_length)

return data, code, headers

# Parse the range header.
# Return None if a single byte range could not be parsed (multi-ranges are not supported),
# else return [startByteIndex, endByteIndex] (inclusive).
# Note that the inclusivity means that these are not the same as a Python list slice
# (you will need to add one to the end index).
# If not present, startByteIndex will be defaulted to 0 and endByteIndex will be data_length.
def _parseRange(raw_range_header, data_length):
try:
return _parseRangeHelper(raw_range_header, data_length)
except Exception as ex:
logging.warn("Cannot parse range header '%s': '%s'." % (raw_range_header, ex))
return None

def _parseRangeHelper(raw_range_header, data_length):
if (raw_range_header is None):
return None

text = str(raw_range_header).strip().lower()

parts = [part.strip() for part in text.split('=')]
if (len(parts) != 2):
raise ValueError('Bad unit split.')

units, ranges = parts
if (units != 'bytes'):
raise ValueError('Units is not bytes.')

parts = [part.strip() for part in ranges.split(',')]
if (len(parts) != 1):
raise ValueError('Multipart ranges not supported.')

parts = [part.strip() for part in parts[0].split('-')]
if (len(parts) != 2):
raise ValueError('Range does not have exactly two components.')

start = parts[0]
if (start == ''):
start = 0
start = int(start)

end = parts[1]
if (end == ''):
end = (data_length - 1)
end = int(end)

return [start, end]
4 changes: 3 additions & 1 deletion sharkclipper/api/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,12 @@ def _read_post_file(self):
def _do_request(self, **kwargs):
logging.debug("Serving: " + self.path)

range_header = self.headers.get('Range', None)

code = http.HTTPStatus.OK
headers = {}

result = self._route(self.path, **kwargs)
result = self._route(self.path, range_header = range_header, **kwargs)
if (result is None):
# All handling was done internally, the response is complete.
return
Expand Down

0 comments on commit e85181d

Please sign in to comment.