#!/usr/bin/env python3 import argparse import http.client import shutil from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from urllib.parse import urlsplit, urlunsplit HOP_BY_HOP = { "connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "proxy-connection", "te", "trailers", "transfer-encoding", "upgrade", } class GiteaProxy(BaseHTTPRequestHandler): protocol_version = "HTTP/1.1" target_host = "192.168.10.248" def do_GET(self): self._forward() def do_HEAD(self): self._forward(send_body=False) def do_POST(self): self._forward() def do_PUT(self): self._forward() def _forward(self, send_body=True): parsed = urlsplit(self.path) path = urlunsplit(("", "", parsed.path or "/", parsed.query, "")) print( f"{self.command} content-length={self.headers.get('Content-Length')} " f"transfer-encoding={self.headers.get('Transfer-Encoding')}", flush=True, ) upstream = http.client.HTTPConnection(self.target_host, 80, timeout=3600) upstream.putrequest( self.command, path, skip_host=True, skip_accept_encoding=True, ) for name, value in self.headers.items(): lower_name = name.lower() if lower_name in HOP_BY_HOP: continue upstream.putheader(name, "gitea" if lower_name == "host" else value) transfer_encoding = self.headers.get("Transfer-Encoding", "").lower() if transfer_encoding == "chunked": upstream.putheader("Transfer-Encoding", "chunked") upstream.endheaders() if transfer_encoding == "chunked": self._forward_chunked_body(upstream) else: content_length = int(self.headers.get("Content-Length", "0")) remaining = content_length while remaining: chunk = self.rfile.read(min(1024 * 1024, remaining)) if not chunk: raise ConnectionError("request body ended early") upstream.send(chunk) remaining -= len(chunk) response = upstream.getresponse() self.send_response(response.status, response.reason) for name, value in response.getheaders(): if name.lower() not in HOP_BY_HOP: self.send_header(name, value) self.send_header("Connection", "close") self.end_headers() self.close_connection = True if send_body: shutil.copyfileobj(response, self.wfile, 1024 * 1024) upstream.close() def _forward_chunked_body(self, upstream): while True: size_line = self.rfile.readline() if not size_line: raise ConnectionError("chunked request ended before size line") upstream.send(size_line) chunk_size = int(size_line.split(b";", 1)[0].strip(), 16) if chunk_size == 0: while True: trailer_line = self.rfile.readline() upstream.send(trailer_line) if trailer_line in (b"\r\n", b"\n", b""): return remaining = chunk_size + 2 while remaining: data = self.rfile.read(min(1024 * 1024, remaining)) if not data: raise ConnectionError("chunked request ended inside data") upstream.send(data) remaining -= len(data) def log_message(self, fmt, *args): print("%s - %s" % (self.address_string(), fmt % args), flush=True) def main(): parser = argparse.ArgumentParser() parser.add_argument("--port", type=int, default=18765) args = parser.parse_args() server = ThreadingHTTPServer(("127.0.0.1", args.port), GiteaProxy) print(f"Listening on 127.0.0.1:{args.port}", flush=True) server.serve_forever() if __name__ == "__main__": main()