Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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 |
-
#
|
| 15 |
-
# Это стандартная практика для HTTP прокси.
|
| 16 |
HOP_BY_HOP_HEADERS = {
|
| 17 |
-
"connection",
|
| 18 |
-
"
|
| 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
|
| 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 |
-
#
|
| 56 |
-
#
|
| 57 |
-
#
|
|
|
|
|
|
|
|
|
|
| 58 |
response_headers = [
|
| 59 |
(key, value)
|
| 60 |
for key, value in resp.headers.items()
|
| 61 |
-
if key.lower() not in
|
| 62 |
]
|
| 63 |
-
|
| 64 |
-
#
|
| 65 |
-
#
|
| 66 |
-
# по сети, БЕЗ какой-либо обработки (например, без распаковки gzip).
|
| 67 |
-
# Это именно то, что нам нужно для идеального прокси.
|
| 68 |
return StreamingResponse(
|
| 69 |
-
resp.
|
| 70 |
status_code=resp.status_code,
|
| 71 |
headers=response_headers,
|
| 72 |
)
|
| 73 |
|
| 74 |
except httpx.RequestError as e:
|
| 75 |
-
|
| 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
|