#!/usr/bin/env python3 """Tap Proxy — logs HTTP headers. Usage: python3 tap-proxy.py """ import http.server, urllib.request, sys, socket from datetime import datetime PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8081 BACKEND = sys.argv[2] if len(sys.argv) > 2 else "127.0.0.1:80" LOG = (sys.argv[3] if len(sys.argv) > 3 else "/tmp") + "/tap-" + datetime.now().strftime("%Y%m%d-%H%M%S") + ".log" BACKEND_URL = "http://" + BACKEND # Don't follow redirects — preserve Set-Cookie class NoRedirect(urllib.request.HTTPRedirectHandler): def http_error_302(self, req, fp, code, msg, headers): fp.status, fp.reason = code, msg return fp http_error_301 = http_error_302 http_error_303 = http_error_302 http_error_307 = http_error_302 http_error_308 = http_error_302 _opener = urllib.request.build_opener(NoRedirect) class Tap(http.server.BaseHTTPRequestHandler): def do_GET(self): self._tap('GET') def do_POST(self): self._tap('POST') def do_PUT(self): self._tap('PUT') def do_DELETE(self): self._tap('DELETE') def _tap(self, method): now = datetime.now().strftime('%H:%M:%S') msg = "\n" + "="*60 + "\n[" + now + "] " + method + " " + self.path msg += "\nClient: " + self.client_address[0] + "\n" + "-"*60 + "\n" for k, v in sorted(self.headers.items()): msg += " " + k + ": " + v + "\n" # Handle chunked transfer-encoding body = None cl = self.headers.get('Content-Length') te = self.headers.get('Transfer-Encoding', '').lower() if cl: body = self.rfile.read(int(cl)) elif 'chunked' in te: body = b'' while True: line = self.rfile.readline().strip() if not line: continue size = int(line, 16) if size == 0: break body += self.rfile.read(size) self.rfile.readline() while True: line = self.rfile.readline().strip() if not line: break req = urllib.request.Request(BACKEND_URL + self.path, data=body, method=method) # Pass through all headers including Host for k, v in self.headers.items(): if k.lower() not in ('connection',): req.add_header(k, v) try: resp = _opener.open(req, timeout=30) msg += "\nRESPONSE: " + str(resp.status) + " " + str(resp.reason) + "\n" for k, v in resp.getheaders(): msg += " " + k + ": " + v + "\n" self.send_response(resp.status) for k, v in resp.getheaders(): if k.lower() not in ('transfer-encoding', 'connection'): self.send_header(k, v) self.end_headers() self.wfile.write(resp.read()) except Exception as e: msg += "\nBACKEND: " + str(e) + "\n" self.send_response(502) self.send_header('Content-Type', 'text/plain') self.end_headers() self.wfile.write(b"Backend unreachable: " + str(e).encode()) with open(LOG, "a") as f: f.write(msg) def log_message(self, *args): pass print("Tap proxy :{0} -> {1}\nLog: {2}\nCtrl+C to stop".format(PORT, BACKEND_URL, LOG)) http.server.HTTPServer(('0.0.0.0', PORT), Tap).serve_forever()