python HTTP Server 文件上传与下载--uploadHttp2.py

python HTTP Server 文件上传与下载

实现在局域网(同一WIFI下) 文件上传与下载

该模块通过实现标准GET在BaseHTTPServer上构建
和HEAD请求。(将所有代码粘贴到同一个py文件中,即可使用)

所需包

基于python3版本实现,python2版本无涉猎

import os
import sys
import argparse
import posixpath

try:
    from html import escape
except ImportError:
    from cgi import escape
import shutil
import mimetypes
import re
import signal
from io import StringIO, BytesIO

if sys.version_info.major == 3:
    # Python3
    from urllib.parse import quote
    from urllib.parse import unquote
    from http.server import HTTPServer
    from http.server import BaseHTTPRequestHandler

基本类 简单HTTP服务类

带有GET/HEAD/POST命令的简单HTTP请求处理程序。

这将提供当前目录中的文件及其子目录。
文件的MIME类型由调用.gues_type()方法。
并且可以接收上传的文件由客户提供。

GET/HEAD/POST请求是相同的,除了HEAD请求忽略文件的实际内容。

class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
    server_version = "simple_http_server/" + __version__

    def do_GET(self):
        """Serve a GET request."""
        fd = self.send_head()
        if fd:
            shutil.copyfileobj(fd, self.wfile)
            fd.close()

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

    def do_POST(self):
        """Serve a POST request."""
        r, info = self.deal_post_data()
        print(r, info, "by: ", self.client_address)
        f = BytesIO()
        f.write(b'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
        f.write(b"<html>\n<title>Upload Result Page</title>\n")
        f.write(b"<body>\n<h2>Upload Result Page</h2>\n")
        f.write(b"<hr>\n")
        if r:
            f.write(b"<strong>Success:</strong>")
        else:
            f.write(b"<strong>Failed:</strong>")
        f.write(info.encode('utf-8'))
        f.write(b"<br><a href=\".\">back</a>")
        f.write(b"<hr><small>Powered By: freelamb, check new version at ")
        # 原始代码地址 可以参考
        f.write(b"<a href=\"https://github.com/freelamb/simple_http_server\">")
        f.write(b"here</a>.</small></body>\n</html>\n")
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html;charset=utf-8")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        if f:
            shutil.copyfileobj(f, self.wfile)
            f.close()

    def deal_post_data(self):
        boundary = self.headers["Content-Type"].split("=")[1].encode('utf-8')
        remain_bytes = int(self.headers['content-length'])
        line = self.rfile.readline()
        remain_bytes -= len(line)
        if boundary not in line:
            return False, "Content NOT begin with boundary"
        line = self.rfile.readline()
        remain_bytes -= len(line)
        fn = re.findall(r'Content-Disposition.*name="file"; filename="(.*)"', line.decode('utf-8'))
        if not fn:
            return False, "Can't find out file name..."
        path = translate_path(self.path)
        fn = os.path.join(path, fn[0])
        while os.path.exists(fn):
            fn += "_"
        line = self.rfile.readline()
        remain_bytes -= len(line)
        line = self.rfile.readline()
        remain_bytes -= len(line)
        try:
            out = open(fn, 'wb')
        except IOError:
            return False, "Can't create file to write, do you have permission to write?"

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

    def send_head(self):
        """
        GET和HEAD命令的通用代码。
		这将发送响应代码和MIME标头。
		返回值要么是文件对象
		(除非命令是HEAD,否则调用方必须将其复制到输出文件中,
		并且在任何情况下都必须由调用方关闭),
		要么是None,在这种情况下,调用方无需进一步操作。
        """
        path = translate_path(self.path)
        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)
        content_type = self.guess_type(path)
        try:
			#始终以二进制模式读取。以文本模式打开文件可能会导致
			#换行翻译,使内容的实际大小
			#传输*小于*内容长度!
            f = open(path, 'rb')
        except IOError:
            self.send_error(404, "File not found")
            return None
        self.send_response(200)
        self.send_header("Content-type", content_type)
        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):
        """
        帮助程序生成目录列表(缺少index.html)。
		返回值为file对象或None(表示错误)。
		无论哪种情况,都会发送标头接口与send_head()相同。
        """
        try:
            list_dir = os.listdir(path)
        except os.error:
            self.send_error(404, "No permission to list directory")
            return None
        list_dir.sort(key=lambda a: a.lower())
        f = BytesIO()
        display_path = escape(unquote(self.path))
        f.write(b'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
        f.write(b"<html>\n<title>Directory listing for %s</title>\n" % display_path.encode('utf-8'))
        f.write(b"<body>\n<h2>Directory listing for %s</h2>\n" % display_path.encode('utf-8'))
        f.write(b"<hr>\n")
        f.write(b"<form ENCTYPE=\"multipart/form-data\" method=\"post\">")
        f.write(b"<input name=\"file\" type=\"file\"/>")
        f.write(b"<input type=\"submit\" value=\"upload\"/></form>\n")
        f.write(b"<hr>\n<ul>\n")
        for name in list_dir:
            fullname = os.path.join(path, name)
            display_name = linkname = name
            # Append / for directories or @ for symbolic links
            if os.path.isdir(fullname):
                display_name = name + "/"
                linkname = name + "/"
            if os.path.islink(fullname):
                display_name = name + "@"
                # Note: a link to a directory displays with @ and links with /
            f.write(
                b'<li><a href="%s">%s</a>\n' % (quote(linkname).encode('utf-8'), escape(display_name).encode('utf-8')))
        f.write(b"</ul>\n<hr>\n</body>\n</html>\n")
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html;charset=utf-8")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        return f

    def guess_type(self, path):
        """
        参数是PATH(文件名)。
		返回值是表单类型/子类型的字符串,
		可用于MIME内容类型标头。
		默认实现在self.extensions_map表中查找文件的扩展名,默认使用application/octet流;
        """
        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',
    })

文件路径处理

将/分隔的PATH转换为本地文件名语法。
对本地文件系统有特殊意义的组件(例如驱动器或目录名),那么可能会被阻止或诊断。

def translate_path(path):
    # abandon query parameters
    path = path.split('?', 1)[0]
    path = path.split('#', 1)[0]
    path = posixpath.normpath(unquote(path))
    words = path.split('/')
    words = filter(None, words)
    # 获取你的py文件存放的路径
    path = os.getcwd()
    # 可在此自定义路径(如果有其路径)
    path = path+"/file_xxx/xxx"
    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

信息提醒

如果HTTP Server被关闭

def signal_handler(signal, frame):
    print("You choose to stop me.")
    exit()

HTTP Server 初始化

设置HTTP Server初始数值,基于自己电脑设置。
对于IP来说,双方ip设置应一样。

# 版本设置 自定义
__version__ = "0.3.1"
def _argparse():
    parser = argparse.ArgumentParser()
    # 一般用户电脑用做服务器,每次开机后,ip4地址可能会发生变化
    ip = input("请输入IP地址:")
    parser.add_argument('--bind', '-b', metavar='ADDRESS', default=ip,
                        help='Specify alternate bind address [default: all interfaces]')
    parser.add_argument('--version', '-v', action='version', version=__version__)
    parser.add_argument('port', action='store', default=8000, type=int, nargs='?',
                        help='Specify alternate port [default: 8000]')
    return parser.parse_args()

启用HTTP Server

py程序启动后,会输出网址,点击后,会自动进入HTTP服务,可以进行文件传输操作。

def main():
    args = _argparse()
    # print(args)
    server_address = (args.bind, args.port)
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)
    httpd = HTTPServer(server_address, SimpleHTTPRequestHandler)
    server = httpd.socket.getsockname()
    print("server_version: " + SimpleHTTPRequestHandler.server_version + ", python_version: " + SimpleHTTPRequestHandler.sys_version)
    print("sys encoding: " + sys.getdefaultencoding())
    print("Serving http on: " + str(server[0]) + ", port: " + str(server[1]) + " ... (http://" + server[0] + ":" + str(server[1]) + "/)")
    httpd.serve_forever()


if __name__ == '__main__':
    main()

 

完整代码

import os
import sys
import argparse
import posixpath

try:
    from html import escape
except ImportError:
    from cgi import escape
import shutil
import mimetypes
import re
import signal
from io import StringIO, BytesIO

if sys.version_info.major == 3:
    # Python3
    from urllib.parse import quote
    from urllib.parse import unquote
    from http.server import HTTPServer
    from http.server import BaseHTTPRequestHandler

# 版本设置 自定义
__version__ = "0.3.1"
def _argparse():
    parser = argparse.ArgumentParser()
    # 一般用户电脑用做服务器,每次开机后,ip4地址可能会发生变化
    #ip = input("请输入IP地址:")
    parser.add_argument('--bind', '--b', metavar='ADDRESS', default="172.21.137.235",
                        help='Specify alternate bind address [default: all interfaces]')
    parser.add_argument('--version', '-v', action='version', version=__version__)
    parser.add_argument('port', action='store', default=8000, type=int, nargs='?',
                        help='Specify alternate port [default: 8000]')
    return parser.parse_args()

class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
    server_version = "simple_http_server/" + __version__

    def do_GET(self):
        """Serve a GET request."""
        fd = self.send_head()
        if fd:
            shutil.copyfileobj(fd, self.wfile)
            fd.close()

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

    def do_POST(self):
        """Serve a POST request."""
        r, info = self.deal_post_data()
        print(r, info, "by: ", self.client_address)
        f = BytesIO()
        f.write(b'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
        f.write(b"<html>\n<title>Upload Result Page</title>\n")
        f.write(b"<body>\n<h2>Upload Result Page</h2>\n")
        f.write(b"<hr>\n")
        if r:
            f.write(b"<strong>Success:</strong>")
        else:
            f.write(b"<strong>Failed:</strong>")
        f.write(info.encode('utf-8'))
        f.write(b"<br><a href=\".\">back</a>")
        f.write(b"<hr><small>Powered By: freelamb, check new version at ")
        # 原始代码地址 可以参考
        f.write(b"<a href=\"https://github.com/freelamb/simple_http_server\">")
        f.write(b"here</a>.</small></body>\n</html>\n")
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html;charset=utf-8")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        if f:
            shutil.copyfileobj(f, self.wfile)
            f.close()

    def deal_post_data(self):
        boundary = self.headers["Content-Type"].split("=")[1].encode('utf-8')
        remain_bytes = int(self.headers['content-length'])
        line = self.rfile.readline()
        remain_bytes -= len(line)
        if boundary not in line:
            return False, "Content NOT begin with boundary"
        line = self.rfile.readline()
        remain_bytes -= len(line)
        fn = re.findall(r'Content-Disposition.*name="file"; filename="(.*)"', line.decode('utf-8'))
        if not fn:
            return False, "Can't find out file name..."
        path = translate_path(self.path)
        fn = os.path.join(path, fn[0])
        while os.path.exists(fn):
            fn += "_"
        line = self.rfile.readline()
        remain_bytes -= len(line)
        line = self.rfile.readline()
        remain_bytes -= len(line)
        try:
            out = open(fn, 'wb')
        except IOError:
            return False, "Can't create file to write, do you have permission to write?"

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

    def send_head(self):
        """
        GET和HEAD命令的通用代码。
        这将发送响应代码和MIME标头。
        返回值要么是文件对象
        (除非命令是HEAD,否则调用方必须将其复制到输出文件中,
        并且在任何情况下都必须由调用方关闭),
        要么是None,在这种情况下,调用方无需进一步操作。
        """
        path = translate_path(self.path)
        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)
        content_type = self.guess_type(path)
        try:
            #始终以二进制模式读取。以文本模式打开文件可能会导致
            #换行翻译,使内容的实际大小
            #传输*小于*内容长度!
            f = open(path, 'rb')
        except IOError:
            self.send_error(404, "File not found")
            return None
        self.send_response(200)
        self.send_header("Content-type", content_type)
        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):
        """
        帮助程序生成目录列表(缺少index.html)。
        返回值为file对象或None(表示错误)。
        无论哪种情况,都会发送标头接口与send_head()相同。
        """
        try:
            list_dir = os.listdir(path)
        except os.error:
            self.send_error(404, "No permission to list directory")
            return None
        list_dir.sort(key=lambda a: a.lower())
        f = BytesIO()
        display_path = escape(unquote(self.path))
        f.write(b'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
        f.write(b"<html>\n<title>Directory listing for %s</title>\n" % display_path.encode('utf-8'))
        f.write(b"<body>\n<h2>Directory listing for %s</h2>\n" % display_path.encode('utf-8'))
        f.write(b"<hr>\n")
        f.write(b"<form ENCTYPE=\"multipart/form-data\" method=\"post\">")
        f.write(b"<input name=\"file\" type=\"file\"/>")
        f.write(b"<input type=\"submit\" value=\"upload\"/></form>\n")
        f.write(b"<hr>\n<ul>\n")
        for name in list_dir:
            fullname = os.path.join(path, name)
            display_name = linkname = name
            # Append / for directories or @ for symbolic links
            if os.path.isdir(fullname):
                display_name = name + "/"
                linkname = name + "/"
            if os.path.islink(fullname):
                display_name = name + "@"
                # Note: a link to a directory displays with @ and links with /
            f.write(
                b'<li><a href="%s">%s</a>\n' % (quote(linkname).encode('utf-8'), escape(display_name).encode('utf-8')))
        f.write(b"</ul>\n<hr>\n</body>\n</html>\n")
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html;charset=utf-8")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        return f

    def guess_type(self, path):
        """
        参数是PATH(文件名)。
        返回值是表单类型/子类型的字符串,
        可用于MIME内容类型标头。
        默认实现在self.extensions_map表中查找文件的扩展名,默认使用application/octet流;
        """
        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',
    })


