462
466
method = getattr(self, mname)
469
_range_regexp = re.compile(r'^(?P<start>\d+)-(?P<end>\d+)$')
470
_tail_regexp = re.compile(r'^-(?P<tail>\d+)$')
472
def parse_ranges(self, ranges_header):
473
"""Parse the range header value and returns ranges and tail"""
476
assert ranges_header.startswith('bytes=')
477
ranges_header = ranges_header[len('bytes='):]
478
for range_str in ranges_header.split(','):
479
range_match = self._range_regexp.match(range_str)
480
if range_match is not None:
481
ranges.append((int(range_match.group('start')),
482
int(range_match.group('end'))))
484
tail_match = self._tail_regexp.match(range_str)
485
if tail_match is not None:
486
tail = int(tail_match.group('tail'))
489
def send_range_content(self, file, start, length):
491
self.wfile.write(file.read(length))
493
def get_single_range(self, file, file_size, start, end):
494
self.send_response(206)
495
length = end - start + 1
496
self.send_header('Accept-Ranges', 'bytes')
497
self.send_header("Content-Length", "%d" % length)
499
self.send_header("Content-type", 'application/octet-stream')
500
self.send_header("Content-Range", "bytes %d-%d/%d" % (start,
504
self.send_range_content(file, start, length)
506
def get_multiple_ranges(self, file, file_size, ranges):
507
self.send_response(206)
508
self.send_header('Accept-Ranges', 'bytes')
509
boundary = "%d" % random.randint(0,0x7FFFFFFF)
510
self.send_header("Content-Type",
511
"multipart/byteranges; boundary=%s" % boundary)
513
for (start, end) in ranges:
514
self.wfile.write("--%s\r\n" % boundary)
515
self.send_header("Content-type", 'application/octet-stream')
516
self.send_header("Content-Range", "bytes %d-%d/%d" % (start,
520
self.send_range_content(file, start, end - start + 1)
521
self.wfile.write("--%s\r\n" % boundary)
525
"""Serve a GET request.
527
Handles the Range header.
530
path = self.translate_path(self.path)
531
ranges_header_value = self.headers.get('Range')
532
if ranges_header_value is None or os.path.isdir(path):
533
# Let the mother class handle most cases
534
return SimpleHTTPRequestHandler.do_GET(self)
537
# Always read in binary mode. Opening files in text
538
# mode may cause newline translations, making the
539
# actual size of the content transmitted *less* than
540
# the content-length!
541
file = open(path, 'rb')
543
self.send_error(404, "File not found")
546
file_size = os.fstat(file.fileno())[6]
547
tail, ranges = self.parse_ranges(ranges_header_value)
548
# Normalize tail into ranges
550
ranges.append((file_size - tail, file_size))
556
for (start, end) in ranges:
557
if start >= file_size or end >= file_size:
561
# RFC2616 14-16 says that invalid Range headers
562
# should be ignored and in that case, the whole file
563
# should be returned as if no Range header was
565
file.close() # Will be reopened by the following call
566
return SimpleHTTPRequestHandler.do_GET(self)
569
(start, end) = ranges[0]
570
self.get_single_range(file, file_size, start, end)
572
self.get_multiple_ranges(file, file_size, ranges)
465
575
if sys.platform == 'win32':
466
576
# On win32 you cannot access non-ascii filenames without
467
577
# decoding them into unicode first.