yangtb2024 commited on
Commit
bd7bd10
·
0 Parent(s):

feat: 初始化反向代理项目

Browse files

- 添加 FastAPI 应用 (app.py)
- 添加依赖文件 (requirements.txt)
- 添加 Hugging Face Space 配置 (README.md)
- 添加 Dockerfile 用于构建环境
- 为 POST 请求添加日志记录

Files changed (4) hide show
  1. Dockerfile +19 -0
  2. README.md +16 -0
  3. app.py +91 -0
  4. requirements.txt +3 -0
Dockerfile ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 使用官方 Python 镜像作为基础镜像
2
+ FROM python:3.9-slim
3
+
4
+ # 设置工作目录
5
+ WORKDIR /app
6
+
7
+ # 复制依赖文件并安装依赖
8
+ COPY requirements.txt requirements.txt
9
+ RUN pip install --no-cache-dir --upgrade pip && \
10
+ pip install --no-cache-dir -r requirements.txt
11
+
12
+ # 复制应用代码到工作目录
13
+ COPY . .
14
+
15
+ # 暴露 FastAPI 应用运行的端口 (与 README.md 中的 app_port 一致)
16
+ EXPOSE 7860
17
+
18
+ # 运行应用的命令
19
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
README.md ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Reverse Proxy
3
+ emoji: 🔄
4
+ colorFrom: blue
5
+ colorTo: green
6
+ sdk: docker
7
+ app_port: 7860
8
+ ---
9
+
10
+ # Reverse Proxy Space
11
+
12
+ 这是一个简单的 FastAPI 应用,用于反向代理请求。
13
+
14
+ 访问 `https://[your-space-id].hf.space/[target-url]` 将会代理请求到 `https://[target-url]`。
15
+
16
+ 例如: `https://[your-space-id].hf.space/google.com`
app.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ from fastapi import FastAPI, Request, Response
3
+ from fastapi.responses import StreamingResponse
4
+ import uvicorn
5
+ import os
6
+ import logging # 添加 logging 导入
7
+
8
+ # 配置日志记录
9
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
10
+
11
+ app = FastAPI()
12
+
13
+ # 允许的目标域名列表(可选,增加安全性)
14
+ # ALLOWED_HOSTS = {"google.com", "example.com"}
15
+
16
+ @app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"])
17
+ async def reverse_proxy(request: Request, path: str):
18
+ """
19
+ 反向代理到目标 URL。
20
+ 从路径中提取目标 URL,例如 /google.com/search?q=test -> https://google.com/search?q=test
21
+ """
22
+ target_url_str = path
23
+
24
+ # 简单的检查,确保路径看起来像一个域名或包含协议
25
+ if not ('.' in target_url_str or target_url_str.startswith(('http://', 'https://'))):
26
+ return Response(content="Invalid target URL format in path.", status_code=400)
27
+
28
+ # 如果路径不包含协议,默认添加 https://
29
+ if not target_url_str.startswith(('http://', 'https://')):
30
+ target_url_str = f"https://{target_url_str}"
31
+
32
+ # 提取域名进行检查(可选)
33
+ # try:
34
+ # from urllib.parse import urlparse
35
+ # parsed_url = urlparse(target_url_str)
36
+ # if parsed_url.netloc not in ALLOWED_HOSTS:
37
+ # return Response(content=f"Host not allowed: {parsed_url.netloc}", status_code=403)
38
+ # except Exception:
39
+ # return Response(content="Could not parse target URL.", status_code=400)
40
+
41
+ # 准备目标请求的 headers,复制客户端 headers,但移除 Host
42
+ headers = {key: value for key, value in request.headers.items() if key.lower() != 'host'}
43
+ # 可以选择性地添加或修改 headers
44
+ # headers['X-Forwarded-For'] = request.client.host
45
+
46
+ # 获取请求体
47
+ body = await request.body()
48
+
49
+ # 如果是 POST 请求,记录请求体内容
50
+ if request.method == "POST":
51
+ try:
52
+ # 尝试解码为 UTF-8 文本进行记录,如果失败则记录原始字节
53
+ body_text = body.decode('utf-8')
54
+ logging.info(f"Received POST request to {path}. Body: {body_text}")
55
+ except UnicodeDecodeError:
56
+ logging.info(f"Received POST request to {path}. Body (bytes): {body}")
57
+
58
+ try:
59
+ # 发送请求到目标服务器,允许重定向,不验证 SSL 证书(在某些情况下可能需要)
60
+ target_response = requests.request(
61
+ method=request.method,
62
+ url=target_url_str,
63
+ headers=headers,
64
+ data=body,
65
+ stream=True, # 使用流式传输处理大文件或长时间响应
66
+ allow_redirects=True, # 允许目标服务器重定向
67
+ verify=True # 通常应保持 True,除非你知道目标证书有问题
68
+ )
69
+
70
+ # 过滤掉不应转发的响应头 (例如 'transfer-encoding', 'content-encoding', 'content-length')
71
+ response_headers = {
72
+ k: v for k, v in target_response.headers.items()
73
+ if k.lower() not in ['transfer-encoding', 'content-encoding', 'content-length']
74
+ }
75
+
76
+ # 使用 StreamingResponse 将目标响应流式传输回客户端
77
+ return StreamingResponse(
78
+ target_response.iter_content(chunk_size=8192),
79
+ status_code=target_response.status_code,
80
+ headers=response_headers,
81
+ media_type=target_response.headers.get('content-type')
82
+ )
83
+
84
+ except requests.exceptions.RequestException as e:
85
+ return Response(content=f"Error connecting to target server: {e}", status_code=502) # Bad Gateway
86
+ except Exception as e:
87
+ return Response(content=f"An unexpected error occurred: {e}", status_code=500)
88
+
89
+ if __name__ == "__main__":
90
+ port = int(os.environ.get("PORT", 7860)) # Hugging Face Spaces 通常使用 7860 端口
91
+ uvicorn.run(app, host="0.0.0.0", port=port)
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ fastapi
2
+ uvicorn[standard]
3
+ requests