rkihacker commited on
Commit
eab2c9c
·
verified ·
1 Parent(s): 6a8ddc4

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +65 -24
main.py CHANGED
@@ -3,11 +3,21 @@ from fastapi import FastAPI, Request, HTTPException
3
  from starlette.responses import StreamingResponse
4
  from starlette.background import BackgroundTask
5
  import os
 
6
  from contextlib import asynccontextmanager
7
 
8
  # --- Configuration ---
9
  # The target URL is configurable via an environment variable.
10
  TARGET_URL = os.getenv("TARGET_URL", "https://api.gmi-serving.com/v1/chat")
 
 
 
 
 
 
 
 
 
11
 
12
  # --- HTTPX Client Lifecycle Management ---
13
  @asynccontextmanager
@@ -29,8 +39,8 @@ app = FastAPI(docs_url=None, redoc_url=None, lifespan=lifespan)
29
  # --- Reverse Proxy Logic ---
30
  async def _reverse_proxy(request: Request):
31
  """
32
- Forwards a request specifically for the /completions endpoint to the target URL.
33
- It injects required headers and allows for a user-provided Authorization header.
34
  """
35
  client: httpx.AsyncClient = request.app.state.http_client
36
 
@@ -48,7 +58,10 @@ async def _reverse_proxy(request: Request):
48
  # 2. Get the user's authorization key from the incoming request.
49
  authorization_header = request.headers.get("authorization")
50
 
51
- # 3. Set the specific, required headers for the target API.
 
 
 
52
  # This will overwrite any conflicting headers from the original request.
53
  specific_headers = {
54
  "accept": "application/json, text/plain, */*",
@@ -64,35 +77,63 @@ async def _reverse_proxy(request: Request):
64
  "sec-fetch-mode": "cors",
65
  "sec-fetch-site": "same-origin",
66
  "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
 
 
67
  }
68
  request_headers.update(specific_headers)
69
 
70
- # 4. Add the user's authorization key to the headers if it exists.
71
  if authorization_header:
72
  request_headers["authorization"] = authorization_header
73
 
74
- # Build the final request to the target service.
75
- rp_req = client.build_request(
76
- method=request.method,
77
- url=url,
78
- headers=request_headers,
79
- content=await request.body(),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  )
81
 
82
- try:
83
- # Send the request and get a streaming response.
84
- rp_resp = await client.send(rp_req, stream=True)
85
- except httpx.ConnectError as e:
86
- # This error occurs if the target service is down or unreachable.
87
- raise HTTPException(status_code=502, detail=f"Bad Gateway: Cannot connect to target service. {e}")
88
-
89
- # Stream the response from the target service back to the original client.
90
- return StreamingResponse(
91
- rp_resp.aiter_raw(),
92
- status_code=rp_resp.status_code,
93
- headers=rp_resp.headers,
94
- background=BackgroundTask(rp_resp.aclose),
95
- )
96
 
97
  # --- API Endpoint ---
98
  @app.api_route(
 
3
  from starlette.responses import StreamingResponse
4
  from starlette.background import BackgroundTask
5
  import os
6
+ import random
7
  from contextlib import asynccontextmanager
8
 
9
  # --- Configuration ---
10
  # The target URL is configurable via an environment variable.
11
  TARGET_URL = os.getenv("TARGET_URL", "https://api.gmi-serving.com/v1/chat")
12
+ # Number of retries for specific error codes.
13
+ MAX_RETRIES = 5
14
+ # HTTP status codes that will trigger a retry.
15
+ RETRY_STATUS_CODES = {429, 502, 503, 504}
16
+
17
+ # --- Helper Function ---
18
+ def generate_random_ip():
19
+ """Generates a random, valid-looking IPv4 address."""
20
+ return ".".join(str(random.randint(1, 254)) for _ in range(4))
21
 
22
  # --- HTTPX Client Lifecycle Management ---
23
  @asynccontextmanager
 
39
  # --- Reverse Proxy Logic ---
40
  async def _reverse_proxy(request: Request):
41
  """
42
+ Forwards a request to the target URL with retry logic and spoofed IP headers.
43
+ It allows for a user-provided Authorization header.
44
  """
45
  client: httpx.AsyncClient = request.app.state.http_client
46
 
 
58
  # 2. Get the user's authorization key from the incoming request.
59
  authorization_header = request.headers.get("authorization")
60
 
61
+ # 3. Generate a random IP for spoofing headers.
62
+ random_ip = generate_random_ip()
63
+
64
+ # 4. Set the specific, required headers for the target API.
65
  # This will overwrite any conflicting headers from the original request.
66
  specific_headers = {
67
  "accept": "application/json, text/plain, */*",
 
77
  "sec-fetch-mode": "cors",
78
  "sec-fetch-site": "same-origin",
79
  "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
80
+ "x-forwarded-for": random_ip,
81
+ "x-real-ip": random_ip,
82
  }
83
  request_headers.update(specific_headers)
84
 
85
+ # 5. Add the user's authorization key to the headers if it exists.
86
  if authorization_header:
87
  request_headers["authorization"] = authorization_header
88
 
89
+ # Read the request body once, as it will be reused in case of retries.
90
+ body = await request.body()
91
+
92
+ # --- Retry Logic ---
93
+ last_exception = None
94
+ for attempt in range(MAX_RETRIES):
95
+ try:
96
+ # Build the request for each attempt to ensure the content stream is fresh.
97
+ rp_req = client.build_request(
98
+ method=request.method,
99
+ url=url,
100
+ headers=request_headers,
101
+ content=body,
102
+ )
103
+ # Send the request and get a streaming response.
104
+ rp_resp = await client.send(rp_req, stream=True)
105
+
106
+ # If the status code is not in our retry list, we can return the response.
107
+ if rp_resp.status_code not in RETRY_STATUS_CODES:
108
+ return StreamingResponse(
109
+ rp_resp.aiter_raw(),
110
+ status_code=rp_resp.status_code,
111
+ headers=rp_resp.headers,
112
+ background=BackgroundTask(rp_resp.aclose),
113
+ )
114
+
115
+ # If it's a retryable error but this is the last attempt, return the error response.
116
+ if attempt == MAX_RETRIES - 1:
117
+ return StreamingResponse(
118
+ rp_resp.aiter_raw(),
119
+ status_code=rp_resp.status_code,
120
+ headers=rp_resp.headers,
121
+ background=BackgroundTask(rp_resp.aclose),
122
+ )
123
+
124
+ # Otherwise, close the unsuccessful response before trying again.
125
+ await rp_resp.aclose()
126
+
127
+ except httpx.ConnectError as e:
128
+ last_exception = e
129
+ # Continue to the next attempt if a connection error occurs.
130
+
131
+ # If all retry attempts failed with a connection error, raise a final exception.
132
+ raise HTTPException(
133
+ status_code=502,
134
+ detail=f"Bad Gateway: Cannot connect to target service after {MAX_RETRIES} attempts. {last_exception}"
135
  )
136
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
 
138
  # --- API Endpoint ---
139
  @app.api_route(