mirror of
https://github.com/rasterio/rasterio.git
synced 2025-12-08 17:36:12 +00:00
* Raise WarpOperationError (new) if ChunkAndWarpMulti/Image fail We've been hiding these errors and allowing chunks of a warp operation to pass with no data copied to the output. Note that the error message isn't very detailed. In a lot of cases it's a generic "TIFF read block error" and doesn't surface root causes like server errors. * Temporarily home in on one test * Make 503 more predictable The exact request range varies a little bit with GDAL version * Skip test on pythons < 3.7
115 lines
3.6 KiB
Python
115 lines
3.6 KiB
Python
#!/usr/bin/python
|
|
'''
|
|
Use this in the same way as Python's SimpleHTTPServer:
|
|
|
|
python -m RangeHTTPServer [port]
|
|
|
|
The only difference from SimpleHTTPServer is that RangeHTTPServer supports
|
|
'Range:' headers to load portions of files. This is helpful for doing local web
|
|
development with genomic data files, which tend to be to large to load into the
|
|
browser all at once.
|
|
'''
|
|
|
|
import os
|
|
import re
|
|
|
|
try:
|
|
# Python3
|
|
from http.server import SimpleHTTPRequestHandler
|
|
|
|
except ImportError:
|
|
# Python 2
|
|
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
|
|
|
|
|
def copy_byte_range(infile, outfile, start=None, stop=None, bufsize=16*1024):
|
|
'''Like shutil.copyfileobj, but only copy a range of the streams.
|
|
|
|
Both start and stop are inclusive.
|
|
'''
|
|
if start is not None: infile.seek(start)
|
|
while 1:
|
|
to_read = min(bufsize, stop + 1 - infile.tell() if stop else bufsize)
|
|
buf = infile.read(to_read)
|
|
if not buf:
|
|
break
|
|
outfile.write(buf)
|
|
|
|
|
|
BYTE_RANGE_RE = re.compile(r'bytes=(\d+)-(\d+)?$')
|
|
def parse_byte_range(byte_range):
|
|
'''Returns the two numbers in 'bytes=123-456' or throws ValueError.
|
|
|
|
The last number or both numbers may be None.
|
|
'''
|
|
if byte_range.strip() == '':
|
|
return None, None
|
|
|
|
m = BYTE_RANGE_RE.match(byte_range)
|
|
if not m:
|
|
raise ValueError('Invalid byte range %s' % byte_range)
|
|
|
|
first, last = [x and int(x) for x in m.groups()]
|
|
if last and last < first:
|
|
raise ValueError('Invalid byte range %s' % byte_range)
|
|
return first, last
|
|
|
|
|
|
class RangeRequestHandler(SimpleHTTPRequestHandler):
|
|
"""Adds support for HTTP 'Range' requests to SimpleHTTPRequestHandler
|
|
|
|
The approach is to:
|
|
- Override send_head to look for 'Range' and respond appropriately.
|
|
- Override copyfile to only transmit a range when requested.
|
|
"""
|
|
def send_head(self):
|
|
if 'Range' not in self.headers:
|
|
self.range = None
|
|
return SimpleHTTPRequestHandler.send_head(self)
|
|
try:
|
|
self.range = parse_byte_range(self.headers['Range'])
|
|
except ValueError as e:
|
|
self.send_error(400, 'Invalid byte range')
|
|
return None
|
|
first, last = self.range
|
|
|
|
# Mirroring SimpleHTTPServer.py here
|
|
path = self.translate_path(self.path)
|
|
f = None
|
|
ctype = self.guess_type(path)
|
|
try:
|
|
f = open(path, 'rb')
|
|
except IOError:
|
|
self.send_error(404, 'File not found')
|
|
return None
|
|
|
|
fs = os.fstat(f.fileno())
|
|
file_len = fs[6]
|
|
if first >= file_len:
|
|
self.send_error(416, 'Requested Range Not Satisfiable')
|
|
return None
|
|
|
|
self.send_response(206)
|
|
self.send_header('Content-type', ctype)
|
|
self.send_header('Accept-Ranges', 'bytes')
|
|
|
|
if last is None or last >= file_len:
|
|
last = file_len - 1
|
|
response_length = last - first + 1
|
|
|
|
self.send_header('Content-Range',
|
|
'bytes %s-%s/%s' % (first, last, file_len))
|
|
self.send_header('Content-Length', str(response_length))
|
|
self.send_header('Last-Modified', self.date_time_string(fs.st_mtime))
|
|
self.end_headers()
|
|
return f
|
|
|
|
def copyfile(self, source, outputfile):
|
|
if not self.range:
|
|
return SimpleHTTPRequestHandler.copyfile(self, source, outputfile)
|
|
|
|
# SimpleHTTPRequestHandler uses shutil.copyfileobj, which doesn't let
|
|
# you stop the copying before the end of the file.
|
|
start, stop = self.range # set in send_head()
|
|
copy_byte_range(source, outputfile, start, stop)
|