文档章节

300 行 python 代码的轻量级 HTTPServer 实现文件上传下载

大数据之路
 大数据之路
发布于 2012/08/08 01:51
字数 1736
阅读 14857
收藏 0

 

# !/usr/bin/env python
# coding=utf-8
# http://my.oschina.net/leejun2005/blog/71444

"""
    简介:这是一个 python 写的轻量级的文件共享服务器(基于内置的SimpleHTTPServer模块),
    支持文件上传下载,只要你安装了python(建议版本2.6~2.7,不支持3.x),
    然后去到想要共享的目录下,执行:
        python SimpleHTTPServerWithUpload.py
    或者 python SimpleHTTPServerWithUpload.py filename
"""

"""Simple HTTP Server With Upload.

This module builds on BaseHTTPServer by implementing the standard GET
and HEAD requests in a fairly straightforward manner.

"""

__version__ = "0.2"
__all__ = ["SimpleHTTPRequestHandler"]
__home_page__ = ""

import os, sys, platform, socket, struct, json
import posixpath
import BaseHTTPServer
from SocketServer import ThreadingMixIn
import threading
import urllib, urllib2
import cgi
import shutil
import mimetypes
import re
import time

reload(sys)
sys.setdefaultencoding("utf-8")

try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO

def echoRed(s):
    return "%s[31;1m%s%s[0m" % (chr(27), s, chr(27))

def get_ip_address(ifname=None):
    if sys.platform == 'win32':
        return socket.getaddrinfo(socket.gethostname(), None, socket.AF_INET, socket.SOCK_DGRAM)[-1][4][0]
    else:
        import fcntl
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        return socket.inet_ntoa(fcntl.ioctl(
            s.fileno(),
            0x8915,  # SIOCGIFADDR
            struct.pack('256s', ifname[:15])
        )[20:24])

class GetWanIp:
    def getip(self):
        try:
            myip = get_ip_address(ifname="eth0")
            # myip = self.visit("http://ipinfo.io")
        except Exception, e:
            print str(e)
            myip = "127.0.0.1"
        return myip
    def visit(self ,url):
        # req = urllib2.Request(url)
        # values = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537',
        #            'Referer': 'http://ip.taobao.com/ipSearch.php',
        #            'ip': 'myip'
        #         }
        # data = urllib.urlencode(values)
        import requests
        content = requests.get('http://ipinfo.io').content
        print content
        json_content = json.loads(content)
        return json_content["ip"]

def showTips():
    print ""
    print '----------------------------------------------------------------------->> '
    try:
        port = 12345
        file_name = sys.argv[1]
        print '-------->> Please visit files or dirs use Chrome Browser:     http://' + GetWanIp().getip() + ':' + str(port)
        print "-------->> Also, You can wget download the file:    " + echoRed("wget http://" + GetWanIp().getip() + ':' + str(port) + "/" + file_name)
    except Exception, e:
        print 'You have not give a filename, plase use Chrome Browser:     http://' + GetWanIp().getip() + ':' + str(port)
        print "You can give a filename and wget download the file, Usage: " + echoRed("pywget filename")

    if not 1024 < port < 65535:  port = 8080
    print '----------------------------------------------------------------------->> '

    print ""
    # serveraddr = ('', port)
    return ('', port)


serveraddr = showTips()


def sizeof_fmt(num):
    for x in ['bytes', 'KB', 'MB', 'GB']:
        if num < 1024.0:
            return "%3.1f%s" % (num, x)
        num /= 1024.0
    return "%3.1f%s" % (num, 'TB')


def modification_date(filename):
    return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(os.path.getmtime(filename)))


