import os
import posixpath
import urlparse
import urllib
import logging
import BaseHTTPServer
import SimpleHTTPServer
[docs]class HTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
[docs] def do_GET(self):
"""
Serve a GET request.
"""
rg = self.parse_header_byte_range()
if rg:
f = self.send_head_range(rg[0], rg[1])
if f:
self.copyfile_range(f, self.wfile, rg[0], rg[1])
f.close()
else:
f = self.send_head()
if f:
self.copyfile(f, self.wfile)
f.close()
[docs] def parse_header_byte_range(self):
range_param = 'Range'
range_discard = 'bytes='
if self.headers.has_key(range_param):
rg = self.headers.get(range_param)
if rg.startswith(range_discard):
rg = rg[len(range_discard):]
begin, end = rg.split('-')
return (int(begin), int(end))
return None
[docs] def copyfile_range(self, source_file, output_file, range_begin, range_end):
"""
Copies a range of a file to destination.
"""
range_size = range_end - range_begin + 1
source_file.seek(range_begin)
buf = source_file.read(range_size)
output_file.write(buf)
[docs] def send_head_range(self, range_begin, range_end):
path = self.translate_path(self.path)
f = None
if os.path.isdir(path):
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(206, "Partial Content")
file_size = str(os.fstat(f.fileno())[6])
range_size = str(range_end - range_begin + 1)
self.send_header("Accept-Ranges", "bytes")
self.send_header("Content-Length", range_size)
self.send_header("Content-Range", "bytes %s-%s/%s" % (range_begin,
range_end,
file_size))
self.send_header("Content-type", ctype)
self.end_headers()
return f
[docs] def translate_path(self, path):
"""
Translate a /-separated PATH to the local filename syntax.
Components that mean special things to the local file system
(e.g. drive or directory names) are ignored. (XXX They should
probably be diagnosed.)
"""
# abandon query parameters
path = urlparse.urlparse(path)[2]
path = posixpath.normpath(urllib.unquote(path))
words = path.split('/')
words = filter(None, words)
path = self.server.cwd
for word in words:
_, word = os.path.splitdrive(word)
_, word = os.path.split(word)
if word in (os.curdir, os.pardir):
continue
path = os.path.join(path, word)
return path
[docs] def address_string(self):
'''
This HTTP server does not care about name resolution for the requests
The first reason is that most of the times our clients are going to be
virtual machines without a proper name resolution setup. Also, by not
resolving names, we should be a bit faster and be resilient about
misconfigured or resilient name servers.
'''
return self.client_address[0]
[docs] def log_message(self, fmt, *args):
logging.debug("builtin http server handling request from %s: %s" %
(self.address_string(), fmt % args))
[docs]def http_server(port=8000, cwd=None, terminate_callable=None):
http = BaseHTTPServer.HTTPServer(('', port), HTTPRequestHandler)
http.timeout = 1
if cwd is None:
cwd = os.getcwd()
http.cwd = cwd
while True:
if terminate_callable is not None:
terminate = terminate_callable()
else:
terminate = False
if terminate:
break
http.handle_request()
if __name__ == '__main__':
http_server()