StarrySkyWorld commited on
Commit
2d3d21c
·
verified ·
1 Parent(s): bbebe5d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +154 -26
app.py CHANGED
@@ -2,22 +2,152 @@ from fastapi import FastAPI, Request, Response
2
  from fastapi.responses import StreamingResponse
3
  import httpx
4
  import os
 
5
 
6
  app = FastAPI()
7
 
8
  TARGET_URL = "https://generativelanguage.googleapis.com"
9
  TIMEOUT = 120.0
10
 
11
- # 可选:设置访问密码
12
  ACCESS_PASSWORD = os.getenv("ACCESS_PASSWORD", "")
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  @app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"])
15
  async def proxy(request: Request, path: str):
16
- # 简单的密码验证(可选)
17
  if ACCESS_PASSWORD:
18
  auth_header = request.headers.get("X-Proxy-Password", "")
19
  if auth_header != ACCESS_PASSWORD:
20
- return Response(content='{"error": "Unauthorized"}', status_code=401, media_type="application/json")
 
 
 
 
21
 
22
  # 构建目标 URL
23
  target_url = f"{TARGET_URL}/{path}"
@@ -27,18 +157,19 @@ async def proxy(request: Request, path: str):
27
  # 获取请求体
28
  body = await request.body()
29
 
30
- # 过滤 headers
31
- headers = {}
32
- for key, value in request.headers.items():
33
- if key.lower() not in ("host", "x-proxy-password", "content-length"):
34
- headers[key] = value
35
 
36
- async with httpx.AsyncClient(timeout=TIMEOUT) as client:
37
- # 检���是否为流式请求
38
- is_stream = "stream" in path.lower() or b'"stream":true' in body.lower() if body else False
 
 
 
 
 
39
 
40
  if is_stream:
