9797import mimetypes
9898import os
9999import posixpath
100+ import re
100101import select
101102import shutil
102103import socket # For gethostbyaddr()
@@ -682,7 +683,7 @@ def do_GET(self):
682683 f = self .send_head ()
683684 if f :
684685 try :
685- self .copyfile (f , self .wfile )
686+ self .copyfile (f , self .wfile , range = self . range )
686687 finally :
687688 f .close ()
688689
@@ -705,6 +706,7 @@ def send_head(self):
705706 """
706707 path = self .translate_path (self .path )
707708 f = None
709+ self .range = self .get_range ()
708710 if os .path .isdir (path ):
709711 parts = urllib .parse .urlsplit (self .path )
710712 if not parts .path .endswith ('/' ):
@@ -769,9 +771,26 @@ def send_head(self):
769771 f .close ()
770772 return None
771773
772- self .send_response (HTTPStatus .OK )
774+ if self .range :
775+ start , end = self .range
776+ if start >= fs .st_size :
777+ # 416 REQUESTED_RANGE_NOT_SATISFIABLE means that none of the range values overlap the extent of the resource
778+ f .close ()
779+ self .send_error (HTTPStatus .REQUESTED_RANGE_NOT_SATISFIABLE )
780+ return None
781+ if end is None or end >= fs .st_size :
782+ end = fs .st_size - 1
783+ self .send_response (HTTPStatus .PARTIAL_CONTENT )
784+ self .send_header ("Content-Range" , "bytes %s-%s/%s" % (start , end , fs .st_size ))
785+ self .send_header ("Content-Length" , str (end - start + 1 ))
786+
787+ # Update range to be sent to be used later in copyfile
788+ self .range = (start , end )
789+ else :
790+ self .send_response (HTTPStatus .OK )
791+ self .send_header ("Accept-Ranges" , "bytes" )
792+ self .send_header ("Content-Length" , str (fs [6 ]))
773793 self .send_header ("Content-type" , ctype )
774- self .send_header ("Content-Length" , str (fs [6 ]))
775794 self .send_header ("Last-Modified" ,
776795 self .date_time_string (fs .st_mtime ))
777796 self .end_headers ()
@@ -868,21 +887,37 @@ def translate_path(self, path):
868887 path += '/'
869888 return path
870889
871- def copyfile (self , source , outputfile ):
872- """Copy all data between two file objects.
890+ def copyfile (self , source , outputfile , range = None ):
891+ """Copy all data between two file objects if range is None.
892+ Otherwise, copy data between two file objects based on the
893+ inclusive range (start, end).
873894
874895 The SOURCE argument is a file object open for reading
875- (or anything with a read() method) and the DESTINATION
876- argument is a file object open for writing (or
877- anything with a write() method).
896+ (or anything with read() and seek() method) and the
897+ DESTINATION argument is a file object open for writing
898+ (or anything with a write() method).
878899
879900 The only reason for overriding this would be to change
880901 the block size or perhaps to replace newlines by CRLF
881902 -- note however that this the default server uses this
882903 to copy binary data as well.
883904
884905 """
885- shutil .copyfileobj (source , outputfile )
906+ if range is None :
907+ shutil .copyfileobj (source , outputfile )
908+ else :
909+ start , end = range
910+ length = end - start + 1
911+ source .seek (start )
912+ while True :
913+ if length <= 0 :
914+ break
915+ buf = source .read (min (length , shutil .COPY_BUFSIZE ))
916+ if not buf :
917+ break
918+ length -= len (buf )
919+ outputfile .write (buf )
920+
886921
887922 def guess_type (self , path ):
888923 """Guess the type of a file.
@@ -909,6 +944,24 @@ def guess_type(self, path):
909944 return guess
910945 return 'application/octet-stream'
911946
947+ def get_range (self ):
948+ """Return a tuple of (start, end) representing the range header in
949+ the HTTP request. If the range header is missing or not resolvable,
950+ None is returned. This only supports single part ranges.
951+
952+ """
953+ range_header = self .headers .get ('range' )
954+ if not range_header :
955+ return None
956+ m = re .match (r'bytes=(\d+)-(\d*)$' , range_header )
957+ if not m :
958+ return None
959+ start = m .group (1 )
960+ if not m .group (2 ):
961+ return int (start ), None
962+ end = m .group (2 )
963+ return int (start ), int (end )
964+
912965
913966# Utilities for CGIHTTPRequestHandler
914967
0 commit comments