Python HTTPサーバーファイルのアップロードとダウンロード

LAN内(同じWIFI下)でファイルのアップロードとダウンロードを実現

このモジュールは、標準の GET
リクエストと HEAD リクエストを実装することにより、BaseHTTPServer 上に構築されます。(すべてのコードを同じ 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サーバーが閉じている場合

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

HTTPサーバーの初期化

HTTPサーバーの初期値は、ご使用のコンピュータの設定に基づいて設定してください。
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サーバーを有効にする

py プログラムを起動すると URL が出力されるので、クリックすると自動的に 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()

おすすめ

転載: blog.csdn.net/love_wgll/article/details/129155712