Rooni commited on
Commit
8a272dd
·
verified ·
1 Parent(s): b6c7964

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +48 -23
app.py CHANGED
@@ -8,47 +8,72 @@ app = FastAPI()
8
  OR_BASE_URL = "https://api.openai.com/v1"
9
 
10
  # Создаем один асинхронный клиент на все приложение для переиспользования соединений
11
- client = httpx.AsyncClient(base_url=OR_BASE_URL)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  @app.get("/")
14
  async def home():
15
- return {"status": "ok", "message": "Async OpenRouter proxy is working :3"}
16
 
17
 
18
  @app.api_route("/{endpoint:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
19
  async def proxy(endpoint: str, request: Request):
20
- # Копируем заголовки, кроме тех, что не должны пересылаться
21
- headers = {k: v for k, v in request.headers.items() if k.lower() not in ["host", "content-length", "content-encoding"]}
22
-
23
  try:
24
- # Асинхронно создаем запрос к удаленному серверу
 
 
 
 
 
 
 
 
 
 
25
  req = client.build_request(
26
  method=request.method,
27
- url=f"/{endpoint}",
28
  headers=headers,
29
- params=request.query_params,
30
- content=await request.body() # Асинхронно читаем тело входящего запроса
31
  )
32
-
33
- # `stream=True` здесь работает по-настоящему асинхронно
34
  resp = await client.send(req, stream=True)
35
 
36
- # Асинхронный генератор для потоковой передачи ответа
37
- async def async_generator():
38
- async for chunk in resp.aiter_bytes():
39
- yield chunk
40
-
41
- # Копируем заголовки ответа
42
- response_headers = {k: v for k, v in resp.headers.items() if k.lower() not in ["content-encoding", "transfer-encoding", "connection"]}
 
43
 
 
 
 
 
44
  return StreamingResponse(
45
- async_generator(),
46
  status_code=resp.status_code,
47
  headers=response_headers,
48
  )
 
49
  except httpx.RequestError as e:
50
- return {"error": f"HTTPX error: {str(e)}"}, 500
 
51
  except Exception as e:
52
- return {"error": str(e)}, 500
53
-
54
- # Для запуска: uvicorn your_file_name:app --host 0.0.0.0 --port 7860
 
8
  OR_BASE_URL = "https://api.openai.com/v1"
9
 
10
  # Создаем один асинхронный клиент на все приложение для переиспользования соединений
11
+ # allow_redirects=False, т.к. мы хотим сами управлять редиректами как прокси
12
+ client = httpx.AsyncClient(base_url=OR_BASE_URL, follow_redirects=False)
13
+
14
+ # Список "hop-by-hop" заголовков, которые не должны проксироваться дальше.
15
+ # Это стандартная практика для HTTP прокси.
16
+ HOP_BY_HOP_HEADERS = {
17
+ "connection",
18
+ "keep-alive",
19
+ "proxy-authenticate",
20
+ "proxy-authorization",
21
+ "te",
22
+ "trailers",
23
+ "transfer-encoding",
24
+ "upgrade",
25
+ }
26
+
27
 
28
  @app.get("/")
29
  async def home():
30
+ return {"status": "ok", "message": "Async Transparent OpenRouter proxy is working :3"}
31
 
32
 
33
  @app.api_route("/{endpoint:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
34
  async def proxy(endpoint: str, request: Request):
 
 
 
35
  try:
36
+ # 1. Строим новый запрос для отправки на целевой сервер
37
+ url = httpx.URL(path=f"/{endpoint}", query=request.url.query.encode("utf-8"))
38
+
39
+ # Копируем заголовки от клиента, исключая "host"
40
+ headers = {k: v for k, v in request.headers.items() if k.lower() != "host"}
41
+
42
+ # Асинхронно читаем тело входящего запроса
43
+ body = await request.body()
44
+
45
+ # 2. Отправляем запрос с потоковой передачей
46
+ # stream=True - критически важно для получения ответа по частям
47
  req = client.build_request(
48
  method=request.method,
49
+ url=url,
50
  headers=headers,
51
+ content=body
 
52
  )
 
 
53
  resp = await client.send(req, stream=True)
54
 
55
+ # 3. Копируем заголовки ответа от целевого сервера
56
+ # Исключаем только hop-by-hop заголовки. Все остальное (Content-Type, Content-Encoding,
57
+ # кастомные x-openai-*, и т.д.) будет скопировано.
58
+ response_headers = [
59
+ (key, value)
60
+ for key, value in resp.headers.items()
61
+ if key.lower() not in HOP_BY_HOP_HEADERS
62
+ ]
63
 
64
+ # 4. Возвращаем StreamingResponse
65
+ # resp.aiter_raw() - это "магия". Этот метод отдает сырые байты, как они приходят
66
+ # по сети, БЕЗ какой-либо обработки (например, без распаковки gzip).
67
+ # Это именно то, что нам нужно для идеального прокси.
68
  return StreamingResponse(
69
+ resp.aiter_raw(),
70
  status_code=resp.status_code,
71
  headers=response_headers,
72
  )
73
+
74
  except httpx.RequestError as e:
75
+ # Ошибки, связанные с сетью или HTTP
76
+ return {"error": f"Proxy request error: {str(e)}"}, 502 # 502 Bad Gateway - подходящий код
77
  except Exception as e:
78
+ # Другие непредвиденные ошибки на стороне прокси
79
+ return {"error": f"Internal proxy error: {str(e)}"}, 500