###########################################

def translate_path(path):
    # abandon query parameters
    path = path.split('?', 1)[0]
    path = path.split('#', 1)[0]
    path = posixpath.normpath(unquote(path))
    words = path.split('/')
    words = filter(None, words)
    # 获取你的py文件存放的路径
    path = os.getcwd()
    # 可在此自定义路径(如果有其路径)
    #path = path+"/file_xxx/xxx"
    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 signal_handler(signal, frame):
    print("You choose to stop me.")
    exit()
    
def main():
    args = _argparse()
    # print(args)
    server_address = (args.bind, args.port)
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)
    httpd = HTTPServer(server_address, SimpleHTTPRequestHandler)
    server = httpd.socket.getsockname()
    print("python_version: " + SimpleHTTPRequestHandler.sys_version)
    print("server_version: " + SimpleHTTPRequestHandler.server_version)
    print("sys encoding: " + sys.getdefaultencoding())
    print("Serving http on: " + str(server[0]) + ", port: " + str(server[1]) + " ... (http://" + server[0] + ":" + str(server[1]) + "/)")
    httpd.serve_forever()


if __name__ == '__main__':
    main()

 

 

【出处】:https://blog.csdn.net/love_wgll/article/details/129155712

=======================================================================================

个人使用

针对以上代码,后续将需要优化:

1)上传大文件时,需要禁用upload按钮,防止多次提交(已完成)

2)使用配置项,优化参数项和帮助

3)translate_path应该放到类内,自动创建文件夹--(已完成)

4)下载文件列表,过滤当前py程序--(已完成)

5)自定义输出日志,打印:日期,调用堆栈信息--(已完成)

6)优化基类的BaseHTTPRequestHandler的send_response方法的服务端打印信息

7)空文件上传时的bug,会上传一个下划线的空文件(已完成,前端不做校验,实现太麻烦了)

8)最小化运行,双击状态栏图标显示界面

版本1

优化说明:

1)路径转换:translate_path方法,作为对象方法,放到类内

2)下载文件列表,过滤当前py程序

import os
import sys
import argparse
import posixpath

try:
    from html import escape
except ImportError:
    from cgi import escape
import shutil
import mimetypes
import re
import signal
from io import StringIO, BytesIO

if sys.version_info.major == 3:
    # Python3
    from urllib.parse import quote
    from urllib.parse import unquote
    from http.server import HTTPServer
    from http.server import BaseHTTPRequestHandler




# 版本设置 自定义
__version__ = "0.3.1"
def _argparse():
    parser = argparse.ArgumentParser()
    # 一般用户电脑用做服务器,每次开机后,ip4地址可能会发生变化
    #ip = input("请输入IP地址:")
    parser.add_argument('--bind', '--b', metavar='ADDRESS', default="172.21.137.235",
                        help='Specify alternate bind address [default: all interfaces]')
    parser.add_argument('--version', '-v', action='version', version=__version__)
    parser.add_argument('port', action='store', default=8000, type=int, nargs='?',
                        help='Specify alternate port [default: 8000]')
    return parser.parse_args()


class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
    server_version = "simple_http_server/" + __version__

    def do_GET(self):
        """Serve a GET request."""
        fd = self.send_head()
        if fd:
            shutil.copyfileobj(fd, self.wfile)
            fd.close()

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

    def do_POST(self):
        """Serve a POST request."""
        r, info = self.deal_post_data()
        print(r, info, "by: ", self.client_address)
        f = BytesIO()
        f.write(b'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
        f.write(b"<html>\n<title>Upload Result Page</title>\n")
        f.write(b"<body>\n<h2>Upload Result Page</h2>\n")
        f.write(b"<hr>\n")
        if r:
            f.write(b"<strong>Success:</strong>")
        else:
            f.write(b"<strong>Failed:</strong>")
        f.write(info.encode('utf-8'))
        f.write(b"<br><a href=\".\">back</a>")
        f.write(b"<hr><small>Powered By: freelamb, check new version at ")
        # 原始代码地址 可以参考
        f.write(b"<a href=\"https://github.com/freelamb/simple_http_server\">")
        f.write(b"here</a>.</small></body>\n</html>\n")
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html;charset=utf-8")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        if f:
            shutil.copyfileobj(f, self.wfile)
            f.close()

    def deal_post_data(self):
        boundary = self.headers["Content-Type"].split("=")[1].encode('utf-8')
        remain_bytes = int(self.headers['content-length'])
        line = self.rfile.readline()
        remain_bytes -= len(line)
        if boundary not in line:
            return False, "Content NOT begin with boundary"
        line = self.rfile.readline()
        remain_bytes -= len(line)
        fn = re.findall(r'Content-Disposition.*name="file"; filename="(.*)"', line.decode('utf-8'))
        if not fn:
            return False, "Can't find out file name..."
        path = self.translate_path(self.path)
        fn = os.path.join(path, fn[0])
        while os.path.exists(fn):
            fn += "_"
        line = self.rfile.readline()
        remain_bytes -= len(line)
        line = self.rfile.readline()
        remain_bytes -= len(line)
        try:
            out = open(fn, 'wb')
        except IOError:
            return False, "Can't create file to write, do you have permission to write?"

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

    def send_head(self):
        """
        GET和HEAD命令的通用代码。
        这将发送响应代码和MIME标头。
        返回值要么是文件对象
        (除非命令是HEAD,否则调用方必须将其复制到输出文件中,
        并且在任何情况下都必须由调用方关闭),
        要么是None,在这种情况下,调用方无需进一步操作。
        """
        path = self.translate_path(self.path)
        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)
        content_type = self.guess_type(path)
        try:
            #始终以二进制模式读取。以文本模式打开文件可能会导致
            #换行翻译,使内容的实际大小
            #传输*小于*内容长度!
            f = open(path, 'rb')
        except IOError:
            self.send_error(404, "File not found")
            return None
        self.send_response(200)
        self.send_header("Content-type", content_type)
        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):
        """
        帮助程序生成目录列表(缺少index.html)。
        返回值为file对象或None(表示错误)。
        无论哪种情况,都会发送标头接口与send_head()相同。
        """
        try:
            list_dir = os.listdir(path)
        except os.error:
            self.send_error(404, "No permission to list directory")
            return None
        list_dir.sort(key=lambda a: a.lower())
        f = BytesIO()
        display_path = escape(unquote(self.path))
        f.write(b'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
        f.write(b"<html>\n<title>Directory listing for %s</title>\n" % display_path.encode('utf-8'))
        f.write(b"<body>\n<h2>Directory listing for %s</h2>\n" % display_path.encode('utf-8'))
        f.write(b"<hr>\n")
        f.write(b"<form ENCTYPE=\"multipart/form-data\" method=\"post\">")
        f.write(b"<input name=\"file\" type=\"file\"/>")
        f.write(b"<input type=\"submit\" value=\"upload\" onclick=\"this.disabled=false\"/></form>\n")
        f.write(b"<hr>\n<ul>\n")
        for name in list_dir:
            if name==sys.argv[0]:continue
            fullname = os.path.join(path, name)
            display_name = linkname = name
            # Append / for directories or @ for symbolic links
            if os.path.isdir(fullname):
                display_name = name + "/"
                linkname = name + "/"
            if os.path.islink(fullname):
                display_name = name + "@"
                # Note: a link to a directory displays with @ and links with /
            f.write(
                b'<li><a href="%s">%s</a>\n' % (quote(linkname).encode('utf-8'), escape(display_name).encode('utf-8')))
        f.write(b"</ul>\n<hr>\n</body>\n</html>\n")
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html;charset=utf-8")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        return f

    def guess_type(self, path):
        """
        参数是PATH(文件名)。
        返回值是表单类型/子类型的字符串,
        可用于MIME内容类型标头。
        默认实现在self.extensions_map表中查找文件的扩展名,默认使用application/octet流;
        """
        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',
    })


###########################################

    def translate_path(self,path):
        # abandon query parameters
        path = path.split('?', 1)[0]
        path = path.split('#', 1)[0]
        path = posixpath.normpath(unquote(path))
        words = path.split('/')
        words = filter(None, words)
        # 获取你的py文件存放的路径
        path = os.getcwd()
        # 可在此自定义路径(如果有其路径)
        #path = path+"/file_xxx/xxx"
        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 signal_handler(signal, frame):
    print("You choose to stop me.")
    exit()
    
def main():
    args = _argparse()
    # print(args)
    server_address = (args.bind, args.port)
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)
    httpd = HTTPServer(server_address, SimpleHTTPRequestHandler)
    server = httpd.socket.getsockname()
    print("python_version: " + SimpleHTTPRequestHandler.sys_version)
    print("server_version: " + SimpleHTTPRequestHandler.server_version)
    print("sys encoding: " + sys.getdefaultencoding())
    print("Serving http on: " + str(server[0]) + ", port: " + str(server[1]) + " ... (http://" + server[0] + ":" + str(server[1]) + "/)")
    httpd.serve_forever()


if __name__ == '__main__':
    main()
View Code

 版本2

优化说明:

1)自定义输出日志,打印:日期,调用堆栈信息

2)自动创建文件夹

import os
import sys
import argparse
import posixpath
from datetime import datetime
import traceback

try:
    from html import escape
except ImportError:
    from cgi import escape
import shutil
import mimetypes
import re
import signal
from io import StringIO, BytesIO

if sys.version_info.major == 3:
    # Python3
    from urllib.parse import quote
    from urllib.parse import unquote
    from http.server import HTTPServer
    from http.server import BaseHTTPRequestHandler




# 版本设置 自定义
__version__ = "0.3.1"
def _argparse():
    parser = argparse.ArgumentParser()
    # 一般用户电脑用做服务器,每次开机后,ip4地址可能会发生变化
    #ip = input("请输入IP地址:")
    parser.add_argument('--bind', '-b', metavar='ADDRESS', default="127.0.0.1",
                        help='Specify alternate bind address [default: all interfaces]')
    parser.add_argument('--version', '-v', action='version', version=__version__)
    parser.add_argument('port', action='store', default=8000, type=int, nargs='?',
                        help='Specify alternate port [default: 8000]')
    return parser.parse_args()


