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 cgi
     18 import sys
     19 import shutil
     20 import mimetypes
     21 try:
     22     from cStringIO import StringIO
     23 except ImportError:
     24     from StringIO import StringIO
     25 
     26 
     27 class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
     28 
     29     """Simple HTTP request handler with GET and HEAD commands.
     30 
     31     This serves files from the current directory and any of its
     32     subdirectories.  The MIME type for files is determined by
     33     calling the .guess_type() method.
     34 
     35     The GET and HEAD requests are identical except that the HEAD
     36     request omits the actual contents of the file.
     37 
     38     """
     39 
     40     server_version = "SimpleHTTP/" + __version__
     41 
     42     def do_GET(self):
     43         """Serve a GET request."""
     44         f = self.send_head()
     45         if f:
     46             self.copyfile(f, self.wfile)
     47             f.close()
     48 
     49     def do_HEAD(self):
     50         """Serve a HEAD request."""
     51         f = self.send_head()
     52         if f:
     53             f.close()
     54 
     55     def send_head(self):
     56         """Common code for GET and HEAD commands.
     57 
     58         This sends the response code and MIME headers.
     59 
     60         Return value is either a file object (which has to be copied
     61         to the outputfile by the caller unless the command was HEAD,
     62         and must be closed by the caller under all circumstances), or
     63         None, in which case the caller has nothing further to do.
     64 
     65         """
     66         path = self.translate_path(self.path)
     67         f = None
     68         if os.path.isdir(path):
     69             if not self.path.endswith('/'):
     70                 # redirect browser - doing basically what apache does

     71                 self.send_response(301)
     72                 self.send_header("Location", self.path + "/")
     73                 self.end_headers()
     74                 return None
     75             for index in "index.html", "index.htm":
     76                 index = os.path.join(path, index)
     77                 if os.path.exists(index):
     78                     path = index
     79                     break
     80             else:
     81                 return self.list_directory(path)
     82         ctype = self.guess_type(path)
     83         try:
     84             # Always read in binary mode. Opening files in text mode may cause

     85             # newline translations, making the actual size of the content

     86             # transmitted *less* than the content-length!

     87             f = open(path, 'rb')
     88         except IOError:
     89             self.send_error(404, "File not found")
     90             return None
     91         self.send_response(200)
     92         self.send_header("Content-type", ctype)
     93         fs = os.fstat(f.fileno())
     94         self.send_header("Content-Length", str(fs[6]))
     95         self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
     96         self.end_headers()
     97         return f
     98 
     99     def list_directory(self, path):
    100         """Helper to produce a directory listing (absent index.html).
    101 
    102         Return value is either a file object, or None (indicating an
    103         error).  In either case, the headers are sent, making the
    104         interface the same as for send_head().
    105 
    106         """
    107         try:
    108             list = os.listdir(path)
    109         except os.error:
    110             self.send_error(404, "No permission to list directory")
    111             return None
    112         list.sort(key=lambda a: a.lower())
    113         f = StringIO()
    114         displaypath = cgi.escape(urllib.unquote(self.path))
    115         f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
    116         f.write("<html>\n<title>Directory listing for %s</title>\n" % displaypath)
    117         f.write("<body>\n<h2>Directory listing for %s</h2>\n" % displaypath)
    118         f.write("<hr>\n<ul>\n")
    119         for name in list:
    120             fullname = os.path.join(path, name)
    121             displayname = linkname = name
    122             # Append / for directories or @ for symbolic links

    123             if os.path.isdir(fullname):
    124                 displayname = name + "/"
    125                 linkname = name + "/"
    126             if os.path.islink(fullname):
    127                 displayname = name + "@"
    128                 # Note: a link to a directory displays with @ and links with /

    129             f.write('<li><a href="%s">%s</a>\n'
    130                     % (urllib.quote(linkname), cgi.escape(displayname)))
    131         f.write("</ul>\n<hr>\n</body>\n</html>\n")
    132         length = f.tell()
    133         f.seek(0)
    134         self.send_response(200)
    135         encoding = sys.getfilesystemencoding()
    136         self.send_header("Content-type", "text/html; charset=%s" % encoding)
    137         self.send_header("Content-Length", str(length))
    138         self.end_headers()
    139         return f
    140 
    141     def translate_path(self, path):
    142         """Translate a /-separated PATH to the local filename syntax.
    143 
    144         Components that mean special things to the local file system
    145         (e.g. drive or directory names) are ignored.  (XXX They should
    146         probably be diagnosed.)
    147 
    148         """
    149         # abandon query parameters

    150         path = path.split('?',1)[0]
    151         path = path.split('#',1)[0]
    152         path = posixpath.normpath(urllib.unquote(path))
    153         words = path.split('/')
    154         words = filter(None, words)
    155         path = os.getcwd()
    156         for word in words:
    157             drive, word = os.path.splitdrive(word)
    158             head, word = os.path.split(word)
    159             if word in (os.curdir, os.pardir): continue
    160             path = os.path.join(path, word)
    161         return path
    162 
    163     def copyfile(self, source, outputfile):
    164         """Copy all data between two file objects.
    165 
    166         The SOURCE argument is a file object open for reading
    167         (or anything with a read() method) and the DESTINATION
    168         argument is a file object open for writing (or
    169         anything with a write() method).
    170 
    171         The only reason for overriding this would be to change
    172         the block size or perhaps to replace newlines by CRLF
    173         -- note however that this the default server uses this
    174         to copy binary data as well.
    175 
    176         """
    177         shutil.copyfileobj(source, outputfile)
    178 
    179     def guess_type(self, path):
    180         """Guess the type of a file.
    181 
    182         Argument is a PATH (a filename).
    183 
    184         Return value is a string of the form type/subtype,
    185         usable for a MIME Content-type header.
    186 
    187         The default implementation looks the file's extension
    188         up in the table self.extensions_map, using application/octet-stream
    189         as a default; however it would be permissible (if
    190         slow) to look inside the data to make a better guess.
    191 
    192         """
    193 
    194         base, ext = posixpath.splitext(path)
    195         if ext in self.extensions_map:
    196             return self.extensions_map[ext]
    197         ext = ext.lower()
    198         if ext in self.extensions_map:
    199             return self.extensions_map[ext]
    200         else:
    201             return self.extensions_map['']
    202 
    203     if not mimetypes.inited:
    204         mimetypes.init() # try to read system mime.types

    205     extensions_map = mimetypes.types_map.copy()
    206     extensions_map.update({
    207         '': 'application/octet-stream', # Default

    208         '.py': 'text/plain',
    209         '.c': 'text/plain',
    210         '.h': 'text/plain',
    211         })
    212 
    213 
    214 def test(HandlerClass = SimpleHTTPRequestHandler,
    215          ServerClass = BaseHTTPServer.HTTPServer):
    216     BaseHTTPServer.test(HandlerClass, ServerClass)
    217 
    218 
    219 if __name__ == '__main__':
    220     test()
    221