SimpleHTTPServer.py 7.17 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 cgi
18
import shutil
19
import mimetypes
20 21 22 23
try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO
Guido van Rossum's avatar
Guido van Rossum committed
24 25 26 27 28 29 30


class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):

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

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

    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):
42 43 44 45 46
        """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
47 48

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

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

98
    def list_directory(self, path):
99 100 101 102 103 104 105
        """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().

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

Guido van Rossum's avatar
Guido van Rossum committed
139
    def translate_path(self, path):
140 141 142 143 144 145 146
        """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.)

        """
147
        # abandon query parameters
148 149
        path = path.split('?',1)[0]
        path = path.split('#',1)[0]
150
        path = posixpath.normpath(urllib.unquote(path))
151
        words = path.split('/')
152 153 154 155 156 157 158 159
        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
160 161

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

164 165 166 167
        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
168

169 170 171 172
        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
173

174
        """
175
        shutil.copyfileobj(source, outputfile)
Guido van Rossum's avatar
Guido van Rossum committed
176 177

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

180
        Argument is a PATH (a filename).
Guido van Rossum's avatar
Guido van Rossum committed
181

182 183
        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
184

185
        The default implementation looks the file's extension
186
        up in the table self.extensions_map, using application/octet-stream
187 188
        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
189

190
        """
Guido van Rossum's avatar
Guido van Rossum committed
191

192
        base, ext = posixpath.splitext(path)
193
        if ext in self.extensions_map:
194
            return self.extensions_map[ext]
195
        ext = ext.lower()
196
        if ext in self.extensions_map:
197 198 199
            return self.extensions_map[ext]
        else:
            return self.extensions_map['']
Tim Peters's avatar
Tim Peters committed
200

201 202
    if not mimetypes.inited:
        mimetypes.init() # try to read system mime.types
203 204 205 206 207 208 209
    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
210 211 212


def test(HandlerClass = SimpleHTTPRequestHandler,
Guido van Rossum's avatar
Guido van Rossum committed
213
         ServerClass = BaseHTTPServer.HTTPServer):
Guido van Rossum's avatar
Guido van Rossum committed
214 215 216 217 218
    BaseHTTPServer.test(HandlerClass, ServerClass)


if __name__ == '__main__':
    test()