一个支持上传的简单http server
I am LAZY bones ? : 一个支持上传的简单http server
bones7456 <[email protected]> reply-to [email protected] to python-cn`CPyUG`华蟒用户组 <[email protected]> date Sat, May 15, 2010 at 17:20
说明
现在,很多人都知道,python里有个SimpleHTTPServer,可以拿来方便地共享文件。比如,你要发送某个文件给局域网里的同学,你只要 cd到所在路径,然后执行这么一行:
python -m SimpleHTTPServer
人家就可以访问 http://你的 IP:8000 来访问你要共享的文件了。 像我早已把这个命令做了alias。
但是,某一天,你需要从同学哪里复制一个文件到本机,然后你就会跟你同学说,XX,共享下某目录。当你以为可以用http来访问他的 8000端口的时 候,他却告诉你,不好意思,我是windows啦~~
当然你可以选择在他windows里装个python,也可以选择使用samba、ftp等其他方式,但是有没有和之前一样简单的方式呢~ 当然了,这时候,你就需要一个支持上传的简单http server,也就是我这个:SimpleHTTPServerWithUpload.py,哈 哈。然后你开个服务,让人家上传即可。
其实这个就是修改自SimpleHTTPServer的,只不过我给它加上了最原始的上传功能,安全性方面没有验证过,不过理论上应该不会没人一直开着 这个吧?另外,我对RFC1867的理解不一定透彻,所以,Use on your own risk!
截图如下:
,单文件、零配置,直接用python运行。
原文: http://li2z.cn/2010/05/15/simplehttpserverwithupload/
SimpleHTTPServerWithUpload.py
1 """Simple HTTP Server With Upload.
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.1"
10 __all__ = ["SimpleHTTPRequestHandler"]
11 __author__ = "bones7456"
12 __home_page__ = "http://li2z.cn/"
13
14 import os
15 import posixpath
16 import BaseHTTPServer
17 import urllib
18 import cgi
19 import shutil
20 import mimetypes
21 import re
22 try:
23 from cStringIO import StringIO
24 except ImportError:
25 from StringIO import StringIO
26
27
28 class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
29
30 """Simple HTTP request handler with GET/HEAD/POST commands.
31
32 This serves files from the current directory and any of its
33 subdirectories. The MIME type for files is determined by
34 calling the .guess_type() method. And can reveive file uploaded
35 by client.
36
37 The GET/HEAD/POST requests are identical except that the HEAD
38 request omits the actual contents of the file.
39
40 """
41
42 server_version = "SimpleHTTPWithUpload/" + __version__
43
44 def do_GET(self):
45 """Serve a GET request."""
46 f = self.send_head()
47 if f:
48 self.copyfile(f, self.wfile)
49 f.close()
50
51 def do_HEAD(self):
52 """Serve a HEAD request."""
53 f = self.send_head()
54 if f:
55 f.close()
56
57 def do_POST(self):
58 """Serve a POST request."""
59 r, info = self.deal_post_data()
60 print r, info, "by: ", self.client_address
61 f = StringIO()
62 f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
63 f.write("<html>\n<title>Upload Result Page</title>\n")
64 f.write("<body>\n<h2>Upload Result Page</h2>\n")
65 f.write("<hr>\n")
66 if r:
67 f.write("<strong>Success:</strong>")
68 else:
69 f.write("<strong>Failed:</strong>")
70 f.write(info)
71 f.write("<br><a href=\"%s\">back</a>" % self.headers['referer'])
72 f.write("<hr><small>Powerd By: bones7456, check new version at ")
73 f.write("<a href=\"http://li2z.cn/?s=SimpleHTTPServerWithUpload\">")
74 f.write("here</a>.</small></body>\n</html>\n")
75 length = f.tell()
76 f.seek(0)
77 self.send_response(200)
78 self.send_header("Content-type", "text/html")
79 self.send_header("Content-Length", str(length))
80 self.end_headers()
81 if f:
82 self.copyfile(f, self.wfile)
83 f.close()
84
85 def deal_post_data(self):
86 boundary = self.headers.plisttext.split("=")[1]
87 remainbytes = int(self.headers['content-length'])
88 line = self.rfile.readline()
89 remainbytes -= len(line)
90 if not boundary in line:
91 return (False, "Content NOT begin with boundary")
92 line = self.rfile.readline()
93 remainbytes -= len(line)
94 fn = re.findall(r'Content-Disposition.*name="file"; filename="(.*)"', line)
95 if not fn:
96 return (False, "Can't find out file name...")
97 path = self.translate_path(self.path)
98 fn = os.path.join(path, fn[0])
99 line = self.rfile.readline()
100 remainbytes -= len(line)
101 line = self.rfile.readline()
102 remainbytes -= len(line)
103 try:
104 out = open(fn, 'wb')
105 except IOError:
106 return (False, "Can't create file to write, do you have permission to write?")
107
108 preline = self.rfile.readline()
109 remainbytes -= len(line)
110 while remainbytes > 0:
111 line = self.rfile.readline()
112 remainbytes -= len(line)
113 if boundary in line:
114 preline = preline[0:-1]
115 if preline.endswith('\r'):
116 preline = preline[0:-1]
117 out.write(preline)
118 out.close()
119 return (True, "File '%s' upload success!" % fn)
120 else:
121 out.write(preline)
122 preline = line
123 return (False, "Unexpect Ends of data.")
124
125 def send_head(self):
126 """Common code for GET and HEAD commands.
127
128 This sends the response code and MIME headers.
129
130 Return value is either a file object (which has to be copied
131 to the outputfile by the caller unless the command was HEAD,
132 and must be closed by the caller under all circumstances), or
133 None, in which case the caller has nothing further to do.
134
135 """
136 path = self.translate_path(self.path)
137 f = None
138 if os.path.isdir(path):
139 if not self.path.endswith('/'):
140 # redirect browser - doing basically what apache does
141 self.send_response(301)
142 self.send_header("Location", self.path + "/")
143 self.end_headers()
144 return None
145 for index in "index.html", "index.htm":
146 index = os.path.join(path, index)
147 if os.path.exists(index):
148 path = index
149 break
150 else:
151 return self.list_directory(path)
152 ctype = self.guess_type(path)
153 try:
154 # Always read in binary mode. Opening files in text mode may cause
155 # newline translations, making the actual size of the content
156 # transmitted *less* than the content-length!
157 f = open(path, 'rb')
158 except IOError:
159 self.send_error(404, "File not found")
160 return None
161 self.send_response(200)
162 self.send_header("Content-type", ctype)
163 fs = os.fstat(f.fileno())
164 self.send_header("Content-Length", str(fs[6]))
165 self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
166 self.end_headers()
167 return f
168
169 def list_directory(self, path):
170 """Helper to produce a directory listing (absent index.html).
171
172 Return value is either a file object, or None (indicating an
173 error). In either case, the headers are sent, making the
174 interface the same as for send_head().
175
176 """
177 try:
178 list = os.listdir(path)
179 except os.error:
180 self.send_error(404, "No permission to list directory")
181 return None
182 list.sort(key=lambda a: a.lower())
183 f = StringIO()
184 displaypath = cgi.escape(urllib.unquote(self.path))
185 f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
186 f.write("<html>\n<title>Directory listing for %s</title>\n" % displaypath)
187 f.write("<body>\n<h2>Directory listing for %s</h2>\n" % displaypath)
188 f.write("<hr>\n")
189 f.write("<form ENCTYPE=\"multipart/form-data\" method=\"post\">")
190 f.write("<input name=\"file\" type=\"file\"/>")
191 f.write("<input type=\"submit\" value=\"upload\"/></form>\n")
192 f.write("<hr>\n<ul>\n")
193 for name in list:
194 fullname = os.path.join(path, name)
195 displayname = linkname = name
196 # Append / for directories or @ for symbolic links
197 if os.path.isdir(fullname):
198 displayname = name + "/"
199 linkname = name + "/"
200 if os.path.islink(fullname):
201 displayname = name + "@"
202 # Note: a link to a directory displays with @ and links with /
203 f.write('<li><a href="%s">%s</a>\n'
204 % (urllib.quote(linkname), cgi.escape(displayname)))
205 f.write("</ul>\n<hr>\n</body>\n</html>\n")
206 length = f.tell()
207 f.seek(0)
208 self.send_response(200)
209 self.send_header("Content-type", "text/html")
210 self.send_header("Content-Length", str(length))
211 self.end_headers()
212 return f
213
214 def translate_path(self, path):
215 """Translate a /-separated PATH to the local filename syntax.
216
217 Components that mean special things to the local file system
218 (e.g. drive or directory names) are ignored. (XXX They should
219 probably be diagnosed.)
220
221 """
222 # abandon query parameters
223 path = path.split('?',1)[0]
224 path = path.split('#',1)[0]
225 path = posixpath.normpath(urllib.unquote(path))
226 words = path.split('/')
227 words = filter(None, words)
228 path = os.getcwd()
229 for word in words:
230 drive, word = os.path.splitdrive(word)
231 head, word = os.path.split(word)
232 if word in (os.curdir, os.pardir): continue
233 path = os.path.join(path, word)
234 return path
235
236 def copyfile(self, source, outputfile):
237 """Copy all data between two file objects.
238
239 The SOURCE argument is a file object open for reading
240 (or anything with a read() method) and the DESTINATION
241 argument is a file object open for writing (or
242 anything with a write() method).
243
244 The only reason for overriding this would be to change
245 the block size or perhaps to replace newlines by CRLF
246 -- note however that this the default server uses this
247 to copy binary data as well.
248
249 """
250 shutil.copyfileobj(source, outputfile)
251
252 def guess_type(self, path):
253 """Guess the type of a file.
254
255 Argument is a PATH (a filename).
256
257 Return value is a string of the form type/subtype,
258 usable for a MIME Content-type header.
259
260 The default implementation looks the file's extension
261 up in the table self.extensions_map, using application/octet-stream
262 as a default; however it would be permissible (if
263 slow) to look inside the data to make a better guess.
264
265 """
266
267 base, ext = posixpath.splitext(path)
268 if ext in self.extensions_map:
269 return self.extensions_map[ext]
270 ext = ext.lower()
271 if ext in self.extensions_map:
272 return self.extensions_map[ext]
273 else:
274 return self.extensions_map['']
275
276 if not mimetypes.inited:
277 mimetypes.init() # try to read system mime.types
278 extensions_map = mimetypes.types_map.copy()
279 extensions_map.update({
280 '': 'application/octet-stream', # Default
281 '.py': 'text/plain',
282 '.c': 'text/plain',
283 '.h': 'text/plain',
284 })
285
286
287 def test(HandlerClass = SimpleHTTPRequestHandler,
288 ServerClass = BaseHTTPServer.HTTPServer):
289 BaseHTTPServer.test(HandlerClass, ServerClass)
290
291 if __name__ == '__main__':
292 test()
反馈
创建 by -- ZoomQuiet [2010-05-15 15:34:51]