move example to different port
[multitaskhttpd.git] / SimpleAppHTTPServer.py
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 urlparse
18 import cgi
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 SimpleAppHTTPRequestHandler(object):
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 onGET(self, client, *args):
43 """Serve a GET request."""
44 self.client = client
45 hr = args[0]
46 self.path = hr.path
47 ka = hr.request_version == "HTTP/1.1"
48 f = self.send_head(hr, ka)
49 if f:
50 self.copyfile(f, hr.wfile)
51 f.close()
52
53 def onHEAD(self):
54 self.client = client
55 """Serve a HEAD request."""
56 hr = args[0]
57 f = self.send_head(hr, False)
58 if f:
59 f.close()
60
61 def send_head(self, hr, ka):
62 """Common code for GET and HEAD commands.
63
64 This sends the response code and MIME headers.
65
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.
70
71 """
72 path = self.translate_path(self.path)
73 f = None
74 if os.path.isdir(path):
75 if not self.path.endswith('/'):
76 # redirect browser - doing basically what apache does
77 hr.send_response(301)
78 hr.send_header("Location", self.path + "/")
79 hr.end_headers()
80 return None
81 for index in "index.html", "index.htm":
82 index = os.path.join(path, index)
83 if os.path.exists(index):
84 path = index
85 break
86 else:
87 return self.list_directory(hr, path, ka)
88 ctype = self.guess_type(path)
89 if ctype.startswith('text/'):
90 mode = 'r'
91 else:
92 mode = 'rb'
93 try:
94 f = open(path, mode)
95 except IOError:
96 hr.send_error(404, "File not found")
97 return None
98 hr.send_response(200)
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))
103 if ka:
104 hr.send_header("Connection", "keep-alive")
105 hr.add_cookies()
106 hr.end_headers()
107 return f
108
109 def list_directory(self, hr, path, ka):
110 """Helper to produce a directory listing (absent index.html).
111
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().
115
116 """
117 try:
118 list = os.listdir(path)
119 except os.error:
120 self.send_error(404, "No permission to list directory")
121 return None
122 list.sort(key=lambda a: a.lower())
123 f = StringIO()
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")
128 for name in list:
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")
141 length = f.tell()
142 f.seek(0)
143 hr.send_response(200)
144 hr.send_header("Content-type", "text/html")
145 hr.send_header("Content-Length", str(length))
146 if ka:
147 hr.send_header("Connection", "keep-alive")
148 hr.add_cookies()
149 hr.end_headers()
150 return f
151
152 def translate_path(self, path):
153 """Translate a /-separated PATH to the local filename syntax.
154
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.)
158
159 """
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)
165 path = os.getcwd()
166 for word in 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)
171 return path
172
173 def copyfile(self, source, outputfile):
174 """Copy all data between two file objects.
175
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).
180
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.
185
186 """
187 shutil.copyfileobj(source, outputfile)
188
189 def guess_type(self, path):
190 """Guess the type of a file.
191
192 Argument is a PATH (a filename).
193
194 Return value is a string of the form type/subtype,
195 usable for a MIME Content-type header.
196
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.
201
202 """
203
204 base, ext = posixpath.splitext(path)
205 if ext in self.extensions_map:
206 return self.extensions_map[ext]
207 ext = ext.lower()
208 if ext in self.extensions_map:
209 return self.extensions_map[ext]
210 else:
211 return self.extensions_map['']
212
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
218 '.py': 'text/plain',
219 '.c': 'text/plain',
220 '.h': 'text/plain',
221 })
222