class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    """Simple HTTP request handler with GET/HEAD/POST commands.

    This serves files from the current directory and any of its
    subdirectories.  The MIME type for files is determined by
    calling the .guess_type() method. And can reveive file uploaded
    by client.

    The GET/HEAD/POST requests are identical except that the HEAD
    request omits the actual contents of the file.

    """

    server_version = "SimpleHTTPWithUpload/" + __version__

    def do_GET(self):
        """Serve a GET request."""
        # print "....................", threading.currentThread().getName()
        f = self.send_head()
        if f:
            self.copyfile(f, self.wfile)
            f.close()

    def do_HEAD(self):
        """Serve a HEAD request."""
        f = self.send_head()
        if f:
            f.close()

    def do_POST(self):
        """Serve a POST request."""
        r, info = self.deal_post_data()
        print r, info, "by: ", self.client_address
        f = StringIO()
        f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
        f.write("<html>\n<title>Upload Result Page</title>\n")
        f.write("<body>\n<h2>Upload Result Page</h2>\n")
        f.write("<hr>\n")
        if r:
            f.write("<strong>Success:</strong>")
        else:
            f.write("<strong>Failed:</strong>")
        f.write(info)
        f.write("<br><a href=\"%s\">back</a>" % self.headers['referer'])
        f.write("<hr><small>Powered By: bones7456, check new version at ")
        f.write("<a href=\"http://li2z.cn/?s=SimpleHTTPServerWithUpload\">")
        f.write("here</a>.</small></body>\n</html>\n")
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        if f:
            self.copyfile(f, self.wfile)
            f.close()

    def deal_post_data(self):
        boundary = self.headers.plisttext.split("=")[1]
        remainbytes = int(self.headers['content-length'])
        line = self.rfile.readline()
        remainbytes -= len(line)
        if not boundary in line:
            return (False, "Content NOT begin with boundary")
        line = self.rfile.readline()
        remainbytes -= len(line)
        fn = re.findall(r'Content-Disposition.*name="file"; filename="(.*)"', line)
        if not fn:
            return (False, "Can't find out file name...")
        path = self.translate_path(self.path)
        osType = platform.system()
        try:
            if osType == "Linux":
                fn = os.path.join(path, fn[0].decode('gbk').encode('utf-8'))
            else:
                fn = os.path.join(path, fn[0])
        except Exception, e:
            return (False, "文件名请不要用中文,或者使用IE上传中文名的文件。")
        while os.path.exists(fn):
            fn += "_"
        line = self.rfile.readline()
        remainbytes -= len(line)
        line = self.rfile.readline()
        remainbytes -= len(line)
        try:
            out = open(fn, 'wb')
        except IOError:
            return (False, "Can't create file to write, do you have permission to write?")

        preline = self.rfile.readline()
        remainbytes -= len(preline)
        while remainbytes > 0:
            line = self.rfile.readline()
            remainbytes -= len(line)
            if boundary in line:
                preline = preline[0:-1]
                if preline.endswith('\r'):
                    preline = preline[0:-1]
                out.write(preline)
                out.close()
                return (True, "File '%s' upload success!" % fn)
            else:
                out.write(preline)
                preline = line
        return (False, "Unexpect Ends of data.")

    def send_head(self):
        """Common code for GET and HEAD commands.

        This sends the response code and MIME headers.

        Return value is either a file object (which has to be copied
        to the outputfile by the caller unless the command was HEAD,
        and must be closed by the caller under all circumstances), or
        None, in which case the caller has nothing further to do.

        """
        path = self.translate_path(self.path)
        f = None
        if os.path.isdir(path):
            if not self.path.endswith('/'):
                # redirect browser - doing basically what apache does
                self.send_response(301)
                self.send_header("Location", self.path + "/")
                self.end_headers()
                return None
            for index in "index.html", "index.htm":
                index = os.path.join(path, index)
                if os.path.exists(index):
                    path = index
                    break
            else:
                return self.list_directory(path)
        ctype = self.guess_type(path)
        try:
            # Always read in binary mode. Opening files in text mode may cause
            # newline translations, making the actual size of the content
            # transmitted *less* than the content-length!
            f = open(path, 'rb')
        except IOError:
            self.send_error(404, "File not found")
            return None
        self.send_response(200)
        self.send_header("Content-type", ctype)
        fs = os.fstat(f.fileno())
        self.send_header("Content-Length", str(fs[6]))
        self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
        self.end_headers()
        return f

    def list_directory(self, path):
        """Helper to produce a directory listing (absent index.html).

        Return value is either a file object, or None (indicating an
        error).  In either case, the headers are sent, making the
        interface the same as for send_head().

        """
        try:
            list = os.listdir(path)
        except os.error:
            self.send_error(404, "No permission to list directory")
            return None
        list.sort(key=lambda a: a.lower())
        f = StringIO()
        displaypath = cgi.escape(urllib.unquote(self.path))
        f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
        f.write("<html>\n<title>Directory listing for %s</title>\n" % displaypath)
        f.write("<body>\n<h2>Directory listing for %s</h2>\n" % displaypath)
        f.write("<hr>\n")
        f.write("<form ENCTYPE=\"multipart/form-data\" method=\"post\">")
        f.write("<input name=\"file\" type=\"file\"/>")
        f.write("<input type=\"submit\" value=\"upload\"/>")
        f.write("&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp")
        f.write("<input type=\"button\" value=\"HomePage\" onClick=\"location='/'\">")
        f.write("</form>\n")
        f.write("<hr>\n<ul>\n")
        for name in list:
            fullname = os.path.join(path, name)
            colorName = displayname = linkname = name
            # Append / for directories or @ for symbolic links
            if os.path.isdir(fullname):
                colorName = '<span style="background-color: #CEFFCE;">' + name + '/</span>'
                displayname = name
                linkname = name + "/"
            if os.path.islink(fullname):
                colorName = '<span style="background-color: #FFBFFF;">' + name + '@</span>'
                displayname = name
                # Note: a link to a directory displays with @ and links with /
            filename = os.getcwd() + '/' + displaypath + displayname
            f.write(
                '<table><tr><td width="60%%"><a href="%s">%s</a></td><td width="20%%">%s</td><td width="20%%">%s</td></tr>\n'
                % (urllib.quote(linkname), colorName,
                   sizeof_fmt(os.path.getsize(filename)), modification_date(filename)))
        f.write("</table>\n<hr>\n</body>\n</html>\n")
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        return f

    def translate_path(self, path):
        """Translate a /-separated PATH to the local filename syntax.

        Components that mean special things to the local file system
        (e.g. drive or directory names) are ignored.  (XXX They should
        probably be diagnosed.)

        """
        # abandon query parameters
        path = path.split('?', 1)[0]
        path = path.split('#', 1)[0]
        path = posixpath.normpath(urllib.unquote(path))
        words = path.split('/')
        words = filter(None, words)
        path = os.getcwd()
        for word in words:
            drive, word = os.path.splitdrive(word)
            head, word = os.path.split(word)
            if word in (os.curdir, os.pardir): continue
            path = os.path.join(path, word)
        return path

    def copyfile(self, source, outputfile):
        """Copy all data between two file objects.

        The SOURCE argument is a file object open for reading
        (or anything with a read() method) and the DESTINATION
        argument is a file object open for writing (or
        anything with a write() method).

        The only reason for overriding this would be to change
        the block size or perhaps to replace newlines by CRLF
        -- note however that this the default server uses this
        to copy binary data as well.

        """
        shutil.copyfileobj(source, outputfile)

    def guess_type(self, path):
        """Guess the type of a file.

        Argument is a PATH (a filename).

        Return value is a string of the form type/subtype,
        usable for a MIME Content-type header.

        The default implementation looks the file's extension
        up in the table self.extensions_map, using application/octet-stream
        as a default; however it would be permissible (if
        slow) to look inside the data to make a better guess.

        """

        base, ext = posixpath.splitext(path)
        if ext in self.extensions_map:
            return self.extensions_map[ext]
        ext = ext.lower()
        if ext in self.extensions_map:
            return self.extensions_map[ext]
        else:
            return self.extensions_map['']

    if not mimetypes.inited:
        mimetypes.init()  # try to read system mime.types
    extensions_map = mimetypes.types_map.copy()
    extensions_map.update({
        '': 'application/octet-stream',  # Default
        '.py': 'text/plain',
        '.c': 'text/plain',
        '.h': 'text/plain',
    })


