用 Python 快速搭建一个支持 HTTPS、CORS 和断点续传的文件服务器

在日常开发或测试中,我们经常需要一个简单的 HTTP 服务器来共享文件或提供静态资源。Python 的 http.server 模块是一个不错的选择,但它默认只支持 HTTP。本文将介绍如何基于 http.server 快速搭建一个功能更强大的 HTTPS 文件服务器,它不仅安全,还支持跨域请求(CORS)和大文件断点续传(Range Requests)。

核心功能

  1. HTTPS 加密 :通过 mkcert 工具自动生成和管理本地信任的 SSL/TLS 证书。

  2. 跨域资源共享 (CORS):允许来自不同域的前端应用无缝访问。

  3. 断点续传 :支持 Range 请求头,实现大文件的分段下载和断点续传。

  4. 简单易用:一键启动,自动处理证书生成。

准备工作

首先,你需要安装 mkcert 工具来生成本地证书。mkcert 是一个简单的工具,用于制作本地信任的开发证书。

在 Linux 上:

复制代码
# 例如,在 Ubuntu/Debian 上
sudo apt update && sudo apt install libnss3-tools
curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64"
chmod +x mkcert-v*-linux-amd64
sudo cp mkcert-v*-linux-amd64 /usr/local/bin/mkcert

在 macOS 上:

复制代码
brew install mkcert

在 Windows 上: 可以通过 Chocolatey 或从 官方 releases 下载。

安装后,在你的终端运行一次 mkcert -install,它会在你的系统信任存储中安装一个本地 CA。

Python 实现代码

将以下代码保存为 https_server.py

python 复制代码
import http.server
import ssl
import os
import subprocess
import re
​
# --- 配置区 ---
# 证书和密钥文件所在目录
CERT_DIR = "./.cert"
# 要生成证书的主机名和IP列表
MKCERT_HOSTS = ["localhost", "127.0.0.1", "::1", "192.168.1.100"] # 修改为你的IP
​
# HTTPS 服务监听地址和端口
HOST = "0.0.0.0"
PORT = 8443
# 文件服务的根目录
ROOT_DIR = "./shared_files" # 修改为你的文件目录
​
# ==================== 1. 通过 mkcert 自动生成/复用证书 ====================
​
def ensure_certificate():
    """确保存在 mkcert 生成的证书,缺失时自动调用 mkcert。"""
    cert_file = os.path.join(CERT_DIR, "server.crt")
    key_file = os.path.join(CERT_DIR, "server.key")
​
    os.makedirs(CERT_DIR, exist_ok=True)
​
    if os.path.exists(cert_file) and os.path.exists(key_file):
        print(f"发现已有证书:{cert_file},直接复用。")
        return cert_file, key_file
​
    print("未检测到证书,正在调用 mkcert 生成...")
    mkcert_cmd = ["mkcert", "-key-file", key_file, "-cert-file", cert_file, *MKCERT_HOSTS]
    try:
        subprocess.run(mkcert_cmd, check=True, capture_output=True, text=True)
    except FileNotFoundError:
        print("错误:未找到 mkcert 命令。请先安装 mkcert 并执行 'mkcert -install'。")
        raise
    except subprocess.CalledProcessError as exc:
        print(f"错误:mkcert 生成证书失败: {exc.stderr}")
        raise
​
    print("证书生成完成。")
    return cert_file, key_file
​
# ==================== 2. 自定义 HTTPS 请求处理器 (支持 CORS 和 Range) ====================
​
class AdvancedHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
    """
    一个增强版的请求处理器,添加了:
    - CORS 支持
    - Range 请求支持 (断点续传)
    """
​
    def end_headers(self):
        """在发送响应头之前,添加 CORS 相关的头。"""
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS')
        self.send_header('Access-Control-Allow-Headers', '*')
        self.send_header('Access-Control-Max-Age', '86400') # 预检请求结果缓存24小时
        super().end_headers()
​
    def do_OPTIONS(self):
        """处理预检请求(Preflight Request)。"""
        self.send_response(200)
        self.end_headers()
​
    def do_GET(self):
        """重写 GET 方法以支持 Range 请求。"""
        range_header = self.headers.get('Range')
        if range_header:
            # 尝试解析 Range 头
            match = re.match(r'bytes=(\d+)-(\d*)', range_header)
            if match:
                start_str, end_str = match.groups()
                start = int(start_str)
                end = int(end_str) if end_str else None
                
                path = self.translate_path(self.path)
                if os.path.isfile(path):
                    file_size = os.path.getsize(path)
                    
                    # 验证并调整范围
                    if start >= file_size:
                        self.send_error(416, "Requested Range Not Satisfiable")
                        return
                    
                    end = min(end, file_size - 1) if end is not None else file_size - 1
                    
                    # 发送 206 Partial Content 响应
                    self.send_response(206)
                    self.send_header('Content-Type', self.guess_type(path))
                    self.send_header('Content-Length', str(end - start + 1))
                    self.send_header('Content-Range', f'bytes {start}-{end}/{file_size}')
                    self.send_header('Accept-Ranges', 'bytes')
                    self.end_headers()
                    
                    # 发送指定范围的文件内容
                    with open(path, 'rb') as f:
                        f.seek(start)
                        remaining = end - start + 1
                        chunk_size = 8192
                        while remaining > 0:
                            chunk = f.read(min(chunk_size, remaining))
                            if not chunk:
                                break
                            self.wfile.write(chunk)
                            remaining -= len(chunk)
                    return
        
        # 如果不是 Range 请求或解析失败,使用默认的 GET 处理逻辑
        super().do_GET()
