Python编写代理服务器报getaddrinfo failed错误如何修复
报错原因
socket.gaierror: [Errno 11001] getaddrinfo failed是DNS解析失败错误,说明传入socket.gethostbyname()的域名字符串非法,核心是代码中URL解析、socket管理存在多处逻辑bug:
- 端口定位逻辑错误:用
url.find(':')查找端口分隔符时,会匹配到http:///https://协议头里的冒号,无法正确识别域名后的端口分隔位置。 - 索引基准不统一:
webserverpos是基于去掉协议头的urlweb计算的索引,但截取端口时用的是原始完整URL的索引,两个字符串长度差了协议头的长度,截取出来的端口、域名内容完全错位。 - 域名截取未排除端口:当URL带自定义端口时,截取
webserver的逻辑会把端口号也拼入域名,比如example.com:8080会被当成域名传入DNS解析,必然失败。 - Socket对象管理错误:代码中未在每个处理线程中独立创建TCP socket对象,要么触发未定义变量错误,要么多线程复用同一个socket导致连接状态混乱。
- 未兼容HTTPS代理逻辑:普通HTTP代理处理HTTPS请求需要走CONNECT隧道,现有逻辑直接按HTTP明文方式请求80端口,HTTPS场景完全无法工作。
- 缺少连接关闭判断:外层循环没有客户端断开的跳出逻辑,连接关闭后会无限循环接收空数据触发异常。
修复方案
以下是可正常转发HTTP请求的修复后完整代码,修正了上述所有逻辑问题:
import socket import threading def handle_client(client, client_address): target_sock = None try: while True: req = client.recv(8192) if not req: break req_text = req.decode('utf-8', errors='ignore') first_line = req_text.split('\n')[0].strip() if not first_line: break method, url, _ = first_line.split(' ') # 解析协议头 if url.startswith('http://'): url_content = url[7:] default_port = 80 elif url.startswith('https://'): # 简易版本暂不支持HTTPS,需要支持可补充CONNECT隧道逻辑 client.send(b'HTTP/1.1 501 Not Implemented\r\n\r\nHTTPS requires CONNECT tunnel support') break else: client.send(b'HTTP/1.1 400 Bad Request\r\n\r\nInvalid proxy request') break # 拆分域名部分和路径部分 if '/' in url_content: host_part, path_part = url_content.split('/', 1) path = '/' + path_part else: host_part = url_content path = '/' # 拆分域名和端口 if ':' in host_part: webserver, port_str = host_part.split(':', 1) port = int(port_str) else: webserver = host_part port = default_port # 每个请求独立创建目标连接 target_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) target_sock.connect((webserver, port)) # 重构请求,替换全路径为相对路径发送给目标服务 new_first_line = f'{method} {path} HTTP/1.1\r\n' req_lines = req_text.split('\n')[1:] new_req_lines = [] for line in req_lines: line = line.strip() if not line: continue if line.lower().startswith('connection:'): new_req_lines.append('Connection: close') else: new_req_lines.append(line) new_req = new_first_line + '\r\n'.join(new_req_lines) + '\r\n\r\n' target_sock.send(new_req.encode('utf-8', errors='ignore')) # 双向透传响应数据 while True: resp_data = target_sock.recv(8192) if not resp_data: break client.send(resp_data) break except Exception as e: print(f'Handle connection {client_address} failed: {str(e)}') finally: if target_sock: target_sock.close() client.close() def main(): listen_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) listen_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) listen_sock.bind(('127.0.0.1', 8888)) listen_sock.listen(128) print('Proxy server listening on 127.0.0.1:8888') while True: client, addr = listen_sock.accept() t = threading.Thread(target=handle_client, args=(client, addr), daemon=True) t.start() if __name__ == '__main__': main()
测试与扩展说明
- 测试时不要直接设置全局系统代理,先用curl命令验证功能:
curl -x http://127.0.0.1:8888 http://example.com,确认可以正常返回网页内容后再配置系统代理。 - 如果需要支持HTTPS请求,需要补充CONNECT隧道逻辑:收到客户端的
CONNECT 目标域名:443 HTTP/1.1请求后,先和目标服务的对应端口建立TCP连接,返回HTTP/1.1 200 Connection Established\r\n\r\n响应,之后直接双向透传客户端和目标服务之间的TCP流量即可,不需要解密HTTPS内容。
内容的提问来源于stack exchange,提问作者Nicholas Picklas
相关产品推荐
相关产品推荐

