# adopted from https://github.com/danvk/RangeHTTPServer to allow CORS import os import re try: from http.server import SimpleHTTPRequestHandler except ImportError: 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 else: 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 if self.range else 0, None # 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', 'text/html') 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 end_headers(self): self.send_header('Access-Control-Allow-Origin', '*') return SimpleHTTPRequestHandler.end_headers(self) 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) #class CORSHTTPRequestHandler(RangeHTTPServer.SimpleHTTPRequestHandler): # def send_head(self): # """Common code for GET and HEAD commands. # # This sends the response code and MIME headers. # # Return value is either a file object (which has to be copied # to the outputfile by the caller unless the command was HEAD, # and must be closed by the caller under all circumstances), or # None, in which case the caller has nothing further to do. # # """ # path = self.translate_path(self.path) # f = None # if os.path.isdir(path): # if not self.path.endswith('/'): # # redirect browser - doing basically what apache does # self.send_response(301) # self.send_header("Location", self.path + "/") # self.end_headers() # return None # for index in "index.html", "index.htm": # index = os.path.join(path, index) # if os.path.exists(index): # path = index # break # else: # return self.list_directory(path) # ctype = self.guess_type(path) # try: # # Always read in binary mode. Opening files in text mode may cause # # newline translations, making the actual size of the content # # transmitted *less* than the content-length! # f = open(path, 'rb') # except IOError: # self.send_error(404, "File not found") # return None # self.send_response(200) # self.send_header("Content-type", ctype) # fs = os.fstat(f.fileno()) # self.send_header("Content-Length", str(fs[6])) # self.send_header("Last-Modified", self.date_time_string(fs.st_mtime)) # self.send_header("Access-Control-Allow-Origin", "*") # self.end_headers() # return f if __name__ == "__main__": import sys try: import http.server as SimpleHTTPServer import socketserver as SocketServer except ImportError: import SimpleHTTPServer import SocketServer PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8000 Handler = RangeRequestHandler httpd = SocketServer.TCPServer(("", PORT), Handler) print("serving at port", PORT) httpd.serve_forever()