class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
    server_version = "simple_http_server/" + __version__

    def do_GET(self):
        """Serve a GET request."""
        fd = self.send_head()
        if fd:
            shutil.copyfileobj(fd, self.wfile)
            fd.close()

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

    def do_POST(self):
        """Serve a POST request."""
        r, info = self.deal_post_data()
        logInfo(r, info, "by: ", self.client_address)
        f = BytesIO()
        f.write(b'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
        f.write(b"<html>\n<title>Upload Result Page</title>\n")
        f.write(b"<body>\n<h2>Upload Result Page</h2>\n")
        f.write(b"<hr>\n")
        if r:
            f.write(b"<strong>Success:</strong>")
        else:
            f.write(b"<strong>Failed:</strong>")
        f.write(info.encode('utf-8'))
        f.write(b"<br><a href=\".\">back</a>")
        f.write(b"<hr><small>Powered By: freelamb, check new version at ")
        # 原始代码地址 可以参考
        f.write(b"<a href=\"https://github.com/freelamb/simple_http_server\">")
        f.write(b"here</a>.</small></body>\n</html>\n")
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html;charset=utf-8")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        if f:
            shutil.copyfileobj(f, self.wfile)
            f.close()

    def deal_post_data(self):
        boundary = self.headers["Content-Type"].split("=")[1].encode('utf-8')
        remain_bytes = int(self.headers['content-length'])
        line = self.rfile.readline()
        remain_bytes -= len(line)
        if boundary not in line:
            return False, "Content NOT begin with boundary"
        line = self.rfile.readline()
        remain_bytes -= len(line)
        fn = re.findall(r'Content-Disposition.*name="file"; filename="(.*)"', line.decode('utf-8'))
        if not fn:
            return False, "Can't find out file name..."
        path = self.translate_path(self.path)
        fn = os.path.join(path, fn[0])
        while os.path.exists(fn):
            fn += "_"
        line = self.rfile.readline()
        remain_bytes -= len(line)
        line = self.rfile.readline()
        remain_bytes -= len(line)
        try:
            out = open(fn, 'wb')
        except IOError:
            return False, "Can't create file to write, do you have permission to write?"

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

    def send_head(self):
        """
        GET和HEAD命令的通用代码。
        这将发送响应代码和MIME标头。
        返回值要么是文件对象
        (除非命令是HEAD,否则调用方必须将其复制到输出文件中,
        并且在任何情况下都必须由调用方关闭),
        要么是None,在这种情况下,调用方无需进一步操作。
        """
        path = self.translate_path(self.path)
        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)
        content_type = self.guess_type(path)
        try:
            #始终以二进制模式读取。以文本模式打开文件可能会导致
            #换行翻译,使内容的实际大小
            #传输*小于*内容长度!
            f = open(path, 'rb')
        except IOError:
            self.send_error(404, "File not found")
            return None
        print("-----------------------",self)
        self.send_response(200)
        self.send_header("Content-type", content_type)
        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):
        """
        帮助程序生成目录列表(缺少index.html)。
        返回值为file对象或None(表示错误)。
        无论哪种情况,都会发送标头接口与send_head()相同。
        """
        try:
            list_dir = os.listdir(path)
        except os.error:
            self.send_error(404, "No permission to list directory")
            return None
        list_dir.sort(key=lambda a: a.lower())
        f = BytesIO()
        display_path = escape(unquote(self.path))
        f.write(b'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
        f.write(b"<html>\n<title>Directory listing for %s</title>\n" % display_path.encode('utf-8'))
        f.write(b"<body>\n<h2>Directory listing for %s</h2>\n" % display_path.encode('utf-8'))
        f.write(b"<hr>\n")
        f.write(b"<form ENCTYPE=\"multipart/form-data\" method=\"post\">")
        f.write(b"<input name=\"file\" type=\"file\"/>")
        f.write(b"<input type=\"submit\" value=\"upload\" onclick=\"this.disabled=false\"/></form>\n")
        f.write(b"<hr>\n<ul>\n")
        for name in list_dir:
            if name==sys.argv[0]:continue
            fullname = os.path.join(path, name)
            display_name = linkname = name
            # Append / for directories or @ for symbolic links
            if os.path.isdir(fullname):
                display_name = name + "/"
                linkname = name + "/"
            if os.path.islink(fullname):
                display_name = name + "@"
                # Note: a link to a directory displays with @ and links with /
            f.write(
                b'<li><a href="%s">%s</a>\n' % (quote(linkname).encode('utf-8'), escape(display_name).encode('utf-8')))
        f.write(b"</ul>\n<hr>\n</body>\n</html>\n")
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html;charset=utf-8")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        return f

    def guess_type(self, path):
        """
        参数是PATH(文件名)。
        返回值是表单类型/子类型的字符串,
        可用于MIME内容类型标头。
        默认实现在self.extensions_map表中查找文件的扩展名,默认使用application/octet流;
        """
        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',
    })


###########################################

    def translate_path(self,path):
        # abandon query parameters
        path = path.split('?', 1)[0]
        path = path.split('#', 1)[0]
        path = posixpath.normpath(unquote(path))
        words = path.split('/')
        words = filter(None, words)
        # 获取你的py文件存放的路径
        path = os.getcwd()
        # 可在此自定义路径(如果有其路径)
        #path = path+"/file_xxx/xxx"
        # 获取文件所在的文件夹路径
        #folder_path = os.path.dirname(path)
        # 判断文件夹是否存在,不存在则创建
        if not os.path.exists(path):
            os.makedirs(path)
        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 signal_handler(signal, frame):
    logInfo("You choose to stop me.")
    exit()
    
def main():
    args = _argparse()
    # logInfo(args)
    server_address = (args.bind, args.port)
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)
    httpd = HTTPServer(server_address, SimpleHTTPRequestHandler)
    server = httpd.socket.getsockname()
    logInfo("server: ", server)
    logInfo("python_version: " + SimpleHTTPRequestHandler.sys_version)
    logInfo("server_version: " + SimpleHTTPRequestHandler.server_version)
    logInfo("sys encoding: " + sys.getdefaultencoding())
    logInfo("Serving http on: http://" + server[0] + ":" + str(server[1]) + "/")
    httpd.serve_forever()


def test():
    print("test")

def logInfo(*msg):
    print("----------")
    print_callstack()
    print("【%s】"%datetime.now(),*msg)
    #print("Result: %s" % msg)

def print_callstack():
    # 获取当前的调用栈信息
    stack = traceback.extract_stack()[:-1]   # 去除最后一行(print_callstack函数本身)
    for frame in stack:
        filename, line_number, function_name, code = frame
        print("==",frame)
        if code is not None and len(code) > 0:
            code = f"{filename}, line:{line_number} - {function_name} -- {code}"
        else:
            code = f"{filename}, {line_number} - {function_name}"
        print(code)



if __name__ == '__main__':
    print("============================================================================")
    #test()
    main()
View Code

 版本3

优化说明:

1)加入debug开关,打印堆栈信息

2)前端防止上传按钮多次提交

3)后台判断上传空文件的问题

import os
import sys
import argparse
import posixpath
from datetime import datetime
import traceback

try:
    from html import escape
except ImportError:
    from cgi import escape
import shutil
import mimetypes
import re
import signal
from io import StringIO, BytesIO

if sys.version_info.major == 3:
    # Python3
    from urllib.parse import quote
    from urllib.parse import unquote
    from http.server import HTTPServer
    from http.server import BaseHTTPRequestHandler



__debug=False
# 版本设置 自定义
__version__ = "0.3.1"
def _argparse():
    parser = argparse.ArgumentParser()
    # 一般用户电脑用做服务器,每次开机后,ip4地址可能会发生变化
    #ip = input("请输入IP地址:")
    parser.add_argument('--bind', '-b', metavar='ADDRESS', default="127.0.0.1",
                        help='Specify alternate bind address [default: all interfaces]')
    parser.add_argument('--version', '-v', action='version', version=__version__)
    parser.add_argument('port', action='store', default=8000, type=int, nargs='?',
                        help='Specify alternate port [default: 8000]')
    return parser.parse_args()


class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
    server_version = "simple_http_server/" + __version__

    def do_GET(self):
        """Serve a GET request."""
        fd = self.send_head()
        if fd:
            shutil.copyfileobj(fd, self.wfile)
            fd.close()

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

    def do_POST(self):
        """Serve a POST request."""
        r, info = self.deal_post_data()
        logInfo(r, info, "by: ", self.client_address)
        f = BytesIO()
        f.write(b'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
        f.write(b"<html>\n<title>Upload Result Page</title>\n")
        f.write(b"<body>\n<h2>Upload Result Page</h2>\n")
        f.write(b"<hr>\n")
        if r:
            f.write(b"<strong>Success:</strong>")
        else:
            f.write(b"<strong>Failed:</strong>")
        f.write(info.encode('utf-8'))
        f.write(b"<br><a href=\".\">back</a>")
        f.write(b"<hr><small>Powered By: freelamb, check new version at ")
        # 原始代码地址 可以参考
        f.write(b"<a href=\"https://github.com/freelamb/simple_http_server\">")
        f.write(b"here</a>.</small></body>\n</html>\n")
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html;charset=utf-8")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        if f:
            shutil.copyfileobj(f, self.wfile)
            f.close()

    def deal_post_data(self):
        boundary = self.headers["Content-Type"].split("=")[1].encode('utf-8')
        remain_bytes = int(self.headers['content-length'])
        line = self.rfile.readline()
        logInfo("=======",line)
        logInfo("=======",remain_bytes)
        remain_bytes -= len(line)
        logInfo("boundary=======",boundary)
        if boundary not in line:
            return False, "Content NOT begin with boundary"
        line = self.rfile.readline()
        logInfo("line=======",line)
        remain_bytes -= len(line)
        fn = re.findall(r'Content-Disposition.*name="file"; filename="(.*)"', line.decode('utf-8'))
        fn = [i for i in fn if i]
        if not fn:
            return False, "Can't find upload file ......"
        path = self.translate_path(self.path)
        fn = os.path.join(path, fn[0])
        while os.path.exists(fn):
            fn += "_"
        line = self.rfile.readline()
        remain_bytes -= len(line)
        line = self.rfile.readline()
        remain_bytes -= len(line)
        try:
            out = open(fn, 'wb')
        except IOError:
            return False, "Can't create file to write, do you have permission to write?"

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

    def send_head(self):
        """
        GET和HEAD命令的通用代码。
        这将发送响应代码和MIME标头。
        返回值要么是文件对象
        (除非命令是HEAD,否则调用方必须将其复制到输出文件中,
        并且在任何情况下都必须由调用方关闭),
        要么是None,在这种情况下,调用方无需进一步操作。
        """
        path = self.translate_path(self.path)
        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)
        content_type = self.guess_type(path)
        try:
            #始终以二进制模式读取。以文本模式打开文件可能会导致
            #换行翻译,使内容的实际大小
            #传输*小于*内容长度!
            f = open(path, 'rb')
        except IOError:
            self.send_error(404, "File not found")
            return None
        print("-----------------------",self)
        self.send_response(200)
        self.send_header("Content-type", content_type)
        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):
        """
        帮助程序生成目录列表(缺少index.html)。
        返回值为file对象或None(表示错误)。
        无论哪种情况,都会发送标头接口与send_head()相同。
        """
        try:
            list_dir = os.listdir(path)
        except os.error:
            self.send_error(404, "No permission to list directory")
            return None
        list_dir.sort(key=lambda a: a.lower())
        f = BytesIO()
        display_path = escape(unquote(self.path))
        f.write(b'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
        f.write(b"<html>\n<title>Directory listing for %s</title>\n" % display_path.encode('utf-8'))
        f.write(b"<body>\n<h2>Directory listing for %s</h2>\n" % display_path.encode('utf-8'))
        f.write(b"<hr>\n")
        f.write(b"<form ENCTYPE=\"multipart/form-data\" method=\"post\">")
        f.write(b"<input name=\"file\" type=\"file\"/>")
        #$("input[name=file]").files.length
        f.write(b"<input type=\"submit\" value=\"upload\" onclick=\"this.form.submit();this.disabled=true;\"/></form>\n")
        f.write(b"<hr>\n<ul>\n")
        for name in list_dir:
            if name==sys.argv[0]:continue
            fullname = os.path.join(path, name)
            display_name = linkname = name
            # Append / for directories or @ for symbolic links
            if os.path.isdir(fullname):
                display_name = name + "/"
                linkname = name + "/"
            if os.path.islink(fullname):
                display_name = name + "@"
                # Note: a link to a directory displays with @ and links with /
            f.write(
                b'<li><a href="%s">%s</a>\n' % (quote(linkname).encode('utf-8'), escape(display_name).encode('utf-8')))
        f.write(b"</ul>\n<hr>\n</body>\n</html>\n")
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html;charset=utf-8")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        return f

    def guess_type(self, path):
        """
        参数是PATH(文件名)。
        返回值是表单类型/子类型的字符串,
        可用于MIME内容类型标头。
        默认实现在self.extensions_map表中查找文件的扩展名,默认使用application/octet流;
        """
        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',
    })


