Initial commit
This commit is contained in:
44
README.md
Normal file
44
README.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# jianda-proxy
|
||||||
|
|
||||||
|
本地 HTTP 代理,解决腾讯云对未备案域名拦截导致的 Git LFS 下载失败问题。
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
内网 Gitea 通过反向隧道暴露到腾讯云,客户端通过 `hosts` 文件将域名指向云服务器 IP。SSH clone 正常,但 LFS 文件下载走 HTTP 协议,腾讯云检测到域名未备案,基于 Host 头拦截请求返回 302。
|
||||||
|
|
||||||
|
本代理将所有发往 `git.zz.com` 的请求的 Host 头改写为 IP 地址,绕过备案拦截。
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 1. 启动代理
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 lfs-proxy.py [port]
|
||||||
|
```
|
||||||
|
|
||||||
|
默认监听 `127.0.0.1:13000`,可指定其他端口。
|
||||||
|
|
||||||
|
### 2. 配置 Git 走代理(只需一次)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git config --global http.http://git.zz.com:3000/.proxy http://127.0.0.1:13000
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 正常 clone
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone ssh://git@git.zz.com:2222/Mustapha/lfs-test.git
|
||||||
|
```
|
||||||
|
|
||||||
|
## 原理
|
||||||
|
|
||||||
|
代理同时支持两种模式:
|
||||||
|
|
||||||
|
- **正向代理**:接收 `GET http://git.zz.com:3000/...` 形式的请求,改写 Host 头转发到上游
|
||||||
|
- **反向代理**:接收 `GET /...` 形式的请求,直接转发到上游并附带 IP Host 头
|
||||||
|
|
||||||
|
上游地址硬编码为 `62.234.191.215:3000`,可在脚本顶部修改。
|
||||||
|
|
||||||
|
## 根治方案
|
||||||
|
|
||||||
|
在 Gitea 服务端 `app.ini` 中将 `ROOT_URL` 从 `http://git.zz.com:3000` 改为 `http://62.234.191.215:3000`,可从根源解决,无需代理。
|
||||||
86
lfs-proxy.py
Normal file
86
lfs-proxy.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
#!/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()
|
||||||
Reference in New Issue
Block a user