You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Tkinter窗口中http.server线程启动/终止时程序冻结问题求助

Tkinter代理服务器终止时程序冻结问题修复

问题说明

在Tkinter应用中,配置IP为127.0.0.1、端口8080的代理服务器可正常启动,但点击终止按钮时程序会发生冻结。当前仅需完善代理的终止逻辑,暂未测试代理转发功能。使用threading.Event作为终止信号,通过httpd.handle_request()循环处理请求,预期实现:点击启动按钮时代理正常运行,点击终止按钮时程序无冻结地停止代理。

原代码

import tkinter as tk
import http.server
import socketserver
import urllib.request
import threading

# Global stop event to signal the server thread
stop_event = threading.Event()
httpd_instance = None


class ProxyHandler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        target_url = self.path
        print(f"Forwarding GET request to: {target_url}")
        try:
            with urllib.request.urlopen(target_url) as response:
                self.send_response(response.status)

                for key, value in response.getheaders():
                    # TODO(martin-montas) Take this get headers functio
                    # and display it in the Text area of the repeaters
                    self.send_header(key, value)
                self.end_headers()
                self.wfile.write(response.read())
        except Exception as e:
            self.send_error(500, f"Error forwarding request: {e}")

    def do_POST(self):
        target_url = self.path
        print(f"Forwarding POST request to: {target_url}")
        try:
            content_length = int(self.headers.get("Content-Length", 0))
            body = self.rfile.read(content_length)

            req = urllib.request.Request(target_url, data=body, method="POST")
            for key in self.headers:
                req.add_header(key, self.headers[key])

            with urllib.request.urlopen(req) as response:
                self.send_response(response.status)
                for key, value in response.getheaders():
                    # TODO(martin-montas) Take this get headers
                    # and display it in the Text area of the repeaters
                    self.send_header(key, value)
                self.end_headers()
                self.wfile.write(response.read())
        except Exception as e:
            self.send_error(500, f"Error forwarding request: {e}")


def run_proxy_server(proxy_ip="127.0.0.1", proxy_port=8080):
    global httpd_instance
    global stop_event
    with socketserver.TCPServer((proxy_ip, proxy_port), ProxyHandler) as httpd:
        httpd_instance = httpd
        print(f"Serving proxy at {proxy_ip}:{proxy_port}")
        try:
            while not stop_event.is_set():
                httpd.handle_request()  # Process one request at a time
        except Exception as e:
            print(f"Server stopped: {e}")
        finally:
            httpd_instance = None

def stop_proxy_server():
    global httpd_instance
    global stop_event
    if httpd_instance:
        print("Stopping the proxy server...")
        stop_event.set()  # Signal the thread to stop
        httpd_instance.shutdown()
        httpd_instance = None
        print("[-]")

    else:
        print("Proxy server is not running.")


class SettingsLayout:
    def __init__(self, root) -> None:
        self.root = root
        tk.Label(self.root, text="Proxy IP").grid(row=0, column=0, padx=10, pady=10)
        self.proxy_ip_entry = tk.Entry(self.root)
        self.proxy_ip_entry.grid(row=1, column=0, padx=10, pady=10)

        tk.Label(self.root, text="Proxy port").grid(row=2, column=0, padx=10, pady=10)
        self.proxy_port_entry = tk.Entry(self.root)
        self.proxy_port_entry.grid(row=3, column=0, padx=10, pady=10)

        tk.Button(
            self.root, text="Set Proxy", bg="orange", command=self.button_command
        ).grid(row=4, column=0, padx=10, pady=10)

        tk.Button(
            self.root,
            text="Disable Proxy",
            bg="orange",
            command=stop_proxy_server,
        ).grid(row=5, column=0, padx=10, pady=10)

    def button_command(self):
        ip = self.proxy_ip_entry.get()
        port = self.proxy_port_entry.get()
        try:
            # Validate port input
            port = int(port)
            print(f"Starting proxy at IP: {ip}, Port: {port}")
            # Run the proxy server in a separate thread
            threading.Thread(
                target=run_proxy_server, args=(ip, port), daemon=True
            ).start()
        except ValueError:
            print("Invalid port number. Please enter a valid integer.")

问题原因

  1. httpd.shutdown()会阻塞调用它的线程(即Tkinter主线程),直到服务器的serve_forever()循环退出,但当前代码使用的是handle_request()循环,shutdown()无法正确触发退出逻辑,导致主线程冻结。
  2. handle_request()会一直阻塞等待新请求,stop_event.set()无法中断这个阻塞状态,导致循环无法及时检测到终止信号。

修复方案

修改后的核心代码

调整run_proxy_server函数

def run_proxy_server(proxy_ip="127.0.0.1", proxy_port=8080):
    global httpd_instance
    global stop_event
    stop_event.clear()  # 重置终止信号
    with socketserver.TCPServer((proxy_ip, proxy_port), ProxyHandler) as httpd:
        httpd_instance = httpd
        httpd.timeout = 1  # 设置超时,让handle_request定期返回
        print(f"Serving proxy at {proxy_ip}:{proxy_port}")
        try:
            while not stop_event.is_set():
                httpd.handle_request()  # 超时后自动返回,检查终止信号
        except Exception as e:
            print(f"Server stopped: {e}")
        finally:
            httpd_instance = None
            print("Proxy server stopped.")

调整stop_proxy_server函数

def stop_proxy_server():
    global httpd_instance
    global stop_event
    if httpd_instance:
        print("Stopping the proxy server...")
        stop_event.set()  # 触发终止信号
        # 主动连接服务器,打破handle_request的阻塞状态
        try:
            urllib.request.urlopen(
                f"http://{httpd_instance.server_address[0]}:{httpd_instance.server_address[1]}/__stop__",
                timeout=1
            )
        except:
            pass  # 忽略连接错误,仅用于触发请求
        httpd_instance = None
    else:
        print("Proxy server is not running.")

关键改动说明

  • 给TCPServer设置timeout=1,让handle_request()最多阻塞1秒就返回,确保循环能定期检查stop_event。
  • 在终止时主动连接服务器,触发一次无效请求,让handle_request()立即返回,无需等待超时,加快终止速度。
  • 移除httpd.shutdown(),改用stop_event+超时机制控制循环退出,避免主线程被阻塞。

内容的提问来源于stack exchange,提问作者Martin Montas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 13:11:01