Files
jianda-proxy/lfs-proxy.py
2026-05-28 17:11:46 +08:00

87 lines
3.3 KiB
Python

#!/usr/bin/env python3
"""HTTP proxy for Gitea LFS - rewrites Host header to bypass ICP domain block.
Acts as both:
- Forward proxy: receives "GET http://git.zz.com:3000/..." and rewrites Host to IP
- Reverse proxy: receives "GET /..." directly and forwards to upstream with IP Host
Usage: python3 lfs-proxy.py [port]
"""
import http.client
import http.server
import sys
from urllib.parse import urlparse
LOCAL_PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 13000
REMOTE_DOMAIN = "git.zz.com"
UPSTREAM_IP = "62.234.191.215"
UPSTREAM_PORT = 3000
class ProxyHandler(http.server.BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1"
def _proxy_request(self, target_host, target_port, target_path, body=None):
body_len = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(body_len) if body_len else None
# Always rewrite Host to IP for upstream
conn = http.client.HTTPConnection(target_host, target_port, timeout=120)
try:
headers = {}
skip = {"host", "transfer-encoding", "connection"}
for k in self.headers:
if k.lower() not in skip:
headers[k] = self.headers[k]
headers["Host"] = f"{UPSTREAM_IP}:{UPSTREAM_PORT}"
if body_len:
headers["Content-Length"] = str(body_len)
conn.request(self.command, target_path, body=body, headers=headers)
resp = conn.getresponse()
resp_body = resp.read()
self.send_response(resp.status)
sent_cl = False
for k, v in resp.getheaders():
kl = k.lower()
if kl in ("transfer-encoding", "connection"):
continue
if kl == "content-length":
self.send_header(k, len(resp_body))
sent_cl = True
continue
self.send_header(k, v)
if not sent_cl:
self.send_header("Content-Length", len(resp_body))
self.end_headers()
self.wfile.write(resp_body)
finally:
conn.close()
def _do(self):
raw_path = self.path
parsed = urlparse(raw_path)
if parsed.hostname and parsed.hostname != "127.0.0.1" and parsed.hostname != "localhost":
# Forward proxy mode: full URL like "http://git.zz.com:3000/path"
target_host = UPSTREAM_IP if parsed.hostname == REMOTE_DOMAIN else parsed.hostname
target_port = parsed.port or 80
target_path = parsed.path + ("?" + parsed.query if parsed.query else "")
self._proxy_request(target_host, target_port, target_path)
else:
# Direct/reverse proxy mode: path like "/Mustapha/lfs-test.git/..."
# This happens when LFS downloads from URLs rewritten to point at us
self._proxy_request(UPSTREAM_IP, UPSTREAM_PORT, raw_path)
do_GET = do_POST = do_PUT = do_DELETE = do_HEAD = do_PATCH = _do
def log_message(self, fmt, *args):
print(f"[proxy] {self.address_string()} - {fmt % args}", file=sys.stderr, flush=True)
if __name__ == "__main__":
with http.server.ThreadingHTTPServer(("127.0.0.1", LOCAL_PORT), ProxyHandler) as s:
print(f"Proxy: 127.0.0.1:{LOCAL_PORT} -> {UPSTREAM_IP}:{UPSTREAM_PORT}", file=sys.stderr, flush=True)
s.serve_forever()