]>
Commit | Line | Data |
---|---|---|
4710c53d | 1 | """Simple HTTP Server.\r |
2 | \r | |
3 | This module builds on BaseHTTPServer by implementing the standard GET\r | |
4 | and HEAD requests in a fairly straightforward manner.\r | |
5 | \r | |
6 | """\r | |
7 | \r | |
8 | \r | |
9 | __version__ = "0.6"\r | |
10 | \r | |
11 | __all__ = ["SimpleHTTPRequestHandler"]\r | |
12 | \r | |
13 | import os\r | |
14 | import posixpath\r | |
15 | import BaseHTTPServer\r | |
16 | import urllib\r | |
17 | import cgi\r | |
18 | import sys\r | |
19 | import shutil\r | |
20 | import mimetypes\r | |
21 | try:\r | |
22 | from cStringIO import StringIO\r | |
23 | except ImportError:\r | |
24 | from StringIO import StringIO\r | |
25 | \r | |
26 | \r | |
27 | class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):\r | |
28 | \r | |
29 | """Simple HTTP request handler with GET and HEAD commands.\r | |
30 | \r | |
31 | This serves files from the current directory and any of its\r | |
32 | subdirectories. The MIME type for files is determined by\r | |
33 | calling the .guess_type() method.\r | |
34 | \r | |
35 | The GET and HEAD requests are identical except that the HEAD\r | |
36 | request omits the actual contents of the file.\r | |
37 | \r | |
38 | """\r | |
39 | \r | |
40 | server_version = "SimpleHTTP/" + __version__\r | |
41 | \r | |
42 | def do_GET(self):\r | |
43 | """Serve a GET request."""\r | |
44 | f = self.send_head()\r | |
45 | if f:\r | |
46 | self.copyfile(f, self.wfile)\r | |
47 | f.close()\r | |
48 | \r | |
49 | def do_HEAD(self):\r | |
50 | """Serve a HEAD request."""\r | |
51 | f = self.send_head()\r | |
52 | if f:\r | |
53 | f.close()\r | |
54 | \r | |
55 | def send_head(self):\r | |
56 | """Common code for GET and HEAD commands.\r | |
57 | \r | |
58 | This sends the response code and MIME headers.\r | |
59 | \r | |
60 | Return value is either a file object (which has to be copied\r | |
61 | to the outputfile by the caller unless the command was HEAD,\r | |
62 | and must be closed by the caller under all circumstances), or\r | |
63 | None, in which case the caller has nothing further to do.\r | |
64 | \r | |
65 | """\r | |
66 | path = self.translate_path(self.path)\r | |
67 | f = None\r | |
68 | if os.path.isdir(path):\r | |
69 | if not self.path.endswith('/'):\r | |
70 | # redirect browser - doing basically what apache does\r | |
71 | self.send_response(301)\r | |
72 | self.send_header("Location", self.path + "/")\r | |
73 | self.end_headers()\r | |
74 | return None\r | |
75 | for index in "index.html", "index.htm":\r | |
76 | index = os.path.join(path, index)\r | |
77 | if os.path.exists(index):\r | |
78 | path = index\r | |
79 | break\r | |
80 | else:\r | |
81 | return self.list_directory(path)\r | |
82 | ctype = self.guess_type(path)\r | |
83 | try:\r | |
84 | # Always read in binary mode. Opening files in text mode may cause\r | |
85 | # newline translations, making the actual size of the content\r | |
86 | # transmitted *less* than the content-length!\r | |
87 | f = open(path, 'rb')\r | |
88 | except IOError:\r | |
89 | self.send_error(404, "File not found")\r | |
90 | return None\r | |
91 | self.send_response(200)\r | |
92 | self.send_header("Content-type", ctype)\r | |
93 | fs = os.fstat(f.fileno())\r | |
94 | self.send_header("Content-Length", str(fs[6]))\r | |
95 | self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))\r | |
96 | self.end_headers()\r | |
97 | return f\r | |
98 | \r | |
99 | def list_directory(self, path):\r | |
100 | """Helper to produce a directory listing (absent index.html).\r | |
101 | \r | |
102 | Return value is either a file object, or None (indicating an\r | |
103 | error). In either case, the headers are sent, making the\r | |
104 | interface the same as for send_head().\r | |
105 | \r | |
106 | """\r | |
107 | try:\r | |
108 | list = os.listdir(path)\r | |
109 | except os.error:\r | |
110 | self.send_error(404, "No permission to list directory")\r | |
111 | return None\r | |
112 | list.sort(key=lambda a: a.lower())\r | |
113 | f = StringIO()\r | |
114 | displaypath = cgi.escape(urllib.unquote(self.path))\r | |
115 | f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')\r | |
116 | f.write("<html>\n<title>Directory listing for %s</title>\n" % displaypath)\r | |
117 | f.write("<body>\n<h2>Directory listing for %s</h2>\n" % displaypath)\r | |
118 | f.write("<hr>\n<ul>\n")\r | |
119 | for name in list:\r | |
120 | fullname = os.path.join(path, name)\r | |
121 | displayname = linkname = name\r | |
122 | # Append / for directories or @ for symbolic links\r | |
123 | if os.path.isdir(fullname):\r | |
124 | displayname = name + "/"\r | |
125 | linkname = name + "/"\r | |
126 | if os.path.islink(fullname):\r | |
127 | displayname = name + "@"\r | |
128 | # Note: a link to a directory displays with @ and links with /\r | |
129 | f.write('<li><a href="%s">%s</a>\n'\r | |
130 | % (urllib.quote(linkname), cgi.escape(displayname)))\r | |
131 | f.write("</ul>\n<hr>\n</body>\n</html>\n")\r | |
132 | length = f.tell()\r | |
133 | f.seek(0)\r | |
134 | self.send_response(200)\r | |
135 | encoding = sys.getfilesystemencoding()\r | |
136 | self.send_header("Content-type", "text/html; charset=%s" % encoding)\r | |
137 | self.send_header("Content-Length", str(length))\r | |
138 | self.end_headers()\r | |
139 | return f\r | |
140 | \r | |
141 | def translate_path(self, path):\r | |
142 | """Translate a /-separated PATH to the local filename syntax.\r | |
143 | \r | |
144 | Components that mean special things to the local file system\r | |
145 | (e.g. drive or directory names) are ignored. (XXX They should\r | |
146 | probably be diagnosed.)\r | |
147 | \r | |
148 | """\r | |
149 | # abandon query parameters\r | |
150 | path = path.split('?',1)[0]\r | |
151 | path = path.split('#',1)[0]\r | |
152 | path = posixpath.normpath(urllib.unquote(path))\r | |
153 | words = path.split('/')\r | |
154 | words = filter(None, words)\r | |
155 | path = os.getcwd()\r | |
156 | for word in words:\r | |
157 | drive, word = os.path.splitdrive(word)\r | |
158 | head, word = os.path.split(word)\r | |
159 | if word in (os.curdir, os.pardir): continue\r | |
160 | path = os.path.join(path, word)\r | |
161 | return path\r | |
162 | \r | |
163 | def copyfile(self, source, outputfile):\r | |
164 | """Copy all data between two file objects.\r | |
165 | \r | |
166 | The SOURCE argument is a file object open for reading\r | |
167 | (or anything with a read() method) and the DESTINATION\r | |
168 | argument is a file object open for writing (or\r | |
169 | anything with a write() method).\r | |
170 | \r | |
171 | The only reason for overriding this would be to change\r | |
172 | the block size or perhaps to replace newlines by CRLF\r | |
173 | -- note however that this the default server uses this\r | |
174 | to copy binary data as well.\r | |
175 | \r | |
176 | """\r | |
177 | shutil.copyfileobj(source, outputfile)\r | |
178 | \r | |
179 | def guess_type(self, path):\r | |
180 | """Guess the type of a file.\r | |
181 | \r | |
182 | Argument is a PATH (a filename).\r | |
183 | \r | |
184 | Return value is a string of the form type/subtype,\r | |
185 | usable for a MIME Content-type header.\r | |
186 | \r | |
187 | The default implementation looks the file's extension\r | |
188 | up in the table self.extensions_map, using application/octet-stream\r | |
189 | as a default; however it would be permissible (if\r | |
190 | slow) to look inside the data to make a better guess.\r | |
191 | \r | |
192 | """\r | |
193 | \r | |
194 | base, ext = posixpath.splitext(path)\r | |
195 | if ext in self.extensions_map:\r | |
196 | return self.extensions_map[ext]\r | |
197 | ext = ext.lower()\r | |
198 | if ext in self.extensions_map:\r | |
199 | return self.extensions_map[ext]\r | |
200 | else:\r | |
201 | return self.extensions_map['']\r | |
202 | \r | |
203 | if not mimetypes.inited:\r | |
204 | mimetypes.init() # try to read system mime.types\r | |
205 | extensions_map = mimetypes.types_map.copy()\r | |
206 | extensions_map.update({\r | |
207 | '': 'application/octet-stream', # Default\r | |
208 | '.py': 'text/plain',\r | |
209 | '.c': 'text/plain',\r | |
210 | '.h': 'text/plain',\r | |
211 | })\r | |
212 | \r | |
213 | \r | |
214 | def test(HandlerClass = SimpleHTTPRequestHandler,\r | |
215 | ServerClass = BaseHTTPServer.HTTPServer):\r | |
216 | BaseHTTPServer.test(HandlerClass, ServerClass)\r | |
217 | \r | |
218 | \r | |
219 | if __name__ == '__main__':\r | |
220 | test()\r |