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 SimpleAppHTTPRequestHandler(object):
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__
42 def onGET(self
, client
, *args
):
43 """Serve a GET request."""
47 ka
= hr
.request_version
== "HTTP/1.1"
48 f
= self
.send_head(hr
, ka
)
50 self
.copyfile(f
, hr
.wfile
)
55 """Serve a HEAD request."""
57 f
= self
.send_head(hr
, False)
61 def send_head(self
, hr
, ka
):
62 """Common code for GET and HEAD commands.
64 This sends the response code and MIME headers.
66 Return value is either a file object (which has to be copied
67 to the outputfile by the caller unless the command was HEAD,
68 and must be closed by the caller under all circumstances), or
69 None, in which case the caller has nothing further to do.
72 path
= self
.translate_path(self
.path
)
74 if os
.path
.isdir(path
):
75 if not self
.path
.endswith('/'):
76 # redirect browser - doing basically what apache does
78 hr
.send_header("Location", self
.path
+ "/")
81 for index
in "index.html", "index.htm":
82 index
= os
.path
.join(path
, index
)
83 if os
.path
.exists(index
):
87 return self
.list_directory(hr
, path
, ka
)
88 ctype
= self
.guess_type(path
)
89 if ctype
.startswith('text/'):
96 hr
.send_error(404, "File not found")
99 hr
.send_header("Content-type", ctype
)
100 fs
= os
.fstat(f
.fileno())
101 hr
.send_header("Content-Length", str(fs
[6]))
102 hr
.send_header("Last-Modified", hr
.date_time_string(fs
.st_mtime
))
104 hr
.send_header("Connection", "keep-alive")
109 def list_directory(self
, hr
, path
, ka
):
110 """Helper to produce a directory listing (absent index.html).
112 Return value is either a file object, or None (indicating an
113 error). In either case, the headers are sent, making the
114 interface the same as for send_head().
118 list = os
.listdir(path
)
120 self
.send_error(404, "No permission to list directory")
122 list.sort(key
=lambda a
: a
.lower())
124 displaypath
= cgi
.escape(urllib
.unquote(self
.path
))
125 f
.write("<title>Directory listing for %s</title>\n" % displaypath
)
126 f
.write("<h2>Directory listing for %s</h2>\n" % displaypath
)
127 f
.write("<hr>\n<ul>\n")
129 fullname
= os
.path
.join(path
, name
)
130 displayname
= linkname
= name
131 # Append / for directories or @ for symbolic links
132 if os
.path
.isdir(fullname
):
133 displayname
= name
+ "/"
134 linkname
= name
+ "/"
135 if os
.path
.islink(fullname
):
136 displayname
= name
+ "@"
137 # Note: a link to a directory displays with @ and links with /
138 f
.write('<li><a href="%s">%s</a>\n'
139 % (urllib
.quote(linkname
), cgi
.escape(displayname
)))
140 f
.write("</ul>\n<hr>\n")
143 hr
.send_response(200)
144 hr
.send_header("Content-type", "text/html")
145 hr
.send_header("Content-Length", str(length
))
147 hr
.send_header("Connection", "keep-alive")
152 def translate_path(self
, path
):
153 """Translate a /-separated PATH to the local filename syntax.
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.)
160 # abandon query parameters
161 path
= urlparse
.urlparse(path
)[2]
162 path
= posixpath
.normpath(urllib
.unquote(path
))
163 words
= path
.split('/')
164 words
= filter(None, words
)
167 drive
, word
= os
.path
.splitdrive(word
)
168 head
, word
= os
.path
.split(word
)
169 if word
in (os
.curdir
, os
.pardir
): continue
170 path
= os
.path
.join(path
, word
)
173 def copyfile(self
, source
, outputfile
):
174 """Copy all data between two file objects.
176 The SOURCE argument is a file object open for reading
177 (or anything with a read() method) and the DESTINATION
178 argument is a file object open for writing (or
179 anything with a write() method).
181 The only reason for overriding this would be to change
182 the block size or perhaps to replace newlines by CRLF
183 -- note however that this the default server uses this
184 to copy binary data as well.
187 shutil
.copyfileobj(source
, outputfile
)
189 def guess_type(self
, path
):
190 """Guess the type of a file.
192 Argument is a PATH (a filename).
194 Return value is a string of the form type/subtype,
195 usable for a MIME Content-type header.
197 The default implementation looks the file's extension
198 up in the table self.extensions_map, using application/octet-stream
199 as a default; however it would be permissible (if
200 slow) to look inside the data to make a better guess.
204 base
, ext
= posixpath
.splitext(path
)
205 if ext
in self
.extensions_map
:
206 return self
.extensions_map
[ext
]
208 if ext
in self
.extensions_map
:
209 return self
.extensions_map
[ext
]
211 return self
.extensions_map
['']
213 if not mimetypes
.inited
:
214 mimetypes
.init() # try to read system mime.types
215 extensions_map
= mimetypes
.types_map
.copy()
216 extensions_map
.update({
217 '': 'application/octet-stream', # Default