Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -8,47 +8,72 @@ app = FastAPI()
|
|
| 8 |
OR_BASE_URL = "https://api.openai.com/v1"
|
| 9 |
|
| 10 |
# Создаем один асинхронный клиент на все приложение для переиспользования соединений
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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=
|
| 28 |
headers=headers,
|
| 29 |
-
|
| 30 |
-
content=await request.body() # Асинхронно читаем тело входящего запроса
|
| 31 |
)
|
| 32 |
-
|
| 33 |
-
# `stream=True` здесь работает по-настоящему асинхронно
|
| 34 |
resp = await client.send(req, stream=True)
|
| 35 |
|
| 36 |
-
#
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
|
|
|
| 43 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
return StreamingResponse(
|
| 45 |
-
|
| 46 |
status_code=resp.status_code,
|
| 47 |
headers=response_headers,
|
| 48 |
)
|
|
|
|
| 49 |
except httpx.RequestError as e:
|
| 50 |
-
|
|
|
|
| 51 |
except Exception as e:
|
| 52 |
-
|
| 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
|
|
|