Deploy commited on
Commit
3dc5fc4
·
1 Parent(s): 6391701

Deploy proxy

Browse files
Files changed (3) hide show
  1. Dockerfile +4 -8
  2. app.py +123 -13
  3. requirements.txt +3 -1
Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- FROM python:3.9
2
 
3
  WORKDIR /code
4
 
@@ -6,11 +6,9 @@ COPY ./requirements.txt /code/requirements.txt
6
 
7
  RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
8
 
9
- # Set up a new user named "user" with user ID 1000
10
  RUN useradd -m -u 1000 user
11
- # Switch to the "user" user
12
  USER user
13
- # Set home to the user's home directory
14
  ENV HOME=/home/user \
15
  PATH=/home/user/.local/bin:$PATH \
16
  PYTHONPATH=$HOME/app \
@@ -20,11 +18,9 @@ ENV HOME=/home/user \
20
  GRADIO_SERVER_NAME=0.0.0.0 \
21
  GRADIO_THEME=huggingface \
22
  SYSTEM=spaces
23
-
24
- # Set the working directory to the user's home directory
25
  WORKDIR $HOME/app
26
 
27
- # Copy the current directory contents into the container at $HOME/app setting the owner to the user
28
  COPY --chown=user . $HOME/app
29
 
30
- CMD ["python", "app.py"]
 
1
+ FROM python:3.12
2
 
3
  WORKDIR /code
4
 
 
6
 
7
  RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
8
 
 
9
  RUN useradd -m -u 1000 user
 
10
  USER user
11
+
12
  ENV HOME=/home/user \
13
  PATH=/home/user/.local/bin:$PATH \
14
  PYTHONPATH=$HOME/app \
 
18
  GRADIO_SERVER_NAME=0.0.0.0 \
19
  GRADIO_THEME=huggingface \
20
  SYSTEM=spaces
21
+
 
22
  WORKDIR $HOME/app
23
 
 
24
  COPY --chown=user . $HOME/app
25
 
26
+ CMD ["python", "app.py"]
app.py CHANGED
@@ -1,13 +1,123 @@
1
- import gradio as gr
2
- def update(name):
3
- return f"Welcome to Gradio, {name}!"
4
-
5
- with gr.Blocks() as demo:
6
- gr.Markdown("Start typing below and then click **Run** to see the output.")
7
- with gr.Row():
8
- inp = gr.Textbox(placeholder="What is your name?")
9
- out = gr.Textbox()
10
- btn = gr.Button("Run")
11
- btn.click(fn=update, inputs=inp, outputs=out)
12
-
13
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Request, Response
2
+ from fastapi.responses import JSONResponse, PlainTextResponse
3
+ import httpx
4
+ import os
5
+ import sys
6
+ from collections import deque
7
+
8
+ app = FastAPI()
9
+
10
+ # 内存日志队列(最多保存1000条)
11
+ log_buffer = deque(maxlen=1000)
12
+
13
+ def log_message(msg):
14
+ """记录日志到内存和标准输出"""
15
+ log_buffer.append(msg)
16
+ print(msg, flush=True)
17
+
18
+ @app.get("/")
19
+ @app.head("/")
20
+ async def root():
21
+ return {"status": "ok", "service": "HTTP Proxy Node"}
22
+
23
+ @app.get("/logs")
24
+ async def get_logs():
25
+ """获取最近的日志"""
26
+ return PlainTextResponse("\n".join(log_buffer))
27
+
28
+ @app.post("/api/proxy")
29
+ async def proxy(request: Request):
30
+ """标准 HTTP 代理 API"""
31
+ import time
32
+ start_time = time.time()
33
+
34
+ try:
35
+ # 解析请求
36
+ parse_start = time.time()
37
+ data = await request.json()
38
+
39
+ target_url = data.get("url")
40
+ method = data.get("method", "GET")
41
+ headers = data.get("headers", {})
42
+ body = data.get("body")
43
+
44
+ log_message(f"[PROXY] {method} {target_url[:80]} - parse: {(time.time()-parse_start)*1000:.0f}ms")
45
+
46
+ if not target_url:
47
+ return JSONResponse(
48
+ {"error": "Missing url parameter"},
49
+ status_code=400
50
+ )
51
+
52
+ # 转换 body(如果是 base64)
53
+ if body and isinstance(body, str):
54
+ import base64
55
+ try:
56
+ body = base64.b64decode(body)
57
+ except:
58
+ body = body.encode('utf-8')
59
+
60
+ # 发送请求(异步)
61
+ fetch_start = time.time()
62
+ log_message(f"[FETCH_START] {method} {target_url[:80]}")
63
+
64
+ try:
65
+ # 设置更详细的超时
66
+ timeout_config = httpx.Timeout(
67
+ connect=10.0, # 连接超时
68
+ read=30.0, # 读取超时
69
+ write=10.0, # 写入超时
70
+ pool=10.0 # 连接池超时
71
+ )
72
+
73
+ async with httpx.AsyncClient(timeout=timeout_config, follow_redirects=True) as client:
74
+ resp = await client.request(
75
+ method=method,
76
+ url=target_url,
77
+ headers=headers,
78
+ content=body
79
+ )
80
+
81
+ log_message(f"[FETCH] {resp.status_code} {len(resp.content)} bytes - {(time.time()-fetch_start)*1000:.0f}ms")
82
+ except httpx.TimeoutException as e:
83
+ error_msg = f"{type(e).__name__}: {str(e) or repr(e)}"
84
+ log_message(f"[FETCH_TIMEOUT] {error_msg} - {(time.time()-fetch_start)*1000:.0f}ms")
85
+ raise
86
+ except httpx.HTTPError as e:
87
+ error_msg = f"{type(e).__name__}: {str(e) or repr(e)}"
88
+ log_message(f"[FETCH_HTTP_ERROR] {error_msg} - {(time.time()-fetch_start)*1000:.0f}ms")
89
+ raise
90
+ except Exception as fetch_error:
91
+ error_msg = f"{type(fetch_error).__name__}: {str(fetch_error) or repr(fetch_error)}"
92
+ log_message(f"[FETCH_ERROR] {error_msg} - {(time.time()-fetch_start)*1000:.0f}ms")
93
+ import traceback
94
+ log_message(f"[TRACEBACK] {traceback.format_exc()}")
95
+ raise
96
+
97
+ # 返回响应(过滤掉会冲突的 headers)
98
+ response_headers = {}
99
+ for k, v in resp.headers.items():
100
+ k_lower = k.lower()
101
+ # 跳过这些 headers,让 FastAPI 自动处理
102
+ if k_lower not in ['content-length', 'content-encoding', 'transfer-encoding']:
103
+ response_headers[k] = v
104
+
105
+ log_message(f"[DONE] Total: {(time.time()-start_time)*1000:.0f}ms")
106
+
107
+ return Response(
108
+ content=resp.content,
109
+ status_code=resp.status_code,
110
+ headers=response_headers
111
+ )
112
+
113
+ except Exception as e:
114
+ log_message(f"[ERROR] {str(e)} - {(time.time()-start_time)*1000:.0f}ms")
115
+ return JSONResponse(
116
+ {"error": str(e)},
117
+ status_code=500
118
+ )
119
+
120
+ if __name__ == "__main__":
121
+ import uvicorn
122
+ port = int(os.environ.get("PORT", 7860))
123
+ uvicorn.run(app, host="0.0.0.0", port=port)
requirements.txt CHANGED
@@ -1 +1,3 @@
1
- gradio==4.5.0
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ httpx