Commit
·
bd7bd10
0
Parent(s):
feat: 初始化反向代理项目
Browse files- 添加 FastAPI 应用 (app.py)
- 添加依赖文件 (requirements.txt)
- 添加 Hugging Face Space 配置 (README.md)
- 添加 Dockerfile 用于构建环境
- 为 POST 请求添加日志记录
- Dockerfile +19 -0
- README.md +16 -0
- app.py +91 -0
- 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
|