​
    def send_head(self):
        """重写 send_head 以确保在正常响应中也包含 Accept-Ranges 头。"""
        path = self.translate_path(self.path)
        f = None
        if os.path.isdir(path):
            # 对于目录,如果没有 index 文件,则返回 403 Forbidden 而不是列表
            # 你可以根据需要修改此行为
            if not self.path.endswith('/'):
                self.send_response(301)
                self.send_header("Location", self.path + "/")
                self.end_headers()
                return None
            for index in "index.html", "index.htm":
                index_path = os.path.join(path, index)
                if os.path.exists(index_path):
                    path = index_path
                    break
            else:
                self.send_error(403, "Directory listing not allowed")
                return None
        
        ctype = self.guess_type(path)
        try:
            f = open(path, 'rb')
        except OSError:
            self.send_error(404, "File not found")
            return None
        
        try:
            fs = os.fstat(f.fileno())
            self.send_response(200)
            self.send_header("Content-type", ctype)
            self.send_header("Content-Length", str(fs[6]))
            self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
            self.send_header("Accept-Ranges", "bytes") # 关键:告知客户端支持断点续传
            self.end_headers()
            return f
        except:
            f.close()
            raise
​
# ==================== 3. 启动服务 ====================
​
if __name__ == "__main__":
    # 确保证书存在
    cert_file, key_file = ensure_certificate()
​
    # 确保文件根目录存在
    os.makedirs(ROOT_DIR, exist_ok=True)
    print(f"文件服务根目录设置为: {os.path.abspath(ROOT_DIR)}")
​
    # 切换工作目录到文件根目录
    os.chdir(ROOT_DIR)
​
    # 创建 HTTP 服务器实例
    server = http.server.HTTPServer((HOST, PORT), AdvancedHTTPRequestHandler)
    
    # 包装 SSL 上下文
    ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
    ssl_context.load_cert_chain(certfile=cert_file, keyfile=key_file)
    server.socket = ssl_context.wrap_socket(server.socket, server_side=True)
​
    print(f"\nHTTPS 文件服务器已启动!")
    print(f"服务地址: https://{HOST}:{PORT}")
    print(f"提示: 将需要共享的文件放入 '{os.path.abspath(ROOT_DIR)}' 目录。")
    print(f"按 Ctrl+C 停止服务。")
​
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print("\n正在关闭服务...")
        server.shutdown()
        print("服务已关闭。")

如何使用

  1. 保存代码 :将上面的代码保存为 https_server.py

  2. 修改配置 :打开 https_server.py 文件,修改 MKCERT_HOSTSPORTROOT_DIR 等配置项以符合你的需求。特别是 MKCERT_HOSTS,建议至少包含你的局域网 IP 地址,这样局域网内的其他设备也可以访问。

  3. 准备文件 :创建 shared_files 目录(或你在配置中指定的 ROOT_DIR),并将需要共享的文件放入其中。

  4. 运行服务器

    复制代码
    python3 https_server.py
  5. 访问文件 :在浏览器或任何支持 HTTPS 的客户端中访问 https://<你的IP或localhost>:8443/文件名。由于证书是本地信任的,浏览器不会显示安全警告。

代码亮点解析

  • 自动证书管理 (ensure_certificate) :脚本会自动检查证书是否存在,如果不存在,会调用 mkcert 命令生成。这简化了初始设置。

  • CORS 支持 (end_headersdo_OPTIONS) :通过重写 end_headers 方法,在每个响应中都加入 CORS 头,允许来自任何域的请求。do_OPTIONS 方法则正确响应预检请求。

  • 断点续传 (do_GET) :通过解析 Range 请求头,并以 206 Partial Content 状态码和 Content-Range 头进行响应,实现了断点续传功能,这对于下载大文件至关重要。

  • 清晰的结构:代码被分成了证书准备、请求处理和服务启动三个主要部分,易于理解和维护。

这个小小的 Python 脚本提供了一个安全、强大且易于部署的文件共享解决方案,非常适合开发和测试环境使用。

相关推荐
汤姆yu43 分钟前
基于python大数据的小说数据可视化及预测系统
大数据·python·信息可视化
x***J3481 小时前
Python多线程爬虫
开发语言·爬虫·python
m***D2861 小时前
Python网络爬虫实战案例
开发语言·爬虫·python
_Stellar1 小时前
Linux 服务器管理 根目录文件夹权限设置 基于用户组实现安全共享
linux·服务器·安全
LUCIFER1 小时前
驱动开发:详细分析 DTB、DTS、DTSI、DTBO 的区别、用途及它们之间的关系
linux·服务器·驱动开发
ID_180079054731 小时前
基于 Python 的淘宝商品详情数据结构化解析:SKU、价格与库存字段提取
开发语言·数据结构·python
Laughtin2 小时前
终端Python环境的选择与切换
开发语言·python
JHC0000002 小时前
Python PDF 相关操作
开发语言·python·pdf
databook2 小时前
Manim进阶:用背景图片让你的数学视频脱颖而出
python·动效