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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +16 -34
app.py CHANGED
@@ -7,43 +7,26 @@ app = FastAPI()
7
 
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,
@@ -52,28 +35,27 @@ async def proxy(endpoint: str, request: Request):
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
 
7
 
8
  OR_BASE_URL = "https://api.openai.com/v1"
9
 
 
 
10
  client = httpx.AsyncClient(base_url=OR_BASE_URL, follow_redirects=False)
11
 
12
+ # "Hop-by-hop" заголовки, которые не должны проксироваться.
 
13
  HOP_BY_HOP_HEADERS = {
14
+ "connection", "keep-alive", "proxy-authenticate", "proxy-authorization",
15
+ "te", "trailers", "transfer-encoding", "upgrade",
 
 
 
 
 
 
16
  }
17
 
 
18
  @app.get("/")
19
  async def home():
20
+ return {"status": "ok", "message": "Robust Async OpenRouter proxy is working :3"}
21
 
22
 
23
  @app.api_route("/{endpoint:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
24
  async def proxy(endpoint: str, request: Request):
25
  try:
 
26
  url = httpx.URL(path=f"/{endpoint}", query=request.url.query.encode("utf-8"))
 
 
27
  headers = {k: v for k, v in request.headers.items() if k.lower() != "host"}
 
 
28
  body = await request.body()
29
 
 
 
30
  req = client.build_request(
31
  method=request.method,
32
  url=url,
 
35
  )
36
  resp = await client.send(req, stream=True)
37
 
38
+ # Заголовки, которые мы должны удалить, потому что мы сами обрабатываем контент.
39
+ # Content-Encoding: мы сами распаковываем.
40
+ # Content-Length: его значение для сжатых данных неверно для распакованных.
41
+ # Transfer-Encoding: управляется сервером, который отправляет ответ.
42
+ EXCLUDED_HEADERS = HOP_BY_HOP_HEADERS | {"content-encoding", "content-length", "transfer-encoding"}
43
+
44
  response_headers = [
45
  (key, value)
46
  for key, value in resp.headers.items()
47
+ if key.lower() not in EXCLUDED_HEADERS
48
  ]
49
+
50
+ # Используем aiter_bytes(), который автоматически распаковывает контент (e.g. gzip).
51
+ # Это гарантирует, что мы отправляем дальше чистый, несжатый поток байт.
 
 
52
  return StreamingResponse(
53
+ resp.aiter_bytes(),
54
  status_code=resp.status_code,
55
  headers=response_headers,
56
  )
57
 
58
  except httpx.RequestError as e:
59
+ return {"error": f"Proxy request error: {str(e)}"}, 502
 
60
  except Exception as e:
 
61
  return {"error": f"Internal proxy error: {str(e)}"}, 500