41
- # 流式响应
42
  req = client.build_request(
43
  method=request.method,
44
  url=target_url,
@@ -55,11 +186,10 @@ async def proxy(request: Request, path: str):
55
  return StreamingResponse(
56
  generate(),
57
  status_code=response.status_code,
58
- headers=dict(response.headers),
59
  media_type=response.headers.get("content-type")
60
  )
61
  else:
62
- # 普通响应
63
  response = await client.request(
64
  method=request.method,
65
  url=target_url,
@@ -67,23 +197,21 @@ async def proxy(request: Request, path: str):
67
  content=body
68
  )
69
 
70
- # 过滤响应 headers
71
- resp_headers = {}
72
- for key, value in response.headers.items():
73
- if key.lower() not in ("content-encoding", "transfer-encoding", "content-length"):
74
- resp_headers[key] = value
75
-
76
  return Response(
77
  content=response.content,
78
  status_code=response.status_code,
79
- headers=resp_headers,
80
  media_type=response.headers.get("content-type")
81
  )
82
 
 
83
  @app.get("/")
84
  async def root():
85
- return {
86
- "status": "running",
87
- "message": "Gemini API Proxy",
88
- "usage": "Replace https://generativelanguage.googleapis.com with this proxy URL"
89
- }
 
 
 
 
2
  from fastapi.responses import StreamingResponse
3
  import httpx
4
  import os
5
+ import random
6
 
7
  app = FastAPI()
8
 
9
  TARGET_URL = "https://generativelanguage.googleapis.com"
10
  TIMEOUT = 120.0
11
 
 
12
  ACCESS_PASSWORD = os.getenv("ACCESS_PASSWORD", "")
13
 
14
+ # ============== 匿名化配置 ==============
15
+
16
+ # 需要移除的泄露源 IP 的请求头
17
+ HEADERS_TO_REMOVE = {
18
+ # IP 相关
19
+ "x-forwarded-for",
20
+ "x-real-ip",
21
+ "x-originating-ip",
22
+ "x-remote-ip",
23
+ "x-remote-addr",
24
+ "x-client-ip",
25
+ "x-host",
26
+ "x-forwarded-host",
27
+ "x-forwarded-proto",
28
+ "x-forwarded-port",
29
+ "x-forwarded-server",
30
+ "forwarded",
31
+ "via",
32
+ "true-client-ip",
33
+ "cf-connecting-ip",
34
+ "cf-ipcountry",
35
+ "cf-ray",
36
+ "cf-visitor",
37
+
38
+ # HF/云服务商特征
39
+ "x-request-id",
40
+ "x-trace-id",
41
+ "x-amzn-trace-id",
42
+ "x-cloud-trace-context",
43
+ "x-appengine-country",
44
+ "x-appengine-city",
45
+ "x-appengine-region",
46
+ "x-vercel-id",
47
+ "x-vercel-ip-country",
48
+ "x-vercel-ip-city",
49
+ "x-railway-request-id",
50
+
51
+ # 浏览器/客户端指纹
52
+ "x-correlation-id",
53
+ "x-session-id",
54
+ "x-device-id",
55
+
56
+ # 其他
57
+ "host",
58
+ "content-length",
59
+ "x-proxy-password",
60
+ "connection",
61
+ "keep-alive",
62
+ "proxy-connection",
63
+ "proxy-authorization",
64
+ "te",
65
+ "trailer",
66
+ "transfer-encoding",
67
+ "upgrade",
68
+ }
69
+
70
+ # 伪装的 User-Agent 池
71
+ USER_AGENTS = [
72
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
73
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
74
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0",
75
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
76
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.2 Safari/605.1.15",
77
+ ]
78
+
79
+ # 伪装的 Accept-Language 池
80
+ ACCEPT_LANGUAGES = [
81
+ "en-US,en;q=0.9",
82
+ "en-GB,en;q=0.9",
83
+ "en-US,en;q=0.9,zh-CN;q=0.8",
84
+ "en,zh-CN;q=0.9,zh;q=0.8",
85
+ ]
86
+
87
+
88
+ def anonymize_headers(original_headers: dict) -> dict:
89
+ """清理并匿名化请求头"""
90
+ clean_headers = {}
91
+
92
+ for key, value in original_headers.items():
93
+ key_lower = key.lower()
94
+
95
+ # 跳过需要移除的头
96
+ if key_lower in HEADERS_TO_REMOVE:
97
+ continue
98
+
99
+ # 跳过任何包含 IP 特征的自定义头
100
+ if any(x in key_lower for x in ["ip", "forward", "real", "client", "trace", "ray", "cf-"]):
101
+ continue
102
+
103
+ clean_headers[key] = value
104
+
105
+ # 强制设置干净的头
106
+ clean_headers["User-Agent"] = random.choice(USER_AGENTS)
107
+ clean_headers["Accept-Language"] = random.choice(ACCEPT_LANGUAGES)
108
+ clean_headers["Accept"] = "application/json, text/plain, */*"
109
+ clean_headers["Accept-Encoding"] = "gzip, deflate, br"
110
+
111
+ # 确保不暴露是代理请求
112
+ clean_headers.pop("Forwarded", None)
113
+ clean_headers.pop("Via", None)
114
+
115
+ return clean_headers
116
+
117
+
118
+ def anonymize_response_headers(headers: dict) -> dict:
119
+ """清理响应头,移除可能暴露代理信息的字段"""
120
+ skip_headers = {
121
+ "content-encoding",
122
+ "transfer-encoding",
123
+ "content-length",
124
+ "alt-svc", # 可能暴露服务信息
125
+ "server", # 服务器信息
126
+ "x-served-by",
127
+ "x-cache",
128
+ "x-cache-hits",
129
+ "x-timer",
130
+ }
131
+
132
+ clean = {}
133
+ for key, value in headers.items():
134
+ if key.lower() not in skip_headers:
135
+ clean[key] = value
136
+
137
+ return clean
138
+
139
+
140
  @app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"])
141
  async def proxy(request: Request, path: str):
142
+ # 密码验证
143
  if ACCESS_PASSWORD:
144
  auth_header = request.headers.get("X-Proxy-Password", "")
145
  if auth_header != ACCESS_PASSWORD:
146
+ return Response(
147
+ content='{"error": "Unauthorized"}',
148
+ status_code=401,
149
+ media_type="application/json"
150
+ )
151
 
152
  # 构建目标 URL
153
  target_url = f"{TARGET_URL}/{path}"
 
157
  # 获取请求体
158
  body = await request.body()
159
 
160
+ # 匿名化请求头
161
+ headers = anonymize_headers(dict(request.headers))
 
 
 
162
 
163
+ # 使用干净的 httpx 客户端(不传递任何默认头)
164
+ async with httpx.AsyncClient(
165
+ timeout=TIMEOUT,
166
+ follow_redirects=True,
167
+ http2=True, # 使用 HTTP/2 更难被识别
168
+ ) as client:
169
+
170
+ is_stream = "stream" in path.lower() or (body and b'"stream":true' in body.lower())
171
 
172
  if is_stream:
 
173
  req = client.build_request(
174
  method=request.method,
175
  url=target_url,
 
186
  return StreamingResponse(
187
  generate(),
188
  status_code=response.status_code,
189
+ headers=anonymize_response_headers(dict(response.headers)),
190
  media_type=response.headers.get("content-type")
191
  )
192
  else:
 
193
  response = await client.request(
194
  method=request.method,
195
  url=target_url,
 
197
  content=body
198
  )
199
 
 
 
 
 
 
 
200
  return Response(
201
  content=response.content,
202
  status_code=response.status_code,
203
+ headers=anonymize_response_headers(dict(response.headers)),
204
  media_type=response.headers.get("content-type")
205
  )
206
 
207
+
208
  @app.get("/")
209
  async def root():
210
+ # 不暴露任何有用信息
211
+ return {"status": "ok"}
212
+
213
+
214
+ # 健康检查也保持匿名
215
+ @app.get("/health")
216
+ async def health():
217
+ return {"status": "ok"}