###########################################

    def translate_path(self,path):
        # abandon query parameters
        path = path.split('?', 1)[0]
        path = path.split('#', 1)[0]
        path = posixpath.normpath(unquote(path))
        words = path.split('/')
        words = filter(None, words)
        # 获取你的py文件存放的路径
        path = os.getcwd()
        # 可在此自定义路径(如果有其路径)
        #path = path+"/file_xxx/xxx"
        # 获取文件所在的文件夹路径
        #folder_path = os.path.dirname(path)
        # 判断文件夹是否存在,不存在则创建
        if not os.path.exists(path):
            os.makedirs(path)
        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 signal_handler(signal, frame):
    logInfo("You choose to stop me.")
    exit()
    
def main():
    args = _argparse()
    # logInfo(args)
    server_address = (args.bind, args.port)
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)
    httpd = HTTPServer(server_address, SimpleHTTPRequestHandler)
    server = httpd.socket.getsockname()
    logInfo("server: ", server)
    logInfo("python_version: " + SimpleHTTPRequestHandler.sys_version)
    logInfo("server_version: " + SimpleHTTPRequestHandler.server_version)
    logInfo("sys encoding: " + sys.getdefaultencoding())
    logInfo("Serving http on: http://" + server[0] + ":" + str(server[1]) + "/")
    httpd.serve_forever()


def test():
    print("test")
    
def is_empty(val):
    # 这个函数检查值是否为空(None,空字符串,空列表,空元组,空字典,空集合)
    return val is None or val == '' or val == [] or val == () or val == {} or val == set()
 

def logInfo(*msg):
    print("----------")
    if __debug:
        print_callstack()
    print("【%s】"%datetime.now(),*msg)
    #print("Result: %s" % msg)

def print_callstack():
    # 获取当前的调用栈信息
    stack = traceback.extract_stack()[:-1]   # 去除最后一行(print_callstack函数本身)
    for frame in stack:
        filename, line_number, function_name, code = frame
        print("==",frame)
        if code is not None and len(code) > 0:
            code = f"{filename}, line:{line_number} - {function_name} -- {code}"
        else:
            code = f"{filename}, {line_number} - {function_name}"
        print(code)



if __name__ == '__main__':
    print("============================================================================")
    #test()
    main()
View Code

 版本4

优化说明:

 测试前端上传空文件提醒

import os
import sys
import argparse
import posixpath
from datetime import datetime
import traceback

try:
    from html import escape
except ImportError:
    from cgi import escape
import shutil
import mimetypes
import re
import signal
from io import StringIO, BytesIO

if sys.version_info.major == 3:
    # Python3
    from urllib.parse import quote
    from urllib.parse import unquote
    from http.server import HTTPServer
    from http.server import BaseHTTPRequestHandler



__debug=False
# 版本设置 自定义
__version__ = "0.3.1"
def _argparse():
    parser = argparse.ArgumentParser()
    # 一般用户电脑用做服务器,每次开机后,ip4地址可能会发生变化
    #ip = input("请输入IP地址:")
    parser.add_argument('--bind', '-b', metavar='ADDRESS', default="127.0.0.1",
                        help='Specify alternate bind address [default: all interfaces]')
    parser.add_argument('--version', '-v', action='version', version=__version__)
    parser.add_argument('port', action='store', default=8000, type=int, nargs='?',
                        help='Specify alternate port [default: 8000]')
    return parser.parse_args()


class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
    server_version = "simple_http_server/" + __version__

    def do_GET(self):
        """Serve a GET request."""
        fd = self.send_head()
        if fd:
            shutil.copyfileobj(fd, self.wfile)
            fd.close()

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

    def do_POST(self):
        """Serve a POST request."""
        r, info = self.deal_post_data()
        logInfo(r, info, "by: ", self.client_address)
        f = BytesIO()
        f.write(b'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
        f.write(b"<html>\n<title>Upload Result Page</title>\n")
        f.write(b"<body>\n<h2>Upload Result Page</h2>\n")
        f.write(b"<hr>\n")
        if r:
            f.write(b"<strong>Success:</strong>")
        else:
            f.write(b"<strong>Failed:</strong>")
        f.write(info.encode('utf-8'))
        f.write(b"<br><a href=\".\">back</a>")
        f.write(b"<hr><small>Powered By: freelamb, check new version at ")
        # 原始代码地址 可以参考
        f.write(b"<a href=\"https://github.com/freelamb/simple_http_server\">")
        f.write(b"here</a>.</small></body>\n</html>\n")
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html;charset=utf-8")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        if f:
            shutil.copyfileobj(f, self.wfile)
            f.close()

    def deal_post_data(self):
        boundary = self.headers["Content-Type"].split("=")[1].encode('utf-8')
        remain_bytes = int(self.headers['content-length'])
        line = self.rfile.readline()
        logInfo("=======",line)
        logInfo("=======",remain_bytes)
        remain_bytes -= len(line)
        logInfo("boundary=======",boundary)
        if boundary not in line:
            return False, "Content NOT begin with boundary"
        line = self.rfile.readline()
        logInfo("line=======",line)
        remain_bytes -= len(line)
        fn = re.findall(r'Content-Disposition.*name="file"; filename="(.*)"', line.decode('utf-8'))
        fn = [i for i in fn if i]
        if not fn:
            return False, "Can't find upload file ......"
        path = self.translate_path(self.path)
        fn = os.path.join(path, fn[0])
        while os.path.exists(fn):
            fn += "_"
        line = self.rfile.readline()
        remain_bytes -= len(line)
        line = self.rfile.readline()
        remain_bytes -= len(line)
        try:
            out = open(fn, 'wb')
        except IOError:
            return False, "Can't create file to write, do you have permission to write?"

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

    def send_head(self):
        """
        GET和HEAD命令的通用代码。
        这将发送响应代码和MIME标头。
        返回值要么是文件对象
        (除非命令是HEAD,否则调用方必须将其复制到输出文件中,
        并且在任何情况下都必须由调用方关闭),
        要么是None,在这种情况下,调用方无需进一步操作。
        """
        path = self.translate_path(self.path)
        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)
        content_type = self.guess_type(path)
        try:
            #始终以二进制模式读取。以文本模式打开文件可能会导致
            #换行翻译,使内容的实际大小
            #传输*小于*内容长度!
            f = open(path, 'rb')
        except IOError:
            self.send_error(404, "File not found")
            return None
        print("-----------------------",self)
        self.send_response(200)
        self.send_header("Content-type", content_type)
        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):
        """
        帮助程序生成目录列表(缺少index.html)。
        返回值为file对象或None(表示错误)。
        无论哪种情况,都会发送标头接口与send_head()相同。
        """
        try:
            list_dir = os.listdir(path)
        except os.error:
            self.send_error(404, "No permission to list directory")
            return None
        list_dir.sort(key=lambda a: a.lower())
        f = BytesIO()
        display_path = escape(unquote(self.path))
        f.write(b'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
        f.write(b"<html  lang='zh-cn'>\n<title>Directory listing for %s</title>\n" % display_path.encode('utf-8'))
        fun = """<script type='text/javascript'>
            function postSubmit(){
                console.log("==");
                alert(event.target);
                var fileInput = document.getElementsByName('file')[0];
                if (fileInput.files.length > 0) {
                //if ($("input[name=file]").files.length > 0) {
                    event.target.disabled=true;
                    #event.target.form.submit();
                    console.log('=');
                } else {
                    console.log('-');
                    event.preventDefault(); 
                    return false;
                }
            }\n</script>\n"""
        #f.write(fun.encode('utf-8'))
        f.write(b"<body>\n<h2>Directory listing for %s</h2>\n" % display_path.encode('utf-8'))
        f.write(b"<hr>\n")
        f.write(b"<form ENCTYPE=\"multipart/form-data\" method=\"post\">")
        f.write(b"<input name=\"file\" type=\"file\"/>")
        f.write(b"<input type=\"submit\" value=\"upload\" onclick=\"this.form.submit();this.disabled=true;\"/></form>\n")
        #f.write(b"<input type=\"submit\" value=\"upload\" onclick=\"postSubmit()\"/></form>\n")
        f.write(b"<hr>\n<ul>\n")
        for name in list_dir:
            if name==sys.argv[0]:continue
            fullname = os.path.join(path, name)
            display_name = linkname = name
            # Append / for directories or @ for symbolic links
            if os.path.isdir(fullname):
                display_name = name + "/"
                linkname = name + "/"
            if os.path.islink(fullname):
                display_name = name + "@"
                # Note: a link to a directory displays with @ and links with /
            f.write(b'<li><a href="%s">%s</a>\n' % (quote(linkname).encode('utf-8'), escape(display_name).encode('utf-8')))
        f.write(b"</ul>\n<hr>\n</body>\n</html>\n")
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html;charset=utf-8")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        return f

    def guess_type(self, path):
        """
        参数是PATH(文件名)。
        返回值是表单类型/子类型的字符串,
        可用于MIME内容类型标头。
        默认实现在self.extensions_map表中查找文件的扩展名,默认使用application/octet流;
        """
        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',
    })


###########################################

    def translate_path(self,path):
        # abandon query parameters
        path = path.split('?', 1)[0]
        path = path.split('#', 1)[0]
        path = posixpath.normpath(unquote(path))
        words = path.split('/')
        words = filter(None, words)
        # 获取你的py文件存放的路径
        path = os.getcwd()
        # 可在此自定义路径(如果有其路径)
        #path = path+"/file_xxx/xxx"
        # 获取文件所在的文件夹路径
        #folder_path = os.path.dirname(path)
        # 判断文件夹是否存在,不存在则创建
        if not os.path.exists(path):
            os.makedirs(path)
        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 signal_handler(signal, frame):
    logInfo("You choose to stop me.")
    exit()
    
def main():
    args = _argparse()
    # logInfo(args)
    server_address = (args.bind, args.port)
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)
    httpd = HTTPServer(server_address, SimpleHTTPRequestHandler)
    server = httpd.socket.getsockname()
    logInfo("server: ", server)
    logInfo("python_version: " + SimpleHTTPRequestHandler.sys_version)
    logInfo("server_version: " + SimpleHTTPRequestHandler.server_version)
    logInfo("sys encoding: " + sys.getdefaultencoding())
    logInfo("Serving http on: http://" + server[0] + ":" + str(server[1]) + "/")
    httpd.serve_forever()


def test():
    print("test")
    
def is_empty(val):
    # 这个函数检查值是否为空(None,空字符串,空列表,空元组,空字典,空集合)
    return val is None or val == '' or val == [] or val == () or val == {} or val == set()
 

def logInfo(*msg):
    print("----------")
    if __debug:
        print_callstack()
    print("【%s】"%datetime.now(),*msg)
    #print("Result: %s" % msg)

def print_callstack():
    # 获取当前的调用栈信息
    stack = traceback.extract_stack()[:-1]   # 去除最后一行(print_callstack函数本身)
    for frame in stack:
        filename, line_number, function_name, code = frame
        print("==",frame)
        if code is not None and len(code) > 0:
            code = f"{filename}, line:{line_number} - {function_name} -- {code}"
        else:
            code = f"{filename}, {line_number} - {function_name}"
        print(code)



if __name__ == '__main__':
    print("============================================================================")
    #test()
    main()
View Code

 

版本5

优化说明:使用隐藏窗口的功能

以下是:icoManage.py
#!/usr/bin/env python3

import signal
import threading
import pystray        # 导入 PyStray 库
from PIL import Image # 导入 Python Imaging Library 的 Image 类
import win32gui
from collections import namedtuple

import time


