import socket import socketserver import select import urllib.parse BUFFER_SIZE = 65536 TIMEOUT = 30 def connect_upstream(host, port): return socket.create_connection((host, port), timeout=TIMEOUT) class ProxyHandler(socketserver.BaseRequestHandler): def handle(self): client = self.request client.settimeout(TIMEOUT) data = b'' while b'\r\n\r\n' not in data and len(data) < 65536: chunk = client.recv(BUFFER_SIZE) if not chunk: return data += chunk header_blob, _, extra = data.partition(b'\r\n\r\n') lines = header_blob.decode('iso-8859-1', 'replace').split('\r\n') if not lines: return parts = lines[0].split() if len(parts) < 3: return method, target, version = parts[0].upper(), parts[1], parts[2] header_lines = lines[1:] if method == 'CONNECT': host, _, port_s = target.partition(':') port = int(port_s or 443) remote = None try: remote = connect_upstream(host, port) remote.sendall(extra) client.sendall(b'HTTP/1.1 200 Connection Established\r\n\r\n') self.relay(client, remote) except Exception as e: try: msg = ('HTTP/1.1 502 Bad Gateway\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n' + str(e)).encode() client.sendall(msg) except Exception: pass if remote: remote.close() return url = urllib.parse.urlsplit(target) host = url.hostname port = url.port or (443 if url.scheme == 'https' else 80) if not host: return path = urllib.parse.urlunsplit(('', '', url.path or '/', url.query, '')) remote = None try: remote = connect_upstream(host, port) remote.sendall(f'{method} {path} {version}\r\n'.encode('iso-8859-1')) for h in header_lines: if not h.lower().startswith('proxy-connection:'): remote.sendall((h + '\r\n').encode('iso-8859-1')) remote.sendall(b'\r\n') if extra: remote.sendall(extra) self.relay(client, remote) except Exception: if remote: remote.close() def relay(self, client, remote): sockets = [client, remote] try: while True: readable, _, _ = select.select(sockets, [], [], TIMEOUT) if not readable: break for s in readable: data = s.recv(BUFFER_SIZE) if not data: return (remote if s is client else client).sendall(data) finally: remote.close() class ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): allow_reuse_address = True daemon_threads = True if __name__ == '__main__': with ThreadingTCPServer(('127.0.0.1', 18888), ProxyHandler) as server: print('Local proxy listening on 127.0.0.1:18888', flush=True) server.serve_forever()