Home | History | Annotate | Download | only in Lib
      1 """Simple HTTP Server.
      2 
      3 This module builds on BaseHTTPServer by implementing the standard GET
      4 and HEAD requests in a fairly straightforward manner.
      5 
      6 """
      7 
      8 
      9 __version__ = "0.6"
     10 
     11 __all__ = ["SimpleHTTPRequestHandler"]
     12 
     13 import os
     14 import posixpath
     15 import BaseHTTPServer
     16 import urllib
     17 import urlparse
     18 import cgi
     19 import sys
     20 import shutil
     21 import mimetypes
     22 try:
     23     from cStringIO import StringIO
     24 except ImportError:
     25     from StringIO import StringIO
     26 
     27 
     28 class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
     29 
     30     """Simple HTTP request handler with GET and HEAD commands.
     31 
     32     This serves files from the current directory and any of its
     33     subdirectories.  The MIME type for files is determined by
     34     calling the .guess_type() method.
     35 
     36     The GET and HEAD requests are identical except that the HEAD
     37     request omits the actual contents of the file.
     38 
     39     """
     40 
     41     server_version = "SimpleHTTP/" + __version__
     42 
     43     def do_GET(self):
     44         """Serve a GET request."""
     45         f = self.send_head()
     46         if f:
     47             try:
     48                 self.copyfile(f, self.wfile)
     49             finally:
     50                 f.close()
     51 
     52     def do_HEAD(self):
     53         """Serve a HEAD request."""
     54         f = self.send_head()
     55         if f:
     56             f.close()
     57 
     58     def send_head(self):
     59         """Common code for GET and HEAD commands.
     60 
     61         This sends the response code and MIME headers.
     62 
     63         Return value is either a file object (which has to be copied
     64         to the outputfile by the caller unless the command was HEAD,
     65         and must be closed by the caller under all circumstances), or
     66         None, in which case the caller has nothing further to do.
     67 
     68         """
     69         path = self.translate_path(self.path)
     70         f = None
     71         if os.path.isdir(path):
     72             parts = urlparse.urlsplit(self.path)
     73             if not parts.path.endswith('/'):
     74                 # redirect browser - doing basically what apache does
     75                 self.send_response(301)
     76                 new_parts = (parts[0], parts[1], parts[2] + '/',
     77                              parts[3], parts[4])
     78                 new_url = urlparse.urlunsplit(new_parts)
     79                 self.send_header("Location", new_url)
     80                 self.end_headers()
     81                 return None
     82             for index in "index.html", "index.htm":
     83                 index = os.path.join(path, index)
     84                 if os.path.exists(index):
     85                     path = index
     86                     break
     87             else:
     88                 return self.list_directory(path)
     89         ctype = self.guess_type(path)
     90         try:
     91             # Always read in binary mode. Opening files in text mode may cause
     92             # newline translations, making the actual size of the content
     93             # transmitted *less* than the content-length!
     94             f = open(path, 'rb')
     95         except IOError:
     96             self.send_error(404, "File not found")
     97             return None
     98         try:
     99             self.send_response(200)
    100             self.send_header("Content-type", ctype)
    101             fs = os.fstat(f.fileno())
    102             self.send_header("Content-Length", str(fs[6]))
    103             self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
    104             self.end_headers()
    105             return f
    106         except:
    107             f.close()
    108             raise
    109 
    110     def list_directory(self, path):
    111         """Helper to produce a directory listing (absent index.html).
    112 
    113         Return value is either a file object, or None (indicating an
    114         error).  In either case, the headers are sent, making the
    115         interface the same as for send_head().
    116 
    117         """
    118         try:
    119             list = os.listdir(path)
    120         except os.error:
    121             self.send_error(404, "No permission to list directory")
    122             return None
    123         list.sort(key=lambda a: a.lower())
    124         f = StringIO()
    125         displaypath = cgi.escape(urllib.unquote(self.path))
    126         f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
    127         f.write("<html>\n<title>Directory listing for %s</title>\n" % displaypath)
    128         f.write("<body>\n<h2>Directory listing for %s</h2>\n" % displaypath)
    129         f.write("<hr>\n<ul>\n")
    130         for name in list:
    131             fullname = os.path.join(path, name)
    132             displayname = linkname = name
    133             # Append / for directories or @ for symbolic links
    134             if os.path.isdir(fullname):
    135                 displayname = name + "/"
    136                 linkname = name + "/"
    137             if os.path.islink(fullname):
    138                 displayname = name + "@"
    139                 # Note: a link to a directory displays with @ and links with /
    140             f.write('<li><a href="%s">%s</a>\n'
    141                     % (urllib.quote(linkname), cgi.escape(displayname)))
    142         f.write("</ul>\n<hr>\n</body>\n</html>\n")
    143         length = f.tell()
    144         f.seek(0)
    145         self.send_response(200)
    146         encoding = sys.getfilesystemencoding()
    147         self.send_header("Content-type", "text/html; charset=%s" % encoding)
    148         self.send_header("Content-Length", str(length))
    149         self.end_headers()
    150         return f
    151 
    152     def translate_path(self, path):
    153         """Translate a /-separated PATH to the local filename syntax.
    154 
    155         Components that mean special things to the local file system
    156         (e.g. drive or directory names) are ignored.  (XXX They should
    157         probably be diagnosed.)
    158 
    159         """
    160         # abandon query parameters
    161         path = path.split('?',1)[0]
    162         path = path.split('#',1)[0]
    163         # Don't forget explicit trailing slash when normalizing. Issue17324
    164         trailing_slash = path.rstrip().endswith('/')
    165         path = posixpath.normpath(urllib.unquote(path))
    166         words = path.split('/')
    167         words = filter(None, words)
    168         path = os.getcwd()
    169         for word in words:
    170             if os.path.dirname(word) or word in (os.curdir, os.pardir):
    171                 # Ignore components that are not a simple file/directory name
    172                 continue
    173             path = os.path.join(path, word)
    174         if trailing_slash:
    175             path += '/'
    176         return path
    177 
    178     def copyfile(self, source, outputfile):
    179         """Copy all data between two file objects.
    180 
    181         The SOURCE argument is a file object open for reading
    182         (or anything with a read() method) and the DESTINATION
    183         argument is a file object open for writing (or
    184         anything with a write() method).
    185 
    186         The only reason for overriding this would be to change
    187         the block size or perhaps to replace newlines by CRLF
    188         -- note however that this the default server uses this
    189         to copy binary data as well.
    190 
    191         """
    192         shutil.copyfileobj(source, outputfile)
    193 
    194     def guess_type(self, path):
    195         """Guess the type of a file.
    196 
    197         Argument is a PATH (a filename).
    198 
    199         Return value is a string of the form type/subtype,
    200         usable for a MIME Content-type header.
    201 
    202         The default implementation looks the file's extension
    203         up in the table self.extensions_map, using application/octet-stream
    204         as a default; however it would be permissible (if
    205         slow) to look inside the data to make a better guess.
    206 
    207         """
    208 
    209         base, ext = posixpath.splitext(path)
    210         if ext in self.extensions_map:
    211             return self.extensions_map[ext]
    212         ext = ext.lower()
    213         if ext in self.extensions_map:
    214             return self.extensions_map[ext]
    215         else:
    216             return self.extensions_map['']
    217 
    218     if not mimetypes.inited:
    219         mimetypes.init() # try to read system mime.types
    220     extensions_map = mimetypes.types_map.copy()
    221     extensions_map.update({
    222         '': 'application/octet-stream', # Default
    223         '.py': 'text/plain',
    224         '.c': 'text/plain',
    225         '.h': 'text/plain',
    226         })
    227 
    228 
    229 def test(HandlerClass = SimpleHTTPRequestHandler,
    230          ServerClass = BaseHTTPServer.HTTPServer):
    231     BaseHTTPServer.test(HandlerClass, ServerClass)
    232 
    233 
    234 if __name__ == '__main__':
    235     test()
    236