class icoManage:
    MenuItemObj = namedtuple('MenuItemObj', ['text','action',"default",'visible','enabled'])
    # exitCallback = None

    def __init__(self, imgFilePath, menuArr=None):
        self.icon = None
        self.tip = None
        signal.signal(signal.SIGINT, self.signal_handler)
        self.hwnd = win32gui.GetForegroundWindow()
        self.imgPath=imgFilePath
        self.img = Image.open(self.imgPath)
        self.menuList=tuple()
        if menuArr is None:
            menuArr=[('显示', self.Show),('隐藏', self.Hid)]
        for i, val in enumerate(menuArr):
            self.menuList += (pystray.MenuItem(val[0],val[1],default=True),) if i == 0 else (pystray.MenuItem(val[0],val[1]),)
        #self.menuList += (pystray.MenuItem(val[0],val[1]),) for item in menuArr


    def Show(self):
        win32gui.ShowWindow(self.hwnd, 1)
        
    def Hid(self):
        win32gui.ShowWindow(self.hwnd, 0)
        
    # 定义退出菜单项的回调函数
    def exit(self,icon, item):
        win32gui.ShowWindow(self.hwnd, 1)
        self.icon.stop() if icon is None else icon.stop()
        self.hwnd = 0
        # if self.exitCallback is not None and callable(self.exitCallback):
        #     self.exitCallback(True)
        return True

    # def SetExitCallback(self, func=None):
    #     if callable(func):
    #         self.exitCallback = func

        
    # 定义点击菜单项的回调函数
    def click_menu(self,icon, item):
        print("点击了", item)

    # 定义通知内容的回调函数
    def notify(self,icon: pystray.Icon):
        icon.notify(title="通知标题", message="通知内容")


    #@property
    def CurrWindHand(self)->int:
        return self.hwnd

    def Tip(self,msg:str):
        self.tip=msg

    def Stop(self)->bool:
        res = self.exit(None, None)
        signal.signal(signal.SIGINT, signal.SIG_DFL)
        # if fun is not None and callable(fun):
        #     fun(res)
        return res
    
    # 定义
    def signal_handler(self, signum, frame):
        print('You pressed Ctrl+C! Exiting icoManage.')
        self.Stop()


        
    def addMenu(self,mentText,handFun,visible=True,default=False):
        self.menuList += (pystray.MenuItem(mentText, handFun, visible=visible, default=default),)
        #self.menuList += (pystray.MenuItem(text=mentText, action=handFun,visible=visible,default=default),)

    def run(self, showExit=False):
        if showExit:
            self.menuList += (pystray.MenuItem(text='退出', action=self.exit),)
        self.icon = pystray.Icon('Double-click Example', self.img, self.tip, self.menuList)  #此处的图标对象不支持字符串路径格式
        print("  taskBar is running...")
        # self.icon.run()
        #已使用守护线程模式,主线程结束则子线程自动退出
        threading.Thread(target=self.icon.run, daemon=True).start()
        
        

        
    def runTest(self):
        # 创建菜单项
        menu = (
            pystray.MenuItem('显示', self.Show),                                 # 第一个菜单项
            pystray.MenuItem('隐藏', self.Hid),                                  # 第二个菜单项
            pystray.MenuItem(text='菜单C', action=self.click_menu),        # 第三个菜单项
            pystray.MenuItem(text='发送通知', action=self.notify, enabled=False),                        # 第四个菜单项
            pystray.MenuItem(text='点击托盘', action=self.click_menu, default=True,visible=False),  # 第五个菜单项,验证visible
            pystray.MenuItem(text='退出', action=self.exit),                           # 最后一个菜单项
        )        
        # 创建图标对象
        imgPath="pythonx50.png"
        #imgPath="pyProject.ico"
        img = Image.open(self.imgPath)          # 打开并读取图片文件
        icon = pystray.Icon("name", img, "鼠标移动到\n托盘图标上\n展示内容", menu)     # 创建图标对象并绑定菜单项
        #icon = pystray.Icon('Double-click Example', img,"鼠标移动到\n托盘图标上\n展示内容", self.menuList)  #此处的图标对象不支持字符串路径格式
        # 显示图标并等待用户操作
        icon.run()
        



if __name__ == '__main__':
    imgPath="pythonx50.png"
    #imgPath="pyProject.ico"
    #menuItems=[('显示1', ui.on_Show),('隐藏1', ui.on_Hid)] #可提前指定菜单和处理函数
    icoMng = icoManage(imgPath)  #必须指定图标文件
    # icoMng.addMenu('显示2', icoMng.Show,default=True)
    # icoMng.addMenu('隐藏2', icoMng.Hid)
    icoMng.addMenu(mentText='点击测试', handFun=icoMng.click_menu)
    icoMng.run()
    for i in range(10):
        time.sleep(1)
        print("Running...")
View Code

 

以下是:uploadHttp.py
#!/usr/bin/env python3

import os
import sys
import argparse
import posixpath
from datetime import datetime
import traceback
# import win32gui

from taskBarTest import icoManage

try:
    from html import escape
except ImportError:
    from cgi import escape
import shutil
import mimetypes
import re
import signal
from io import StringIO, BytesIO

if sys.version_info.major == 3:
    # Python3
    from urllib.parse import quote
    from urllib.parse import unquote
    from http.server import HTTPServer
    from http.server import BaseHTTPRequestHandler

"""
说明:
    1)安装打包环境:pip install pyinstaller
    2)打包:pyinstaller --onefile uploadHttp2.py
"""

# 定义模块全局变量
_args = None
icoMng = None
# 定义模块私有变量
__debug=False
__hidRun=False

# 版本设置 自定义
__version__ = "0.4.1"
def _argparse():
    parser = argparse.ArgumentParser()
    # 一般用户电脑用做服务器,每次开机后,ip4地址可能会发生变化
    #ip = input("请输入IP地址:")
    #parser.add_argument('-s',dest='--sex',type=str,default='无',help='性别') 
    parser.add_argument('-d','--debug',action='store_true', default=False, help='Use Debug mode to track programs [default: False]')
    parser.add_argument('--hid',action='store_true', default=False, help='Hide running this program  [default: False]')
    parser.add_argument("-p",'--path', type=str, default=os.getcwd(), help='Web server path [default: os.getcwd()]')
    parser.add_argument('-b', '--bind', metavar='ADDRESS', default="127.0.0.1", help='Specify alternate bind address [default: all interfaces]')
    #必选参数,已定义默认值
    parser.add_argument('port', metavar='port', default=8000, type=int, nargs='?',
                        help='Specify alternate port [default: 8000]')                    
    #parser.add_argument('-v', '--version', action='version', version=__version__)
    parser.add_argument('-v','--version', action='version', version='Version '+__version__, help='Show version information')
    return parser.parse_args()


def logInfo(*msg):
    if __debug:
        print_callstack()
    print("【%s】"%datetime.now(),*msg)
    #print("Result: %s" % msg)
    #print("----------")

def print_callstack():
    # 获取当前的调用栈信息
    stack = traceback.extract_stack()[:-1]   # 去除最后一行(print_callstack函数本身)
    for frame in stack:
        filename, line_number, function_name, code = frame
        if code is not None and len(code) > 0:
            code = f"{filename}, line:{line_number} - {function_name} -- {code}"
        else:
            code = f"{filename}, {line_number} - {function_name}"
        #print("【%s】"%datetime.now(),"[stack]%s"%frame)
        #print("【%s】"%datetime.now(),"[code]%s"%code)
        print("【%s】"%datetime.now(),"[stack]%s"%frame,"===%s"%code)


class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
    server_version = "simple_http_server/" + __version__

    def do_GET(self):
        """Serve a GET request."""
        fd = self.send_head()
        if fd:
            shutil.copyfileobj(fd, self.wfile)
            fd.close()

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

    def do_POST(self):
        """Serve a POST request."""
        r, info = self.deal_post_data()
        logInfo(r, info, "by: ", self.client_address)
        f = BytesIO()
        f.write(b'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
        f.write(b"<html>\n<title>Upload Result Page</title>\n")
        f.write(b"<body>\n<h2>Upload Result Page</h2>\n")
        f.write(b"<hr>\n")
        if r:
            f.write(b"<strong>Success:</strong>")
        else:
            f.write(b"<strong>Failed:</strong>")
        f.write(info.encode('utf-8'))
        f.write(b"<br><a href=\".\">back</a>")
        f.write(b"<hr><small>Powered By: freelamb, check new version at ")
        # 原始代码地址 可以参考
        f.write(b"<a href=\"https://github.com/freelamb/simple_http_server\">")
        f.write(b"here</a>.</small></body>\n</html>\n")
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html;charset=utf-8")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        if f:
            shutil.copyfileobj(f, self.wfile)
            f.close()

    def deal_post_data(self):
        boundary = self.headers["Content-Type"].split("=")[1].encode('utf-8')
        remain_bytes = int(self.headers['content-length'])
        line = self.rfile.readline()
        logInfo("=======",line)
        logInfo("=======",remain_bytes)
        remain_bytes -= len(line)
        logInfo("boundary=======",boundary)
        if boundary not in line:
            return False, "Content NOT begin with boundary"
        line = self.rfile.readline()
        logInfo("line=======",line)
        remain_bytes -= len(line)
        fn = re.findall(r'Content-Disposition.*name="file"; filename="(.*)"', line.decode('utf-8'))
        fn = [i for i in fn if i]
        if not fn:
            return False, "Can't find upload file ......"
        path = self.translate_path(self.path)
        fn = os.path.join(path, fn[0])
        while os.path.exists(fn):
            fn += "_"
        line = self.rfile.readline()
        remain_bytes -= len(line)
        line = self.rfile.readline()
        remain_bytes -= len(line)
        try:
            out = open(fn, 'wb')
        except IOError:
            return False, "Can't create file to write, do you have permission to write?"

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

    def send_head(self):
        """
        GET和HEAD命令的通用代码。
        这将发送响应代码和MIME标头。
        返回值要么是文件对象
        (除非命令是HEAD,否则调用方必须将其复制到输出文件中,
        并且在任何情况下都必须由调用方关闭),
        要么是None,在这种情况下,调用方无需进一步操作。
        """
        path = self.translate_path(self.path)
        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)
        content_type = self.guess_type(path)
        logInfo("content_type=",content_type)
        try:
            #始终以二进制模式读取。以文本模式打开文件可能会导致
            #换行翻译,使内容的实际大小
            #传输*小于*内容长度!
            f = open(path, 'rb')
        except IOError:
            self.send_error(404, "File not found")
            return None
        print("-----------------------",self)
        self.send_response(200)
        self.send_header("Content-type", content_type)
        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):
        """
        帮助程序生成目录列表(缺少index.html)。
        返回值为file对象或None(表示错误)。
        无论哪种情况,都会发送标头接口与send_head()相同。
        """
        try:
            list_dir = os.listdir(path)
        except os.error:
            self.send_error(404, "No permission to list directory")
            return None
        list_dir.sort(key=lambda a: a.lower())
        f = BytesIO()
        display_path = escape(unquote(self.path))
        f.write(b'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
        f.write(b"<html  lang='zh-cn'>\n<title>Directory listing for %s</title>\n" % display_path.encode('utf-8'))
        fun = """<script type='text/javascript'>
            function postSubmit(){
                console.log("==");
                alert(event.target);
                var fileInput = document.getElementsByName('file')[0];
                if (fileInput.files.length > 0) {
                //if ($("input[name=file]").files.length > 0) {
                    event.target.disabled=true;
                    #event.target.form.submit();
                    console.log('=');
                } else {
                    console.log('-');
                    event.preventDefault(); 
                    return false;
                }
            }\n</script>\n"""
        #f.write(fun.encode('utf-8'))
        f.write(b"<body>\n<h2><a href='javascript:' onclick='window.history.go(-1)'>Back</a> Directory listing for %s</h2>\n" % display_path.encode('utf-8'))
        f.write(b"<hr>\n")
        f.write(b"<form ENCTYPE=\"multipart/form-data\" method=\"post\">")
        f.write(b"<input name=\"file\" type=\"file\"/>")
        f.write(b"<input type=\"submit\" value=\"upload\" onclick=\"this.form.submit();this.disabled=true;\"/></form>\n")
        #f.write(b"<input type=\"submit\" value=\"upload\" onclick=\"postSubmit()\"/></form>\n")
        f.write(b"<hr>\n<ul>\n")
        for name in list_dir:
            if name==os.path.basename(sys.argv[0]):continue
            fullname = os.path.join(path, name)
            display_name = linkname = name
            # Append / for directories or @ for symbolic links
            if os.path.isdir(fullname):
                display_name = name + "/"
                linkname = name + "/"
            if os.path.islink(fullname):
                display_name = name + "@"
                # Note: a link to a directory displays with @ and links with /
            f.write(b'<li><a href="%s">%s</a>\n' % (quote(linkname).encode('utf-8'), escape(display_name).encode('utf-8')))
        f.write(b"</ul>\n<hr>\n</body>\n</html>\n")
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html;charset=utf-8")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        return f

    def guess_type(self, path):
        """
        参数是PATH(文件名)。
        返回值是表单类型/子类型的字符串,
        可用于MIME内容类型标头。
        默认实现在self.extensions_map表中查找文件的扩展名,默认使用application/octet流;
        """
        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['']


    # 初始化Http传输文件类型
    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',
        '.mp4': 'application/octet-stream',
    })
    #删除字典中指定类型,以使用默认流处理
    del extensions_map['.mp4']
    


