SimpleHTTPServer.py 6.67 KB
Newer Older
Guido van Rossum's avatar
Guido van Rossum committed
1 2 3 4 5 6 7 8
"""Simple HTTP Server.

This module builds on BaseHTTPServer by implementing the standard GET
and HEAD requests in a fairly straightforward manner.

"""


9
__version__ = "0.6"
Guido van Rossum's avatar
Guido van Rossum committed
10

11
__all__ = ["SimpleHTTPRequestHandler"]
Guido van Rossum's avatar
Guido van Rossum committed
12 13 14 15

import os
import posixpath
import BaseHTTPServer
16
import urllib
17
import urlparse
18
import cgi
19
import shutil
20
import mimetypes
21 22 23 24
try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO
Guido van Rossum's avatar
Guido van Rossum committed
25 26 27 28 29 30 31


class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):

    """Simple HTTP request handler with GET and HEAD commands.

    This serves files from the current directory and any of its
32 33
    subdirectories.  The MIME type for files is determined by
    calling the .guess_type() method.
Guido van Rossum's avatar
Guido van Rossum committed
34 35 36 37 38 39 40 41 42

    The GET and HEAD requests are identical except that the HEAD
    request omits the actual contents of the file.

    """

    server_version = "SimpleHTTP/" + __version__

    def do_GET(self):
43 44 45 46 47
        """Serve a GET request."""
        f = self.send_head()
        if f:
            self.copyfile(f, self.wfile)
            f.close()
Guido van Rossum's avatar
Guido van Rossum committed
48 49

    def do_HEAD(self):
50 51 52 53
        """Serve a HEAD request."""
        f = self.send_head()
        if f:
            f.close()
Guido van Rossum's avatar
Guido van Rossum committed
54 55

    def send_head(self):
56 57 58 59 60 61 62 63 64 65 66
        """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)
67
        f = None
68
        if os.path.isdir(path):
69 70 71 72 73 74 75 76 77 78
            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)
        if ctype.startswith('text/'):
            mode = 'r'
79
        else:
80 81 82 83 84 85
            mode = 'rb'
        try:
            f = open(path, mode)
        except IOError:
            self.send_error(404, "File not found")
            return None
86
        self.send_response(200)
87
        self.send_header("Content-type", ctype)
88 89 90
        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))
91 92
        self.end_headers()
        return f
Guido van Rossum's avatar
Guido van Rossum committed
93

94
    def list_directory(self, path):
95 96 97 98 99 100 101
        """Helper to produce a directory listing (absent index.html).

        Return value is either a file object, or None (indicating an
        error).  In either case, the headers are sent, making the
        interface the same as for send_head().

        """
102 103 104
        try:
            list = os.listdir(path)
        except os.error:
Jeremy Hylton's avatar
Jeremy Hylton committed
105
            self.send_error(404, "No permission to list directory")
106
            return None
107
        list.sort(key=lambda a: a.lower())
108
        f = StringIO()
109 110 111
        displaypath = cgi.escape(urllib.unquote(self.path))
        f.write("<title>Directory listing for %s</title>\n" % displaypath)
        f.write("<h2>Directory listing for %s</h2>\n" % displaypath)
112 113 114
        f.write("<hr>\n<ul>\n")
        for name in list:
            fullname = os.path.join(path, name)
115
            displayname = linkname = name
116 117 118
            # Append / for directories or @ for symbolic links
            if os.path.isdir(fullname):
                displayname = name + "/"
119
                linkname = name + "/"
120 121
            if os.path.islink(fullname):
                displayname = name + "@"
122
                # Note: a link to a directory displays with @ and links with /
123 124
            f.write('<li><a href="%s">%s</a>\n'
                    % (urllib.quote(linkname), cgi.escape(displayname)))
125
        f.write("</ul>\n<hr>\n")
126
        length = f.tell()
127
        f.seek(0)
128 129
        self.send_response(200)
        self.send_header("Content-type", "text/html")
130
        self.send_header("Content-Length", str(length))
131
        self.end_headers()
132 133
        return f

Guido van Rossum's avatar
Guido van Rossum committed
134
    def translate_path(self, path):
135 136 137 138 139 140 141
        """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.)

        """
142 143
        # abandon query parameters
        path = urlparse.urlparse(path)[2]
144
        path = posixpath.normpath(urllib.unquote(path))
145
        words = path.split('/')
146 147 148 149 150 151 152 153
        words = filter(None, words)
        path = os.getcwd()
        for word in words:
            drive, word = os.path.splitdrive(word)
            head, word = os.path.split(word)
            if word in (os.curdir, os.pardir): continue
            path = os.path.join(path, word)
        return path
Guido van Rossum's avatar
Guido van Rossum committed
154 155

    def copyfile(self, source, outputfile):
156
        """Copy all data between two file objects.
Guido van Rossum's avatar
Guido van Rossum committed
157

158 159 160 161
        The SOURCE argument is a file object open for reading
        (or anything with a read() method) and the DESTINATION
        argument is a file object open for writing (or
        anything with a write() method).
Guido van Rossum's avatar
Guido van Rossum committed
162

163 164 165 166
        The only reason for overriding this would be to change
        the block size or perhaps to replace newlines by CRLF
        -- note however that this the default server uses this
        to copy binary data as well.
Guido van Rossum's avatar
Guido van Rossum committed
167

168
        """
169
        shutil.copyfileobj(source, outputfile)
Guido van Rossum's avatar
Guido van Rossum committed
170 171

    def guess_type(self, path):
172
        """Guess the type of a file.
Guido van Rossum's avatar
Guido van Rossum committed
173

174
        Argument is a PATH (a filename).
Guido van Rossum's avatar
Guido van Rossum committed
175

176 177
        Return value is a string of the form type/subtype,
        usable for a MIME Content-type header.
Guido van Rossum's avatar
Guido van Rossum committed
178

179
        The default implementation looks the file's extension
180
        up in the table self.extensions_map, using application/octet-stream
181 182
        as a default; however it would be permissible (if
        slow) to look inside the data to make a better guess.
Guido van Rossum's avatar
Guido van Rossum committed
183

184
        """
Guido van Rossum's avatar
Guido van Rossum committed
185

186
        base, ext = posixpath.splitext(path)
187
        if ext in self.extensions_map:
188
            return self.extensions_map[ext]
189
        ext = ext.lower()
190
        if ext in self.extensions_map:
191 192 193
            return self.extensions_map[ext]
        else:
            return self.extensions_map['']
Tim Peters's avatar
Tim Peters committed
194

195 196
    if not mimetypes.inited:
        mimetypes.init() # try to read system mime.types
197 198 199 200 201 202 203
    extensions_map = mimetypes.types_map.copy()
    extensions_map.update({
        '': 'application/octet-stream', # Default
        '.py': 'text/plain',
        '.c': 'text/plain',
        '.h': 'text/plain',
        })
Guido van Rossum's avatar
Guido van Rossum committed
204 205 206


def test(HandlerClass = SimpleHTTPRequestHandler,
Guido van Rossum's avatar
Guido van Rossum committed
207
         ServerClass = BaseHTTPServer.HTTPServer):
Guido van Rossum's avatar
Guido van Rossum committed
208 209 210 211 212
    BaseHTTPServer.test(HandlerClass, ServerClass)


if __name__ == '__main__':
    test()