启动一个支持文件上传的HTTP-Server

Python实现,源码来自网络,代码内部有作者信息。

HTTP方式共享文件,对于不需要用户名和密码验证的系统非常方便。通过浏览器就可以实现文件上传和下载。非常适合用作测试系统的脚手架。

对于系统使用curl命令行也可以轻松实现文件上传。wget实现文件下载。

nohup python SimpleHTTPServerWithUpload.py 8088 &  # 在8088端口启动这个http-server服务

 

  1 #!/usr/bin/env python
  2  
  3 """Simple HTTP Server With Upload.
  4  
  5 This module builds on BaseHTTPServer by implementing the standard GET
  6 and HEAD requests in a fairly straightforward manner.
  7  
  8 """
  9  
 10  
 11 __version__ = "0.1"
 12 __all__ = ["SimpleHTTPRequestHandler"]
 13 __author__ = "bones7456"
 14 __home_page__ = "http://li2z.cn/"
 15  
 16 import os
 17 import posixpath
 18 import BaseHTTPServer
 19 import urllib
 20 import cgi
 21 import shutil
 22 import mimetypes
 23 import re
 24 try:
 25     from cStringIO import StringIO
 26 except ImportError:
 27     from StringIO import StringIO
 28  
 29  
 30 class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
 31  
 32     """Simple HTTP request handler with GET/HEAD/POST commands.
 33  
 34     This serves files from the current directory and any of its
 35     subdirectories.  The MIME type for files is determined by
 36     calling the .guess_type() method. And can reveive file uploaded
 37     by client.
 38  
 39     The GET/HEAD/POST requests are identical except that the HEAD
 40     request omits the actual contents of the file.
 41  
 42     """
 43  
 44     server_version = "SimpleHTTPWithUpload/" + __version__
 45  
 46     def do_GET(self):
 47         """Serve a GET request."""
 48         f = self.send_head()
 49         if f:
 50             self.copyfile(f, self.wfile)
 51             f.close()
 52  
 53     def do_HEAD(self):
 54         """Serve a HEAD request."""
 55         f = self.send_head()
 56         if f:
 57             f.close()
 58  
 59     def do_POST(self):
 60         """Serve a POST request."""
 61         r, info = self.deal_post_data()
 62         print r, info, "by: ", self.client_address
 63         f = StringIO()
 64         f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
 65         f.write("<html>\n<title>Upload Result Page</title>\n")
 66         f.write("<body>\n<h2>Upload Result Page</h2>\n")
 67         f.write("<hr>\n")
 68         if r:
 69             f.write("<strong>Success:</strong>")
 70         else:
 71             f.write("<strong>Failed:</strong>")
 72         f.write(info)
 73         f.write("<br><a href=\"%s\">back</a>" % self.headers['referer'])
 74         f.write("<hr><small>Powered By: bones7456, check new version at ")
 75         f.write("<a href=\"http://li2z.cn/?s=SimpleHTTPServerWithUpload\">")
 76         f.write("here</a>.</small></body>\n</html>\n")
 77         length = f.tell()
 78         f.seek(0)
 79         self.send_response(200)
 80         self.send_header("Content-type", "text/html")
 81         self.send_header("Content-Length", str(length))
 82         self.end_headers()
 83         if f:
 84             self.copyfile(f, self.wfile)
 85             f.close()
 86          
 87     def deal_post_data(self):
 88         boundary = self.headers.plisttext.split("=")[1]
 89         remainbytes = int(self.headers['content-length'])
 90         line = self.rfile.readline()
 91         remainbytes -= len(line)
 92         if not boundary in line:
 93             return (False, "Content NOT begin with boundary")
 94         line = self.rfile.readline()
 95         remainbytes -= len(line)
 96         fn = re.findall(r'Content-Disposition.*name="file"; filename="(.*)"', line)
 97         if not fn:
 98             return (False, "Can't find out file name...")
 99         path = self.translate_path(self.path)