###########################################

    def translate_path(self,path):
        # abandon query parameters
        path = path.split('?', 1)[0]
        path = path.split('#', 1)[0]
        path = posixpath.normpath(unquote(path))
        words = path.split('/')
        words = filter(None, words)
        # 获取你的py文件存放的路径
        path = os.getcwd()
        path = _args.path
        # 可在此自定义路径(如果有其路径)
        # 获取文件所在的文件夹路径
        #folder_path = os.path.dirname(path)
        # 判断文件夹是否存在,不存在则创建
        if not os.path.exists(path):
            os.makedirs(path)
        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 signal_handler(signal, frame):
    #logInfo("You choose to stop me.")
    # todo : Stop后需要返回处理结果,根据结果显示提示信息
    # None if icoMng.hwnd==0 else icoMng.Stop()
    # if icoMng.hwnd != 0:
    #     logInfo("正在停止任务栏,请等待...")
    #     isIcoMngStop = icoMng.Stop()
    #     if not isIcoMngStop:
    #         logInfo("任务栏停止失败,请手动右键任务栏图标->退出")
    logInfo("Web程序已停止运行")
    sys.exit()
    # os.kill(os.getpid(), signal.SIGTERM)
    # os._exit()
    

def InitArgs():
    global _args,__debug,__hidRun
    _args = _argparse()
    logInfo("args info :",_args)
    __debug=_args.debug
    __hidRun=_args.hid


def main():
    logInfo("current version: ",__version__)
    server_address = (_args.bind, _args.port)
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)
    httpd = HTTPServer(server_address, SimpleHTTPRequestHandler)
    server = httpd.socket.getsockname()
    logInfo("server IP, port: ", server)
    logInfo("python_version: " + SimpleHTTPRequestHandler.sys_version)
    logInfo("server_version: " + SimpleHTTPRequestHandler.server_version)
    logInfo("sys encoding: " + sys.getdefaultencoding())
    logInfo("web server work path: ",_args.path)
    logInfo("Serving http on: http://" + server[0] + ":" + str(server[1]) + "/")
    httpd.serve_forever()


def test():
    print("test")
    
def is_empty(val):
    # 这个函数检查值是否为空(None,空字符串,空列表,空元组,空字典,空集合)
    return val is None or val == '' or val == [] or val == () or val == {} or val == set()
 

def IcoManage():
    global icoMng
    imgPath="E:/PyWork/taskBarTest/pyProject.ico"
    #menuItems=[('显示1', ui.on_Show),('隐藏1', ui.on_Hid)]
    icoMng = icoManage.icoManage(imgPath)  #必须指定图标文件
    # icoMng.addMenu(mentText='点击测试', handFun=icoMng.click_menu)
    icoMng.Hid() if __hidRun else icoMng.Show()
    icoMng.run()


if __name__ == '__main__':
    print("============================================================================")
    InitArgs()
    #test()
    IcoManage()
    main()
View Code

 

版本6

优化:状态栏图标直接读取,如果是直接运行py文件,则使用黑色图标,如果是打包成exe,则直接读取exe中的icon
以下是:icoManage.py 
 
#!/usr/bin/env python3

import os
import sys
import argparse
import posixpath
from datetime import datetime
import traceback
# import win32gui

from taskBarTest import icoManage

try:
    from html import escape
except ImportError:
    from cgi import escape
import shutil
import mimetypes
import re
import signal
from io import StringIO, BytesIO

if sys.version_info.major == 3:
    # Python3
    from urllib.parse import quote
    from urllib.parse import unquote
    from http.server import HTTPServer
    from http.server import BaseHTTPRequestHandler

"""
说明:
    1)安装打包环境:pip install pyinstaller
    2)打包:pyinstaller --onefile uploadHttp2.py
"""

# 定义模块全局变量
_args = None
icoMng = None
# 定义模块私有变量
__debug=False
__hidRun=False

# 版本设置 自定义
__version__ = "0.4.2"
def _argparse():
    parser = argparse.ArgumentParser()
    # 一般用户电脑用做服务器,每次开机后,ip4地址可能会发生变化
    #ip = input("请输入IP地址:")
    #parser.add_argument('-s',dest='--sex',type=str,default='无',help='性别') 
    parser.add_argument('-d','--debug',action='store_true', default=False, help='Use Debug mode to track programs [default: False]')
    parser.add_argument('--hid',action='store_true', default=False, help='Hide running this program  [default: False]')
    parser.add_argument("-p",'--path', type=str, default=os.getcwd(), help='Web server path [default: os.getcwd()]')
    parser.add_argument('-b', '--bind', metavar='ADDRESS', default="127.0.0.1", help='Specify alternate bind address [default: all interfaces]')
    #必选参数,已定义默认值
    parser.add_argument('port', metavar='port', default=8000, type=int, nargs='?',
                        help='Specify alternate port [default: 8000]')                    
    #parser.add_argument('-v', '--version', action='version', version=__version__)
    parser.add_argument('-v','--version', action='version', version='Version '+__version__, help='Show version information')
    return parser.parse_args()


def logInfo(*msg):
    if __debug:
        print_callstack()
    print("【%s】"%datetime.now(),*msg)
    #print("Result: %s" % msg)
    #print("----------")

def print_callstack():
    # 获取当前的调用栈信息
    stack = traceback.extract_stack()[:-1]   # 去除最后一行(print_callstack函数本身)
    for frame in stack:
        filename, line_number, function_name, code = frame
        if code is not None and len(code) > 0:
            code = f"{filename}, line:{line_number} - {function_name} -- {code}"
        else:
            code = f"{filename}, {line_number} - {function_name}"
        #print("【%s】"%datetime.now(),"[stack]%s"%frame)
        #print("【%s】"%datetime.now(),"[code]%s"%code)
        print("【%s】"%datetime.now(),"[stack]%s"%frame,"===%s"%code)


class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
    server_version = "simple_http_server/" + __version__

    def do_GET(self):
        """Serve a GET request."""
        fd = self.send_head()
        if fd:
            shutil.copyfileobj(fd, self.wfile)
            fd.close()

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

    def do_POST(self):
        """Serve a POST request."""
        r, info = self.deal_post_data()
        logInfo(r, info, "by: ", self.client_address)
        f = BytesIO()
        f.write(b'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
        f.write(b"<html>\n<title>Upload Result Page</title>\n")
        f.write(b"<body>\n<h2>Upload Result Page</h2>\n")
        f.write(b"<hr>\n")
        if r:
            f.write(b"<strong>Success:</strong>")
        else:
            f.write(b"<strong>Failed:</strong>")
        f.write(info.encode('utf-8'))
        f.write(b"<br><a href=\".\">back</a>")
        f.write(b"<hr><small>Powered By: freelamb, check new version at ")
        # 原始代码地址 可以参考
        f.write(b"<a href=\"https://github.com/freelamb/simple_http_server\">")
        f.write(b"here</a>.</small></body>\n</html>\n")
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html;charset=utf-8")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        if f:
            shutil.copyfileobj(f, self.wfile)
            f.close()

    def deal_post_data(self):
        boundary = self.headers["Content-Type"].split("=")[1].encode('utf-8')
        remain_bytes = int(self.headers['content-length'])
        line = self.rfile.readline()
        logInfo("=======",line)
        logInfo("=======",remain_bytes)
        remain_bytes -= len(line)
        logInfo("boundary=======",boundary)
        if boundary not in line:
            return False, "Content NOT begin with boundary"
        line = self.rfile.readline()
        logInfo("line=======",line)
        remain_bytes -= len(line)
        fn = re.findall(r'Content-Disposition.*name="file"; filename="(.*)"', line.decode('utf-8'))
        fn = [i for i in fn if i]
        if not fn:
            return False, "Can't find upload file ......"
        path = self.translate_path(self.path)
        fn = os.path.join(path, fn[0])
        while os.path.exists(fn):
            fn += "_"
        line = self.rfile.readline()
        remain_bytes -= len(line)
        line = self.rfile.readline()
        remain_bytes -= len(line)
        try:
            out = open(fn, 'wb')
        except IOError:
            return False, "Can't create file to write, do you have permission to write?"

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

    def send_head(self):
        """
        GET和HEAD命令的通用代码。
        这将发送响应代码和MIME标头。
        返回值要么是文件对象
        (除非命令是HEAD,否则调用方必须将其复制到输出文件中,
        并且在任何情况下都必须由调用方关闭),
        要么是None,在这种情况下,调用方无需进一步操作。
        """
        path = self.translate_path(self.path)
        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)
        content_type = self.guess_type(path)
        logInfo("content_type=",content_type)
        try:
            #始终以二进制模式读取。以文本模式打开文件可能会导致
            #换行翻译,使内容的实际大小
            #传输*小于*内容长度!
            f = open(path, 'rb')
        except IOError:
            self.send_error(404, "File not found")
            return None
        print("-----------------------",self)
        self.send_response(200)
        self.send_header("Content-type", content_type)
        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):
        """
        帮助程序生成目录列表(缺少index.html)。
        返回值为file对象或None(表示错误)。
        无论哪种情况,都会发送标头接口与send_head()相同。
        """
        try:
            list_dir = os.listdir(path)
        except os.error:
            self.send_error(404, "No permission to list directory")
            return None
        list_dir.sort(key=lambda a: a.lower())
        f = BytesIO()
        display_path = escape(unquote(self.path))
        f.write(b'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
        f.write(b"<html  lang='zh-cn'>\n<title>Directory listing for %s</title>\n" % display_path.encode('utf-8'))
        fun = """<script type='text/javascript'>
            function postSubmit(){
                console.log("==");
                alert(event.target);
                var fileInput = document.getElementsByName('file')[0];
                if (fileInput.files.length > 0) {
                //if ($("input[name=file]").files.length > 0) {
                    event.target.disabled=true;
                    #event.target.form.submit();
                    console.log('=');
                } else {
                    console.log('-');
                    event.preventDefault(); 
                    return false;
                }
            }\n</script>\n"""
        #f.write(fun.encode('utf-8'))
        f.write(b"<body>\n<h2><a href='javascript:' onclick='window.history.go(-1)'>Back</a> Directory listing for %s</h2>\n" % display_path.encode('utf-8'))
        f.write(b"<hr>\n")
        f.write(b"<form ENCTYPE=\"multipart/form-data\" method=\"post\">")
        f.write(b"<input name=\"file\" type=\"file\"/>")
        f.write(b"<input type=\"submit\" value=\"upload\" onclick=\"this.form.submit();this.disabled=true;\"/></form>\n")
        #f.write(b"<input type=\"submit\" value=\"upload\" onclick=\"postSubmit()\"/></form>\n")
        f.write(b"<hr>\n<ul>\n")
        for name in list_dir:
            if name==os.path.basename(sys.argv[0]):continue
            fullname = os.path.join(path, name)
            display_name = linkname = name
            # Append / for directories or @ for symbolic links
            if os.path.isdir(fullname):
                display_name = name + "/"
                linkname = name + "/"
            if os.path.islink(fullname):
                display_name = name + "@"
                # Note: a link to a directory displays with @ and links with /
            f.write(b'<li><a href="%s">%s</a>\n' % (quote(linkname).encode('utf-8'), escape(display_name).encode('utf-8')))
        f.write(b"</ul>\n<hr>\n</body>\n</html>\n")
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html;charset=utf-8")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        return f

    def guess_type(self, path):
        """
        参数是PATH(文件名)。
        返回值是表单类型/子类型的字符串,
        可用于MIME内容类型标头。
        默认实现在self.extensions_map表中查找文件的扩展名,默认使用application/octet流;
        """
        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['']


    # 初始化Http传输文件类型
    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',
        '.mp4': 'application/octet-stream',
    })
    #删除字典中指定类型,以使用默认流处理
    del extensions_map['.mp4']
    