class ThreadingServer(ThreadingMixIn, BaseHTTPServer.HTTPServer):
    pass


def test(HandlerClass=SimpleHTTPRequestHandler,
         ServerClass=BaseHTTPServer.HTTPServer):
    BaseHTTPServer.test(HandlerClass, ServerClass)


if __name__ == '__main__':
    # test()

    # 单线程
    # srvr = BaseHTTPServer.HTTPServer(serveraddr, SimpleHTTPRequestHandler)

    # 多线程
    srvr = ThreadingServer(serveraddr, SimpleHTTPRequestHandler)

    srvr.serve_forever()

REF: 

[1] httpserver

httpserver
=======================================
This httpserver is a enhanced version of SimpleHTTPServer. 
It was write in python, I use some code from bottle[https://github.com/defnull/bottle] 
It support resuming download, you can set the document root, it has more 
friendly error hit, and it can handle mimetype gracefully

https://github.com/lerry/httpserver/blob/master/httpserver.py

[2] py2_SimpleHTTPServerWithUpload

https://github.com/jJayyyyyyy/cs/blob/master/just%20for%20fun/file_transfer/http/py2_SimpleHTTPServerWithUpload/SimpleHTTPServerWithUpload.py

[3] py3_SimpleHTTPServerWithUpload.py

https://github.com/jJayyyyyyy/cs/blob/master/just%20for%20fun/file_transfer/http/py3_SimpleHTTPServerWithUpload.py

[4] python3重写SimpleHTTPServerWithUpload

https://jjayyyyyyy.github.io/2016/10/07/reWrite_SimpleHTTPServerWithUpload_with_python3.html

[5] 支持upload文件的SimpleHTTPServer

http://buptguo.com/2015/11/07/simplehttpserver-with-upload-file/

本文转载自:https://github.com/tualatrix/tools/blob/master/SimpleHTTPServerWithUpload.py

共有 人打赏支持
大数据之路
粉丝 1542
博文 516
码字总数 343694
作品 0
武汉
架构师
私信 提问
加载中

评论(9)

不得了啦
不得了啦
赞赞赞!!!
开源中国首席打酱油啊哎滴
开源中国首席打酱油啊哎滴
可以了 ,python3版本13
https://gist.github.com/0312birdzhang/a56566e6395d14f10c96
开源中国首席打酱油啊哎滴
开源中国首席打酱油啊哎滴
楼主,我改了一下 python3版本的https://gist.github.com/7dc7b8fde43b64ecd004.git
现在还不能上传文件,查看下载是可以的
_帅的被人砍
_帅的被人砍
找不到 module fcntl
luxf
luxf
用了 3Q
大数据之路
大数据之路

引用来自“久欠幸福”的评论

引用来自“xrzs”的评论

引用来自“久欠幸福”的评论

找不到 module fcntl

请检查版本:2.7+ 是没问题的。。。

我的是2.7.3,我比对了一下文章里面的代码与 github 中的代码,发现代码不一样,github里面的代码是没有问题的,本文中的代码貌似不行,其中 53行 import fcntl 在源码中是没有的

该脚本我增加了一些功能,如不需要,请将相应内容删除即可。
久欠幸福
久欠幸福

引用来自“xrzs”的评论

引用来自“久欠幸福”的评论

找不到 module fcntl

请检查版本:2.7+ 是没问题的。。。

我的是2.7.3,我比对了一下文章里面的代码与 github 中的代码,发现代码不一样,github里面的代码是没有问题的,本文中的代码貌似不行,其中 53行 import fcntl 在源码中是没有的
大数据之路
大数据之路

引用来自“久欠幸福”的评论

找不到 module fcntl

请检查版本:2.7+ 是没问题的。。。
久欠幸福
久欠幸福
找不到 module fcntl
一个轻量级的框架

试了一个HTTPServer,RequestHandle的数据响应服务,响应速度比较慢(只是还可以接受而已);又试了一个SocketServer - Client 响应非常快可以作为一组分布式应用的数据服务内核;Web-Client的...

Tim Spac
2010/04/25
223
0
15 个简单、有趣而实用的 单行 HTTP Server

不少语言或服务开发框架都内置了简单的 Web Server 供我们方便的调试使用。比如有时候我们需要调试单个 PHP 页面而不想搭建一套完整的 PHP 环境,亦或者我们只是临时的共享一下文件而不想搭建...

xrzs
2016/05/15
3.4K
0
初探Python CGI:搭建CGI编程调试环境

“CGI(Common Gateway Interface,通用网关接口) 是Web 服务器运行时外部程序的规范,按CGI 编写的程序可以扩展服务器功能。”(百度百科)Python语言自带CGI处理模块,可以通过编写python ...

SamYjy
12/02
0
0
Python全栈 项目(HTTPServer、PiP使用)

pip是Python官方推荐的包管理工具 属于python的一部分 pip的使用 pip的安装 sudo apt-get install python-pip sudo apt-get install python3-pip 安装包 pip3 install package 升级 pip3 ins......

巴黎香榭
08/28
0
0
利用Python实现wiki中文语料的word2vec模型构建

本实例主要介绍的是选取wiki中文语料,并使用python完成Word2vec模型构建的实践过程,不包含原理部分,旨在一步一步的了解自然语言处理的基本方法和步骤。文章主要包含了开发环境准备、数据的...

atLee
2017/05/04
0
0

没有更多内容

加载失败,请刷新页面

加载更多

保护Hadoop集群三大方法

大约在两年前,开源数据库解决方案MongoDB以及Hadoop曾遭受过大量恶意攻击,这些攻击后被统称为“勒索软件”,因为其攻击者会擦除或加密数据,然后向被攻击者索要资金以恢复数据。自今年以来...

linuxCool
13分钟前
1
0
开源 java CMS - FreeCMS2.8会员我的留言

项目地址:http://www.freeteam.cn/ 我的留言 从左侧管理菜单点击我的留言进入。在这里可以查看当前登录会员的所有留言记录。 查看留言 点击留言标题可以查看留言详细内容。 删除留言 选择留...

freeteam
33分钟前
2
0
OSChina 周五乱弹 —— 这就是不要女朋友的理由

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @狄安娜的猫 :分享丁家鑫的单曲《丁家鑫 - 克罗地亚狂想曲 - 古筝remix》 《丁家鑫 - 克罗地亚狂想曲 - 古筝remix》 手机党少年们想听歌,请...

小小编辑
今天
690
19
CentOS配置Tomcat监听80端口,虚拟主机

Tomcat更改默认端口为80 更改的配置文件是: /usr/local/tomcat/conf/server.xml [root@test-a ~]# vim /usr/local/tomcat/conf/server.xml # 找到 Connector port="8080" protocol="HTTP/1......

野雪球
今天
6
0
《稻盛和夫经营学》读后感心得体会3180字范文

《稻盛和夫经营学》读后感心得体会3180字范文: 一代日本经营之圣稻盛和夫凭借刻苦勤奋的精神以及深植于佛教的商业道德准则,成为了“佛系”企业家的代表人物。在《稻盛和夫经营学》“领导人...

原创小博客
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部