100         fn = os.path.join(path, fn[0])
101         while os.path.exists(fn):
102             fn += "_"
103         line = self.rfile.readline()
104         remainbytes -= len(line)
105         line = self.rfile.readline()
106         remainbytes -= len(line)
107         try:
108             out = open(fn, 'wb')
109         except IOError:
110             return (False, "Can't create file to write, do you have permission to write?")
111                  
112         preline = self.rfile.readline()
113         remainbytes -= len(preline)
114         while remainbytes > 0:
115             line = self.rfile.readline()
116             remainbytes -= len(line)
117             if boundary in line:
118                 preline = preline[0:-1]
119                 if preline.endswith('\r'):
120                     preline = preline[0:-1]
121                 out.write(preline)
122                 out.close()
123                 return (True, "File '%s' upload success!" % fn)
124             else:
125                 out.write(preline)
126                 preline = line
127         return (False, "Unexpect Ends of data.")
128  
129     def send_head(self):
130         """Common code for GET and HEAD commands.
131  
132         This sends the response code and MIME headers.
133  
134         Return value is either a file object (which has to be copied
135         to the outputfile by the caller unless the command was HEAD,
136         and must be closed by the caller under all circumstances), or
137         None, in which case the caller has nothing further to do.
138  
139         """
140         path = self.translate_path(self.path)
141         f = None
142         if os.path.isdir(path):
143             if not self.path.endswith('/'):
144                 # redirect browser - doing basically what apache does
145                 self.send_response(301)
146                 self.send_header("Location", self.path + "/")
147                 self.end_headers()
148                 return None
149             for index in "index.html", "index.htm":
150                 index = os.path.join(path, index)
151                 if os.path.exists(index):
152                     path = index
153                     break
154             else:
155                 return self.list_directory(path)
156         ctype = self.guess_type(path)
157         try:
158             # Always read in binary mode. Opening files in text mode may cause
159             # newline translations, making the actual size of the content
160             # transmitted *less* than the content-length!
161             f = open(path, 'rb')
162         except IOError:
163             self.send_error(404, "File not found")
164             return None
165         self.send_response(200)
166         self.send_header("Content-type", ctype)
167         fs = os.fstat(f.fileno())
168         self.send_header("Content-Length", str(fs[6]))
169         self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
170         self.end_headers()
171         return f
172  
173     def list_directory(self, path):
174         """Helper to produce a directory listing (absent index.html).
175  
176         Return value is either a file object, or None (indicating an
177         error).  In either case, the headers are sent, making the
178         interface the same as for send_head().
179  
180         """
181         try:
182             list = os.listdir(path)
183         except os.error:
184             self.send_error(404, "No permission to list directory")
185             return None
186         list.sort(key=lambda a: a.lower())
187         f = StringIO()
188         displaypath = cgi.escape(urllib.unquote(self.path))
189         f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
190         f.write("<html>\n<title>Directory listing for %s</title>\n" % displaypath)
191         f.write("<body>\n<h2>Directory listing for %s</h2>\n" % displaypath)
192         f.write("<hr>\n")
193         f.write("<form ENCTYPE=\"multipart/form-data\" method=\"post\">")
194         f.write("<input name=\"file\" type=\"file\"/>")
195         f.write("<input type=\"submit\" value=\"upload\"/></form>\n")
196         f.write("<hr>\n<ul>\n")
197         for name in list:
198             fullname = os.path.join(path, name)
199             displayname = linkname = name
200             # Append / for directories or @ for symbolic links
201             if os.path.isdir(fullname):
202                 displayname = name + "/"
203                 linkname = name + "/"
204             if os.path.islink(fullname):
205                 displayname = name + "@"
206                 # Note: a link to a directory displays with @ and links with /
207             f.write('<li><a href="%s">%s</a>\n'
208                     % (urllib.quote(linkname), cgi.escape(displayname)))
209         f.write("</ul>\n<hr>\n</body>\n</html>\n")
210         length = f.tell()
211         f.seek(0)
212         self.send_response(200)
213         self.send_header("Content-type", "text/html")
214         self.send_header("Content-Length", str(length))
215         self.end_headers()
216         return f
217  
218     def translate_path(self, path):
219         """Translate a /-separated PATH to the local filename syntax.
220  
221         Components that mean special things to the local file system
222         (e.g. drive or directory names) are ignored.  (XXX They should
223         probably be diagnosed.)
224  
225         """
226         # abandon query parameters
227         path = path.split('?',1)[0]
228         path = path.split('#',1)[0]
229         path = posixpath.normpath(urllib.unquote(path))
230         words = path.split('/')
231         words = filter(None, words)
232         path = os.getcwd()
233         for word in words:
234             drive, word = os.path.splitdrive(word)
235             head, word = os.path.split(word)
236             if word in (os.curdir, os.pardir): continue
237             path = os.path.join(path, word)
238         return path
239  
240     def copyfile(self, source, outputfile):
241         """Copy all data between two file objects.
242  
243         The SOURCE argument is a file object open for reading
244         (or anything with a read() method) and the DESTINATION
245         argument is a file object open for writing (or
246         anything with a write() method).
247  
248         The only reason for overriding this would be to change
249         the block size or perhaps to replace newlines by CRLF
250         -- note however that this the default server uses this
251         to copy binary data as well.
252  
253         """
254         shutil.copyfileobj(source, outputfile)
255  
256     def guess_type(self, path):
257         """Guess the type of a file.
258  
259         Argument is a PATH (a filename).
260  
261         Return value is a string of the form type/subtype,
262         usable for a MIME Content-type header.
263  
264         The default implementation looks the file's extension
265         up in the table self.extensions_map, using application/octet-stream
266         as a default; however it would be permissible (if
267         slow) to look inside the data to make a better guess.
268  
269         """
270  
271         base, ext = posixpath.splitext(path)
272         if ext in self.extensions_map:
273             return self.extensions_map[ext]
274         ext = ext.lower()
275         if ext in self.extensions_map:
276             return self.extensions_map[ext]
277         else:
278             return self.extensions_map['']
279  
280     if not mimetypes.inited:
281         mimetypes.init() # try to read system mime.types
282     extensions_map = mimetypes.types_map.copy()
283     extensions_map.update({
284         '': 'application/octet-stream', # Default
285         '.py': 'text/plain',
286         '.c': 'text/plain',
287         '.h': 'text/plain',
288         })
289  
290  
291 def test(HandlerClass = SimpleHTTPRequestHandler,
292          ServerClass = BaseHTTPServer.HTTPServer):
293     BaseHTTPServer.test(HandlerClass, ServerClass)
294  
295 if __name__ == '__main__':
296     test()

 

posted @ 2016-01-11 20:17  极客堂  阅读(5663)  评论(0编辑  收藏  举报