###########################################

    def translate_path(self,path):
        # abandon query parameters
        path = path.split('?', 1)[0]
        path = path.split('#', 1)[0]
        path = posixpath.normpath(unquote(path))
        words = path.split('/')
        words = filter(None, words)
        # 获取你的py文件存放的路径
        path = os.getcwd()
        path = _args.path
        # 可在此自定义路径(如果有其路径)
        # 获取文件所在的文件夹路径
        #folder_path = os.path.dirname(path)
        # 判断文件夹是否存在,不存在则创建
        if not os.path.exists(path):
            os.makedirs(path)
        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 signal_handler(signal, frame):
    #logInfo("You choose to stop me.")
    # todo : Stop后需要返回处理结果,根据结果显示提示信息
    # None if icoMng.hwnd==0 else icoMng.Stop()
    # if icoMng.hwnd != 0:
    #     logInfo("正在停止任务栏,请等待...")
    #     isIcoMngStop = icoMng.Stop()
    #     if not isIcoMngStop:
    #         logInfo("任务栏停止失败,请手动右键任务栏图标->退出")
    logInfo("Web程序已停止运行")
    sys.exit()
    # os.kill(os.getpid(), signal.SIGTERM)
    # os._exit()
    

def InitArgs():
    global _args,__debug,__hidRun
    _args = _argparse()
    logInfo("args info :",_args)
    __debug=_args.debug
    __hidRun=_args.hid

def setCmdTitle(title):
    if sys.platform == "win32":
        # 发送 ANSI 转义序列来修改 CMD 窗口标题
        sys.stdout.write(f"\033]0;{title}\007")
        sys.stdout.flush()

def main():
    print("=======================================================================")
    logInfo("current version: ",__version__)
    server_address = (_args.bind, _args.port)
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)
    httpd = HTTPServer(server_address, SimpleHTTPRequestHandler)
    server = httpd.socket.getsockname()
    logInfo("server IP, port: ", server)
    logInfo("python_version: " + SimpleHTTPRequestHandler.sys_version)
    logInfo("server_version: " + SimpleHTTPRequestHandler.server_version)
    logInfo("sys encoding: " + sys.getdefaultencoding())
    logInfo("Serving http on: http://" + server[0] + ":" + str(server[1]) + "/")
    logInfo("web root path: ",_args.path)
    icoMng.setCmdTitle(f"{sys.argv[0]}||{server[0]}:{server[1]}||{_args.path}")
    print("=======================================================================")
    httpd.serve_forever()


def test():
    print("test")
    
def is_empty(val):
    # 这个函数检查值是否为空(None,空字符串,空列表,空元组,空字典,空集合)
    return val is None or val == '' or val == [] or val == () or val == {} or val == set()
 

def IcoManage():
    global icoMng
    imgPath="E:/PyWork/taskBarTest/pyProject.ico"
    #menuItems=[('显示1', ui.on_Show),('隐藏1', ui.on_Hid)]
    # icoMng = icoManage.icoManage(imgPath)
    icoMng = icoManage.icoManage()
    # icoMng.addMenu(mentText='点击测试', handFun=icoMng.click_menu)
    logInfo("__hidRun: ",__hidRun)
    icoMng.Hid() if __hidRun else icoMng.Show()
    icoMng.run()


if __name__ == '__main__':
    InitArgs()
    #test()
    IcoManage()
    main()
View Code

 

以下是:uploadHttp.py
#!/usr/bin/env python3
#状态栏图标使用系统dll或exe中的图标
import signal
import threading
import pystray        # 导入 PyStray 库
from PIL import Image # 导入 Python Imaging Library 的 Image 类
import win32gui
import win32ui
from collections import namedtuple

import time
import sys



class icoManage:
    MenuItemObj = namedtuple('MenuItemObj', ['text','action',"default",'visible','enabled'])
    # exitCallback = None

    def __init__(self, imgFilePath=None, menuArr=None):
        self.icon = None
        self.tip = None
        signal.signal(signal.SIGINT, self.signal_handler)
        self.hwnd = win32gui.GetForegroundWindow()
        if imgFilePath is None:
            self._getExeIcon()
        else:
            self.imgPath=imgFilePath
            self.img = Image.open(self.imgPath)
        self.menuList=tuple()
        if menuArr is None:
            menuArr=[('显示', self.Show),('隐藏', self.Hid)]
        for i, val in enumerate(menuArr):
            self.menuList += (pystray.MenuItem(val[0],val[1],default=True),) if i == 0 else (pystray.MenuItem(val[0],val[1]),)
        #self.menuList += (pystray.MenuItem(val[0],val[1]),) for item in menuArr


    def _getExeIcon(self):        
        large, small = win32gui.ExtractIconEx(sys.argv[0], 0)
        if sys.platform != "win32" or len(small) == 0:
            img = Image.new('RGBA',(32,32),'#000000') #颜色可以使用字符串,如'red','#FF0000' 或rgb数组如(200,100,100)
        else:
            win32gui.DestroyIcon(small[0])
            hdc = win32ui.CreateDCFromHandle(win32gui.GetDC(0))
            hbmp = win32ui.CreateBitmap()
            hbmp.CreateCompatibleBitmap(hdc, 32, 32)
            hdc = hdc.CreateCompatibleDC()
            hdc.SelectObject(hbmp)
            hdc.DrawIcon((0, 0), large[0])
            # 保存图标到本地文件
            bmpinfo = hbmp.GetInfo()
            bmpstr = hbmp.GetBitmapBits(True)
            img = Image.frombuffer(
                'RGB',
                (bmpinfo['bmWidth'], bmpinfo['bmHeight']),
                bmpstr, 'raw', 'BGRX', 0, 1)
        self.img=img



# 激活窗口并将其最大化。nCmdShow=3
# SW_SHOWMINIMIZED:激活窗口并将其最小化。nCmdShow=2。
# SW_SHOWMINNOACTIVE:窗口最小化,激活窗口仍然维持激活状态。nCmdShow=7。
# SW_SHOWNA:以窗口原来的状态显示窗口。激活窗口仍然维持激活状态。nCmdShow=8。
# SW_SHOWNOACTIVATE:以窗口最近一次的大小和状态显示窗口。激活窗口仍然维持激活状态。nCmdShow=4。
# SW_SHOWNORMAL:激活并显示一个窗口。如果窗口被最小化或最大化,系统将其恢复到原来的尺寸和大小。应用程序在第一次显示窗口的时候应该指定此标志。nCmdShow=1。
    def Show(self):
        win32gui.ShowWindow(self.hwnd, 8) #原值:1
        
        
    def Hid(self):
        win32gui.ShowWindow(self.hwnd, 0)
        
    # 定义退出菜单项的回调函数
    def exit(self,icon, item):
        win32gui.ShowWindow(self.hwnd, 1)
        self.icon.stop() if icon is None else icon.stop()
        self.hwnd = 0
        # if self.exitCallback is not None and callable(self.exitCallback):
        #     self.exitCallback(True)
        return True

    # def SetExitCallback(self, func=None):
    #     if callable(func):
    #         self.exitCallback = func

        
    # 定义点击菜单项的回调函数
    def click_menu(self,icon, item):
        print("点击了", item)

    # 定义通知内容的回调函数
    def notify(self,icon: pystray.Icon):
        icon.notify(title="通知标题", message="通知内容")


    #@property
    def CurrWindHand(self)->int:
        return self.hwnd

    def Tip(self,msg:str):
        self.tip=msg

    def Stop(self)->bool:
        res = self.exit(None, None)
        signal.signal(signal.SIGINT, signal.SIG_DFL)
        # if fun is not None and callable(fun):
        #     fun(res)
        return res
    
    # 定义
    def signal_handler(self, signum, frame):
        print('You pressed Ctrl+C! Exiting icoManage.')
        self.Stop()


        
    def addMenu(self,mentText,handFun,visible=True,default=False):
        self.menuList += (pystray.MenuItem(mentText, handFun, visible=visible, default=default),)
        #self.menuList += (pystray.MenuItem(text=mentText, action=handFun,visible=visible,default=default),)

    def run(self, showExit=False):
        if showExit:
            self.menuList += (pystray.MenuItem(text='退出', action=self.exit),)
        self.icon = pystray.Icon('Double-click Example', self.img, self.tip, self.menuList)  #此处的图标对象不支持字符串路径格式


        print("  taskBar is running...")
        # self.icon.run()
        #已使用守护线程模式,主线程结束则子线程自动退出
        threading.Thread(target=self.icon.run, daemon=True).start()
        
    # 修改窗口标题
    def setCmdTitle(self,strTitle):
        win32gui.SetWindowText(self.hwnd, strTitle)


    def runTest(self):
        # 创建菜单项
        menu = (
            pystray.MenuItem('显示', self.Show),                                 # 第一个菜单项
            pystray.MenuItem('隐藏', self.Hid),                                  # 第二个菜单项
            pystray.MenuItem(text='菜单C', action=self.click_menu),        # 第三个菜单项
            pystray.MenuItem(text='发送通知', action=self.notify, enabled=False),                        # 第四个菜单项
            pystray.MenuItem(text='点击托盘', action=self.click_menu, default=True,visible=False),  # 第五个菜单项,验证visible
            pystray.MenuItem(text='退出', action=self.exit),                           # 最后一个菜单项
        )        
        # 创建图标对象
        imgPath="pythonx50.png"
        #imgPath="pyProject.ico"
        img = Image.open(self.imgPath)          # 打开并读取图片文件
        icon = pystray.Icon("name", img, "鼠标移动到\n托盘图标上\n展示内容", menu)     # 创建图标对象并绑定菜单项
        #icon = pystray.Icon('Double-click Example', img,"鼠标移动到\n托盘图标上\n展示内容", self.menuList)  #此处的图标对象不支持字符串路径格式
        # 显示图标并等待用户操作
        icon.run()
        



if __name__ == '__main__':
    imgPath="pythonx50.png"
    #imgPath="pyProject.ico"
    #menuItems=[('显示1', ui.on_Show),('隐藏1', ui.on_Hid)] #可提前指定菜单和处理函数
    icoMng = icoManage(imgPath)  #必须指定图标文件
    # icoMng.addMenu('显示2', icoMng.Show,default=True)
    # icoMng.addMenu('隐藏2', icoMng.Hid)
    icoMng.addMenu(mentText='点击测试', handFun=icoMng.click_menu)
    icoMng.run()
    for i in range(10):
        time.sleep(1)
        print("Running...")
View Code

 

版本7

优化:打包时在exe文件中包含版本信息等

 
 
 
 
 
 
 
 
 

=======================================================================================

python上传下载文件

场景:

  • 点击上传文件按钮,选择需要上传的文件后上传
  • 文件上传成功后,会将文件保存到指定目录下
  • 限制上传文件的格式
  • 在前端点击文件后下载
  • 基于上述上传并保存到指定目录下的文件

上手

文件上传

app.py

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    # 渲染文件
    return render_template('upload.html')

# 文件保存的目录,根据实际情况的文件结构做调整;
# 若不指定目录,可以写成f.save(f.filename),可以默认保存到当前文件夹下的根目录
# 设置上传文件保存路径 可以是指定绝对路径,也可以是相对路径(测试过)
app.config['UPLOAD_FOLDER'] = './upload'
# 将地址赋值给变量
file_dir = app.config['UPLOAD_FOLDER']


@app.route('/uploader', methods=['GET', 'POST'])
def uploader():
    """
        文件上传
    """
    if request.method == 'POST':
        # input标签中的name的属性值
        f = request.files['file']

        # 拼接地址,上传地址,f.filename:直接获取文件名
        f.save(os.path.join(app.config['UPLOAD_FOLDER'], f.filename))
        # 输出上传的文件名
        print(request.files, f.filename)

        return '文件上传成功!'
    else:
        return render_template('upload.html')
 

