MiscItems/2010-05-15

一个支持上传的简单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!

截图如下: http://li2z.cn/wp-content/uploads/2010/05/SimpleHTTPServerWithUpload.png

,单文件、零配置,直接用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]

MiscItems/2010-05-15 (last edited 2010-05-15 15:34:51 by ZoomQuiet)