Вопрос или проблема
Представьте, что существует простое приложение FastAPI, которое имеет один маршрут, который повторно транслирует ответ файла от внешнего API:
from fastapi import FastAPI
app = FastAPI()
@app.get(
"/download",
response_class=FileResponse,
responses={
200: {"content": {"application/octet-stream": {}}, "description": "Файл"},
},
)
async def download():
async def iterfile():
url = "http://external-api:8000/api/v1/attachments-archive/download?id__in=d6ace33a-c068-45ac-8e7c-60f30f262e09"
async with AsyncClient() as client:
async with client.stream("GET", url) as resp:
# resp.headers возвращает:
# headers = {
# 'content-disposition': "attachment;filename*=UTF-8''archive-2024-10-03T21%3A28%3A13.597138.zip",
# 'content-type': 'application/x-zip-compressed',
# ... другие заголовки
# }
async for chunk in resp.aiter_bytes():
yield chunk
return StreamingResponse(iterfile())
# но мне нужно указать (скопировать из resp) заголовки и media_type так:
# return StreamingResponse(
# iterfile(),
# headers={'content-disposition': "attachment;filename*=UTF-8''archive-2024-10-03T21%3A28%3A13.597138.zip"},
# media_type="application/x-zip-compressed"
# )
Внешний API возвращает zip-архив. Мне нужно повторно транслировать его. Чтобы транслировать ответ из моего приложения, я использую StreamingResponse.
Основная трудность заключается в том, чтобы указать headers
и media_type
для StreamingResponse. Как я могу сначала получить эти значения, а затем получить поток iterfile()
?
П.С. headers
и media_type
могут быть разными. Я не могу их захардкодить.
Ответ или решение
Чтобы реализовать restream
ответа от внешнего API в вашем приложении на FastAPI, вам нужно сначала получить заголовки и медиаподтип (media type) от ответа внешнего API, а затем передать их в StreamingResponse
.
Ниже приведен пример кода, который делает именно это:
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from httpx import AsyncClient
app = FastAPI()
@app.get(
"/download",
responses={
200: {"content": {"application/octet-stream": {}}, "description": "File"},
},
)
async def download():
url = "http://external-api:8000/api/v1/attachments-archive/download?id__in=d6ace33a-c068-45ac-8e7c-60f30f262e09"
async def iterfile():
async with AsyncClient() as client:
async with client.stream("GET", url) as resp:
# Получаем заголовки для ответа
headers = resp.headers
# Проверяем, что используем правильные заголовки
content_disposition = headers.get('content-disposition')
content_type = headers.get('content-type')
# Итерация по байтам ответа
async for chunk in resp.aiter_bytes():
yield chunk
# Создаем потоковый ответ, используя заголовки и медиаподтип, полученные из внешнего API
return StreamingResponse(
iterfile(),
headers={
'content-disposition': content_disposition,
'content-type': content_type
},
media_type=content_type
)
Объяснение кода:
-
Импорт необходимых библиотек:
- Мы используем FastAPI и
httpx
для асинхронного HTTP-запроса к внешнему API.
- Мы используем FastAPI и
-
Определение маршрута
/download
:- Мы прописываем маршрутизацию с использованием HTTP метода GET.
-
Функция
iterfile
:- В этой вложенной функции мы осуществляем асинхронный запрос к внешнему API.
- Получаем заголовки ответа (
resp.headers
), которые содержат информацию, необходимую для корректного формирования ответа (например,content-disposition
иcontent-type
). - Используем
resp.aiter_bytes()
для асинхронной итерации по байтам файла.
-
Возврат
StreamingResponse
:- При создании
StreamingResponse
передаемiterfile()
как источник данных, а также заголовки и медиаподтип, полученные из ответа внешнего API.
- При создании
Таким образом, данное решение позволяет "репликировать" поток данных от внешнего API в вашем FastAPI приложении, динамически устанавливая необходимые заголовки и тип медиа.