启动一个支持文件上传的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()
乐趣和分享!