搭建一个简易 Mock Server 服务

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
@File    : mockHTTPServer_Imporved_PT.py
@Create Time: 2025-08-14 9:22
@Description: Mock Server 服务 (Command Line Interface Version)
             - 使用 logging 模块替换 print
             - 接口返回值包含动态信息 (路径, 方法, 时间戳, UUID)
             - 显式添加 Content-Length 响应头
             - 在日志中打印接收到的 POST 数据内容
             - 支持通过命令行参数或环境变量配置 Host 和 Port
             - 默认监听所有接口 (0.0.0.0) 的 9012 端口
"""

import json
import logging
import argparse
import os
from http.server import HTTPServer, BaseHTTPRequestHandler
from socketserver import ThreadingMixIn
from datetime import datetime
import uuid

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    """Handle requests in a separate thread."""

class RequestHandler(BaseHTTPRequestHandler):
    """处理 HTTP 请求"""

    def _send_dynamic_response(self, method, path, received_data=None):
        """发送包含动态信息的 JSON 响应,ID使用UUID"""
        # 生成唯一的请求ID
        request_id = str(uuid.uuid4())

        data = {
            'status_code': 200,
            'message': 'Request processed successfully',
            'timestamp': datetime.now().isoformat(),
            'id': request_id, # 使用动态生成的UUID
            'request': {
                'method': method,
                'path': path
            }
        }
        if received_data is not None:
            # 将接收到的原始数据作为字符串存入响应
            decoded_data = received_data.decode('utf-8') if isinstance(received_data, bytes) else received_data
            data['request']['received_data'] = decoded_data

        # 序列化并编码响应
        response_bytes = json.dumps(data, ensure_ascii=False).encode('utf-8')

        self.send_response(200)
        self.send_header('Content-Type', 'application/json')
        self.send_header('Content-Length', str(len(response_bytes)))
        # 为了方便前端调用,可以添加 CORS 头 (根据评价建议)
        # 注意:* 是不安全的,生产环境应指定具体域名
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
        self.send_header('Access-Control-Allow-Headers', 'Content-Type')
        self.end_headers()
        self.wfile.write(response_bytes)
        # 记录响应发送
        logger.info(f"Sent dynamic response for {method} {path} with ID: {request_id}")


    def do_GET(self):
        """处理 GET 请求"""
        logger.info(f"Received GET request for path: {self.path}")
        try:
            self._send_dynamic_response('GET', self.path)
        except Exception as e:
            logger.error(f"Error in GET request: {e}")
            self.send_error(500, f"Internal Server Error: {e}")

    def do_POST(self):
        """处理 POST 请求"""
        logger.info(f"Received POST request for path: {self.path}")
        try:
            content_length = int(self.headers.get('Content-Length', 0))
            post_data = self.rfile.read(content_length) if content_length > 0 else b''

            # --- 打印 POST 数据内容 ---
            if post_data:
                try:
                    # 尝试将其作为 JSON 解析并美化打印
                    post_data_str = post_data.decode('utf-8')
                    post_data_json = json.loads(post_data_str)
                    logger.info(f"POST data received (JSON):\n{json.dumps(post_data_json, indent=2, ensure_ascii=False)}")
                except json.JSONDecodeError:
                    # 如果不是 JSON,直接打印原始字符串
                    logger.info(f"POST data received (Raw): {post_data.decode('utf-8')}")
            else:
                logger.info("POST request received with no data.")

            self._send_dynamic_response('POST', self.path, post_data)
        except Exception as e:
            logger.error(f"Error in POST request: {e}")
            self.send_error(500, f"Internal Server Error: {e}")

    def do_OPTIONS(self):
        # Handle preflight CORS requests
        self.send_response(200)
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
        self.send_header('Access-Control-Allow-Headers', 'Content-Type')
        self.end_headers()

    # 禁用 BaseHTTPRequestHandler 默认的日志记录格式
    def log_message(self, format, *args):
        pass # 使用我们自己的 logger

def parse_arguments():
    """解析命令行参数"""
    parser = argparse.ArgumentParser(description="A simple mock HTTP server.")
    parser.add_argument(
        "--host", "-H",
        type=str,
        default=os.environ.get("MOCK_SERVER_HOST", "0.0.0.0"), # 默认从环境变量或 '0.0.0.0' 获取
        help="Host to bind the server to (default: 0.0.0.0 or value of MOCK_SERVER_HOST env var)"
    )
    parser.add_argument(
        "--port", "-p",
        type=int,
        default=int(os.environ.get("MOCK_SERVER_PORT", 9012)), # 默认从环境变量或 9012 获取
        help="Port to bind the server to (default: 9012 or value of MOCK_SERVER_PORT env var)"
    )
    return parser.parse_args()

if __name__ == '__main__':
    args = parse_arguments()
    host = args.host
    port = args.port

    # 使用 ('', port) 也可以绑定到所有接口,但显式使用 '0.0.0.0' 更清晰
    # 如果 host 是 '0.0.0.0' 或 '',则监听所有接口
    server_address = (host, port)
    server = ThreadedHTTPServer(server_address, RequestHandler)

    logger.info("====== Mock Server with CLI Main Begin ======")
    logger.info("Starting server, listening at http://%s:%s", host if host else '0.0.0.0', port)
    logger.info("Server will run persistently. Press Ctrl+C to stop.")
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        logger.info("Received interrupt signal. Shutting down server gracefully...")
        server.shutdown()
        server.server_close()
        logger.info("Server stopped.")

最终效果




↙↙↙阅读原文可查看相关链接,并与作者交流