8080# (Actually, the latter is only true if you know the server configuration
8181# at the time the request was made!)
8282
83- __version__ = "0.6 "
83+ __version__ = "0.7 "
8484
8585__all__ = [
8686 "HTTPServer" , "ThreadingHTTPServer" , "BaseHTTPRequestHandler" ,
8787 "SimpleHTTPRequestHandler" , "CGIHTTPRequestHandler" ,
8888]
8989
90+ import re
91+
9092import copy
9193import datetime
9294import email .utils
129131
130132DEFAULT_ERROR_CONTENT_TYPE = "text/html;charset=utf-8"
131133
134+
135+ HTTP_BYTES_RANGE_HEADER = re .compile (r"bytes=(?P<first>\d+)-(?P<last>\d+)$" )
136+
137+
132138class HTTPServer (socketserver .TCPServer ):
133139
134140 allow_reuse_address = 1 # Seems to make sense in testing environment
@@ -657,7 +663,12 @@ def do_GET(self):
657663 f = self .send_head ()
658664 if f :
659665 try :
660- self .copyfile (f , self .wfile )
666+ if "Range" in self .headers :
667+ res = HTTP_BYTES_RANGE_HEADER .match (string = self .headers .get ("Range" ))
668+ if res is not None :
669+ self .copyfile (f , self .wfile , int (res .group ("first" )), int (res .group ("last" )))
670+ else :
671+ self .copyfile (f , self .wfile )
661672 finally :
662673 f .close ()
663674
@@ -742,10 +753,22 @@ def send_head(self):
742753 self .end_headers ()
743754 f .close ()
744755 return None
745-
746- self .send_response (HTTPStatus .OK )
756+ if "Range" in self .headers :
757+ res = HTTP_BYTES_RANGE_HEADER .match (string = self .headers ["Range" ])
758+ if res is None :
759+ self .send_error (code = HTTPStatus .REQUESTED_RANGE_NOT_SATISFIABLE ,
760+ message = "Range header is not a valid single part ranges" )
761+ self .end_headers ()
762+ f .close ()
763+ return None
764+ self .send_response (HTTPStatus .PARTIAL_CONTENT )
765+ self .send_header ("Content-Range" , f"{ self .headers ['Range' ]} /{ fs [6 ]} " )
766+ self .send_header ("Content-Length" , int (res .group ("last" ))- int (res .group ("first" )))
767+ else :
768+ self .send_response (HTTPStatus .OK )
769+ self .send_header ("Accept-Ranges" , "bytes" )
770+ self .send_header ("Content-Length" , str (fs [6 ]))
747771 self .send_header ("Content-type" , ctype )
748- self .send_header ("Content-Length" , str (fs [6 ]))
749772 self .send_header ("Last-Modified" ,
750773 self .date_time_string (fs .st_mtime ))
751774 self .end_headers ()
@@ -842,8 +865,10 @@ def translate_path(self, path):
842865 path += '/'
843866 return path
844867
845- def copyfile (self , source , outputfile ):
846- """Copy all data between two file objects.
868+ def copyfile (self , source , outputfile , start_byte = None , end_byte = None ):
869+ """Copy all data between two file objects if start_byte and end_byte are None.
870+
871+ Otherwise, copy (end_byte-start_byte) bytes data between two file objects.
847872
848873 The SOURCE argument is a file object open for reading
849874 (or anything with a read() method) and the DESTINATION
@@ -856,7 +881,11 @@ def copyfile(self, source, outputfile):
856881 to copy binary data as well.
857882
858883 """
859- shutil .copyfileobj (source , outputfile )
884+ if start_byte is not None and end_byte is not None :
885+ source .seek (start_byte )
886+ outputfile .write (source .read (end_byte ))
887+ else :
888+ shutil .copyfileobj (source , outputfile )
860889
861890 def guess_type (self , path ):
862891 """Guess the type of a file.
0 commit comments