3
This module builds on BaseHTTPServer by implementing the standard GET
4
and HEAD requests in a fairly straightforward manner.
11
__all__ = ["SimpleHTTPRequestHandler"]
22
from cStringIO import StringIO
24
from StringIO import StringIO
27
class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
29
"""Simple HTTP request handler with GET and HEAD commands.
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.
35
The GET and HEAD requests are identical except that the HEAD
36
request omits the actual contents of the file.
40
server_version = "SimpleHTTP/" + __version__
43
"""Serve a GET request."""
46
self.copyfile(f, self.wfile)
50
"""Serve a HEAD request."""
56
"""Common code for GET and HEAD commands.
58
This sends the response code and MIME headers.
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.
66
path = self.translate_path(self.path)
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 + "/")
75
for index in "index.html", "index.htm":
76
index = os.path.join(path, index)
77
if os.path.exists(index):
81
return self.list_directory(path)
82
ctype = self.guess_type(path)
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!
89
self.send_error(404, "File not found")
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))
99
def list_directory(self, path):
100
"""Helper to produce a directory listing (absent index.html).
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().
108
list = os.listdir(path)
110
self.send_error(404, "No permission to list directory")
112
list.sort(key=lambda a: a.lower())
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")
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")
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))
141
def translate_path(self, path):
142
"""Translate a /-separated PATH to the local filename syntax.
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.)
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)
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)
163
def copyfile(self, source, outputfile):
164
"""Copy all data between two file objects.
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).
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.
177
shutil.copyfileobj(source, outputfile)
179
def guess_type(self, path):
180
"""Guess the type of a file.
182
Argument is a PATH (a filename).
184
Return value is a string of the form type/subtype,
185
usable for a MIME Content-type header.
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.
194
base, ext = posixpath.splitext(path)
195
if ext in self.extensions_map:
196
return self.extensions_map[ext]
198
if ext in self.extensions_map:
199
return self.extensions_map[ext]
201
return self.extensions_map['']
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
214
def test(HandlerClass = SimpleHTTPRequestHandler,
215
ServerClass = BaseHTTPServer.HTTPServer):
216
BaseHTTPServer.test(HandlerClass, ServerClass)
219
if __name__ == '__main__':