upload.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>上传</title>

        <style>
            a{
                text-decoration: none;
                color: #2062e0;
            }
            a:hover{
                text-decoration: none;
                color: #2062e0;
            }
        </style>
    </head>
    <body>
        <h1 align="center">TEST</h1>
        <div align="center">
        	{# enctype:属性用于指定将上传文件数据发送给服务器端之前对表单数据进行编码的斱式 #}
        	{# enctype="multipart/form-data" => 表示不对字符编码。当使用有文件上传控件的表单时,该值是必需的。 #}
            <form action="/uploader" method="post" enctype="multipart/form-data">
                <br><br><br>
                {# accept可以自定以文件上传格式 #}
                <input type="file" name="file" accept=".txt, .pdf, .doc, .docx, .md" value="{{ csrf_token }}" />
                <br><br><br>
                <input type="submit" value="提交" />
            </form>

            <br><br><br>
        </div>
    </body>
</html>
 

文件下载

app.py

@app.route('/download', methods=['GET', 'POST'])
def download():
    """
        文件下载
    :return:
    """
    timelist = []   # 获取指定文件夹下文件并显示
    Foder_Name = []     # 文件夹下所有文件
    Files_Name = []     # 文件名

    # 获取到指定文件夹下所有文件
    lists = os.listdir(file_dir + '/')

    # 遍历文件夹下所有文件
    for i in lists:
        # os.path.getatime => 获取对指定路径的最后访问时间
        timelist.append(time.ctime(os.path.getatime(file_dir + '/' + i)))

    # 遍历文件夹下的所有文件
    for k in range(len(lists)):
        # 单显示文件名
        Files_Name.append(lists[k])
        # 获取文件名以及时间信息
        Foder_Name.append(lists[k] + " ~~~~~~~~~~~~~~~~~~~~~ " + timelist[k])

    print(file_dir)     # ./upload

    return render_template('download.html', allname=Foder_Name, name=Files_Name)


@app.route('/downloads/<path:path>', methods=['GET', 'POST'])
def downloads(path):
    """
        重写download方法,根据前端点击的文件传送过来的path,下载文件
	
		send_from_directory:用于下载文件
		flask.send_from_directory(所有文件的存储目录,相对于要下载的目录的文件名,as_attachment:设置为True是否要发送带有标题的文件)
    :param path:
    :return:
    """
    return send_from_directory(app.config['UPLOAD_FOLDER'], path, as_attachment=True)
 

download.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>文件下载</title>

        <style>
            a{
                text-decoration: none;
                color: #2062e0;
            }
            a:hover{
                text-decoration: none;
                color: #2062e0;
            }
        </style>

    </head>
    <body>
        <div align="center">
            <h1>文件下载</h1><br><br>
            {# 输出文件名及文件详细信息(文件时间信息等) #}
            {% for fruit in allname %}
                <br>
                {{ fruit }}
            {% endfor %}
            <br><br><br><br>
            {# 将指定文件夹中的文件获取遍历显示 #}
            {% for n in name %}

                <a href="downloads/{{ n }}">{{ n }}</a>
                <br><br>

            {% endfor %}
        </div>
    </body>
</html>
 

运行

  • 文件上传和下载的视图函数代码都完成后,开始运行项目

app.py(在编写好试图代码的最下方编写运行代码)

"""
    运行项目
"""
if __name__ == '__main__':
	# 可以使统一局域网下的其他电脑访问该项目
    HOST = '0.0.0.0'
    # debug=True => 打开调试模式,在对代码进行修改后,可以实时运行代码
    app.run(host=HOST, debug=True)
 
  • 打开调试模式后,运行项目建议在Pycharm中的Terminal命令行中输入以下运行
python app.py
 
 

整个app.py代码

import os
import time

from flask import Flask, render_template, request, send_from_directory

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'


@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    # 渲染文件
    return render_template('upload.html')


# 文件保存的目录,根据实际情况的文件结构做调整;
# 若不指定目录,可以写成f.save(f.filename),可以默认保存到当前文件夹下的根目录
# 设置上传文件保存路径 可以是指定绝对路径,也可以是相对路径(测试过)
app.config['UPLOAD_FOLDER'] = './upload'	## 该目录需要自行创建
# 将地址赋值给变量
file_dir = app.config['UPLOAD_FOLDER']


@app.route('/uploader', methods=['GET', 'POST'])
def uploader():
    """  文件上传  """
    if request.method == 'POST':
        # input标签中的name的属性值
        f = request.files['file']

        # 拼接地址,上传地址,f.filename:直接获取文件名
        f.save(os.path.join(app.config['UPLOAD_FOLDER'], f.filename))
        # 输出上传的文件名
        print(request.files, f.filename)

        return '文件上传成功!'
    else:
        return render_template('upload.html')


@app.route('/download', methods=['GET', 'POST'])
def download():
    """  文件下载  """
    timelist = []  # 获取指定文件夹下文件并显示
    Foder_Name = []  # 文件夹下所有文件
    Files_Name = []  # 文件名

    # 获取到指定文件夹下所有文件
    lists = os.listdir(file_dir + '/')

    # 遍历文件夹下所有文件
    for i in lists:
        # os.path.getatime => 获取对指定路径的最后访问时间
        timelist.append(time.ctime(os.path.getatime(file_dir + '/' + i)))

    # 遍历文件夹下的所有文件
    for k in range(len(lists)):
        # 单显示文件名
        Files_Name.append(lists[k])
        # 获取文件名以及时间信息
        Foder_Name.append(lists[k] + " ~~~~~~~~~~~~~~~~~~~~~ " + timelist[k])

    print(file_dir)  # ./upload

    return render_template('download.html', allname=Foder_Name, name=Files_Name)


@app.route('/downloads/<path:path>', methods=['GET', 'POST'])
def downloads(path):
    """ 下载 """
    """
        重写download方法,根据前端点击的文件传送过来的path,下载文件
		send_from_directory:用于下载文件
		flask.send_from_directory(所有文件的存储目录,相对于要下载的目录的文件名,as_attachment:设置为True是否要发送带有标题的文件)
    """
    return send_from_directory(app.config['UPLOAD_FOLDER'], path, as_attachment=True)


"""
    运行项目
"""
if __name__ == '__main__':
    # 可以使统一局域网下的其他电脑访问该项目
    HOST = '0.0.0.0'
    # debug=True => 打开调试模式,在对代码进行修改后,可以实时运行代码
    app.run(host=HOST, debug=True)

模板文件中代码已完整

注意

  • 代码编写好后,需要在项目根目录先创建一个路径用于存放上传的文件
  • 即创建upload文件夹
  • 运行项目后,需要在浏览器地址后补充输入/upload,即可进入上传页面
  • http://127.0.0.1:5000/upload,具体端口号需根据个人项目运行修改,可以在运行时看控制台

 

模拟文件上传Python python上传下载文件_flask

 

 

模拟文件上传Python python上传下载文件_python_02

 

 

 

  • 下载页面

 

模拟文件上传Python python上传下载文件_python_03

 

项目文件目录

模拟文件上传Python python上传下载文件_python_04

 

 

出处:https://blog.51cto.com/u_14273/7758108

=======================================================================================

python-上传下载文件

一、服务端接口

复制代码
import flask, os,sys,time
from flask import request, send_from_directory

interface_path = os.path.dirname(__file__)
sys.path.insert(0, interface_path)  #将当前文件的父目录加入临时系统变量


server = flask.Flask(__name__)


#get方法:指定目录下载文件
@server.route('/download', methods=['get'])
def download():
    fpath = request.values.get('path', '') #获取文件路径
    fname = request.values.get('filename', '')  #获取文件名
    if fname.strip() and fpath.strip():
        print(fname, fpath)
        if os.path.isfile(os.path.join(fpath,fname)) and os.path.isdir(fpath):
            return send_from_directory(fpath, fname, as_attachment=True) #返回要下载的文件内容给客户端
        else:
            return '{"msg":"参数不正确"}'
    else:
        return '{"msg":"请输入参数"}'


# get方法:查询当前路径下的所有文件
@server.route('/getfiles', methods=['get'])
def getfiles():
    fpath = request.values.get('fpath', '') #获取用户输入的目录
    print(fpath)
    if os.path.isdir(fpath):
        filelist = os.listdir(fpath)
        files = [file for file in filelist if os.path.isfile(os.path.join(fpath, file))]
    return '{"files":"%s"}' % files


# post方法:上传文件的
@server.route('/upload', methods=['post'])
def upload():
    fname = request.files.get('file')  #获取上传的文件
    if fname:
        t = time.strftime('%Y%m%d%H%M%S')
        new_fname = r'upload/' + t + fname.filename
        fname.save(new_fname)  #保存文件到指定路径
        return '{"code": "ok"}'
    else:
        return '{"msg": "请上传文件!"}'


server.run(port=8000, debug=True)
复制代码

 

二、客户端发送请求

复制代码
import requests
import os


#上传文件到服务器
file = {'file': open('hello.txt','rb')}
r = requests.post('http://127.0.0.1:8000/upload', files=file)
print(r.text)


#查询fpath下的所有文件
r1 = requests.get('http://127.0.0.1:8000/getfiles',data={'fpath': r'download/'})
print(r1.text)


#下载服务器download目录下的指定文件
r2 = requests.get('http://127.0.0.1:8000/download',data={'filename':'hello_upload.txt', 'path': r'upload/'})
file = r2.text #获取文件内容
basepath = os.path.join(os.path.dirname(__file__), r'download/')
with open(os.path.join(basepath, 'hello_download.txt'),'w',encoding='utf-8') as f: #保存文件
    f.write(file)
复制代码

 

 

【出处】:https://www.cnblogs.com/jessicaxu/p/7891372.html

=======================================================================================

python-文件上传下载,解决粘包问题

一、数据粘包

【1】客户端两次发送请求,但是可能被服务端的同个recv收到,不能区分,会造成数据粘包(实际上需要服务端将两次请求区分接受)

二、服务器

# -*- coding:utf-8 -*-
# __author__:pansy
# 2022/5/14
import socket

# 创建socket对象
sk = socket.socket()
# 给服务器绑定ip和端口
sk.bind(('127.0.0.1',8889))
# 创建监听,监听客户端是否有发请求过来
sk.listen()

def get_file(sk_obj):
    '''
    接收文件
    :param sk_obj: 文件对象
    :return:
    '''
    # 从服务端会发送1个请求,用来传输文件大小,文件大小是整形,需要将string类型强转成int类型
    file_size = int(sk_obj.recv(1024).decode('utf8'))
    # 为了避免粘包,当执行完接收file_size语句后,需要告知post_file,文件大小已经接收成功
    sk_obj.sendall(b'ok')

    # 从服务端会发送1个请求,用来传输文件名称
    file_name = sk_obj.recv(1024).decode('utf8')
    sk_obj.sendall(b'ok')

    # 接收文件内容
    with open('./%s' %file_name,'wb') as f:
        while file_size > 0:
            f.write(sk_obj.recv(1024))
            file_size -= 1024


# 阻塞状态,若接收到数据,则阻塞解除
# accept返回一个套接字和客户端的ip端口
conn ,addr = sk.accept()

# 调用接收文件方法,conn是专门用来处理客户端业务的套接字
get_file(conn)

conn.close()
sk.close()

三、客户端

# -*- coding:utf-8 -*-
# __author__:pansy
# 2022/5/14
import os
import socket

# 创建socket对象
sk = socket.socket()
# 连接服务器,连接的是服务器绑定的ip和端口
sk.connect(('127.0.0.1',8889))

def post_file(sk_obj,file_path):
    '''
    发送文件,需要和接收文件一一对应
    :param sk_obj:文件对象
    :param file_path:文件路径
    :return:
    '''

    # 发送文件大小,用os.stat方法可以获取文件属性
    file_size = os.stat(file_path).st_size
    # 获取到的file_size是整形,不能直接编码,所以需要先强转成字符串
    sk_obj.sendall(str(file_size).encode('utf8'))
    # 为了避免粘包,需要用recv接收下参数,直到接收到ok后,才会继续下面的代码
    sk_obj.recv(1024)

    # 发送文件名称,用os.path.split方法,可以将文件路径切割成路径和文件名,返回这两个字段
    file_name = os.path.split(file_path)[1]
    sk_obj.sendall(file_name.encode('utf8'))
    sk_obj.recv(1024)

    # 发送文件内容,循环发送,1次发送1024个字节
    # 1、先读取文件,用rb二进制读取
    with open(file_path,'rb') as f:
        # 每发送1次,file_size会减少1024,不满足1024的,全部发送
        while file_size > 0:
            sk_obj.sendall(f.read(1024))
            file_size -= 1024

# 调用发送文件方法
path = '/Users/panshaoying/Desktop/database/data/img1.png'
post_file(sk,path)

sk.close()

 

【出处】:https://www.cnblogs.com/flowers-pansy/p/16269797.html

=======================================================================================

posted on 2023-12-01 11:05  jack_Meng  阅读(1256)  评论(0编辑  收藏  举报

导航