콘텐츠로 이동

사용자 정의 응답 - HTML, Stream, 파일, 기타

기본적으로, FastAPI 응답을 JSONResponse를 사용하여 반환합니다.

이를 재정의 하려면 응답을 직접 반환하기에서 본 것처럼 Response를 직접 반환하면 됩니다.

그러나 Response (또는 JSONResponse와 같은 하위 클래스)를 직접 반환하면, 데이터가 자동으로 변환되지 않으며 (심지어 response_model을 선언했더라도), 문서화가 자동으로 생성되지 않습니다(예를 들어, 생성된 OpenAPI의 일부로 HTTP 헤더 Content-Type에 특정 "미디어 타입"을 포함하는 경우).

하지만 경로 작업 데코레이터에서 response_class 매개변수를 사용하여 원하는 Response(예: 모든 Response 하위 클래스)를 선언할 수도 있습니다.

경로 작업 함수에서 반환하는 내용은 해당 Response안에 포함됩니다.

그리고 만약 그 ResponseJSONResponseUJSONResponse의 경우 처럼 JSON 미디어 타입(application/json)을 가지고 있다면, 경로 작업 데코레이터에서 선언한 Pydantic의 response_model을 사용해 자동으로 변환(및 필터링) 됩니다.

참고

미디어 타입이 없는 응답 클래스를 사용하는 경우, FastAPI는 응답에 내용이 없을 것으로 예상하므로 생성된 OpenAPI 문서에서 응답 형식을 문서화하지 않습니다.

ORJSONResponse 사용하기

예를 들어, 성능을 극대화하려는 경우, orjson을 설치하여 사용하고 응답을 ORJSONResponse로 설정할 수 있습니다.

사용하고자 하는 Response 클래스(하위 클래스)를 임포트한 후, *경로 작업 데코레이터에서 선언하세요.

대규모 응답의 경우, 딕셔너리를 반환하는 것보다 Response를 반환하는 것이 훨씬 빠릅니다.

이유는 기본적으로, FastAPI가 내부의 모든 항목을 검사하고 JSON으로 직렬화할 수 있는지 확인하기 때문입니다. 이는 사용자 안내서에서 설명된 JSON 호환 가능 인코더를 사용하는 방식과 동일합니다. 이를 통해 데이터베이스 모델과 같은 임의의 객체를 반환할 수 있습니다.

하지만 반환하는 내용이 JSON으로 직렬화 가능하다고 확신하는 경우, 해당 내용을 응답 클래스에 직접 전달할 수 있으며, FastAPI가 반환 내용을 jsonable_encoder를 통해 처리한 뒤 응답 클래스에 전달하는 오버헤드를 피할 수 있습니다.

from fastapi import FastAPI
from fastapi.responses import ORJSONResponse

app = FastAPI()


@app.get("/items/", response_class=ORJSONResponse)
async def read_items():
    return ORJSONResponse([{"item_id": "Foo"}])

정보

response_class 매개변수는 응답의 "미디어 타입"을 정의하는 데에도 사용됩니다.

이 경우, HTTP 헤더 Content-Typeapplication/json으로 설정됩니다.

그리고 이는 OpenAPI에 그대로 문서화됩니다.

ORJSONResponse는 FastAPI에서만 사용할 수 있고 Starlette에서는 사용할 수 없습니다.

HTML 응답

FastAPI에서 HTML 응답을 직접 반환하려면 HTMLResponse를 사용하세요.

  • HTMLResponse를 임포트 합니다.
  • 경로 작업 데코레이터response_class 매개변수로 HTMLResponse를 전달합니다.
from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()


@app.get("/items/", response_class=HTMLResponse)
async def read_items():
    return """
    <html>
        <head>
            <title>Some HTML in here</title>
        </head>
        <body>
            <h1>Look ma! HTML!</h1>
        </body>
    </html>
    """

정보

response_class 매개변수는 응답의 "미디어 타입"을 정의하는 데에도 사용됩니다.

이 경우, HTTP 헤더 Content-Typetext/html로 설정됩니다.

그리고 이는 OpenAPI에 그대로 문서화 됩니다.

Response 반환하기

응답을 직접 반환하기에서 본 것 처럼, 경로 작업에서 응답을 직접 반환하여 재정의할 수도 있습니다.

위의 예제와 동일하게 HTMLResponse를 반환하는 코드는 다음과 같을 수 있습니다:

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()


@app.get("/items/")
async def read_items():
    html_content = """
    <html>
        <head>
            <title>Some HTML in here</title>
        </head>
        <body>
            <h1>Look ma! HTML!</h1>
        </body>
    </html>
    """
    return HTMLResponse(content=html_content, status_code=200)

경고

경로 작업 함수에서 직접 반환된 Response는 OpenAPI에 문서화되지 않습니다(예를들어, Content-Type이 문서화되지 않음) 자동 대화형 문서에서도 표시되지 않습니다.

정보

물론 실제 Content-Type 헤더, 상태 코드 등은 반환된 Response 객체에서 가져옵니다.

OpenAPI에 문서화하고 Response 재정의 하기

함수 내부에서 응답을 재정의하면서 동시에 OpenAPI에서 "미디어 타입"을 문서화하고 싶다면, response_class 매게변수를 사용하면서 Response 객체를 반환할 수 있습니다.

이 경우 response_class는 OpenAPI 경로 작업을 문서화하는 데만 사용되고, 실제로는 여러분이 반환한 Response가 그대로 사용됩니다.

HTMLResponse직접 반환하기

예를 들어, 다음과 같이 작성할 수 있습니다:

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()


def generate_html_response():
    html_content = """
    <html>
        <head>
            <title>Some HTML in here</title>
        </head>
        <body>
            <h1>Look ma! HTML!</h1>
        </body>
    </html>
    """
    return HTMLResponse(content=html_content, status_code=200)


@app.get("/items/", response_class=HTMLResponse)
async def read_items():
    return generate_html_response()

이 예제에서, generate_html_response() 함수는 HTML을 str로 반환하는 대신 이미 Response를 생성하고 반환합니다.

generate_html_response()를 호출한 결과를 반환함으로써, 기본적인 FastAPI 기본 동작을 재정의 하는 Response를 이미 반환하고 있습니다.

하지만 response_classHTMLResponse를 함께 전달했기 때문에, FastAPI는 이를 OpenAPI 및 대화형 문서에서 text/html로 HTML을 문서화 하는 방법을 알 수 있습니다.

사용 가능한 응답들

다음은 사용할 수 있는 몇가지 응답들 입니다.

Response를 사용하여 다른 어떤 것도 반환 할수 있으며, 직접 하위 클래스를 만들 수도 있습니다.

기술 세부사항

from starlette.responses import HTMLResponse를 사용할 수도 있습니다.

FastAPI는 개발자인 여러분의 편의를 위해 starlette.responsesfastapi.responses로 제공 하지만, 대부분의 사용 가능한 응답은 Starlette에서 직접 가져옵니다.

Response

기본 Response 클래스는 다른 모든 응답 클래스의 부모 클래스 입니다.

이 클래스를 직접 반환할 수 있습니다.

다음 매개변수를 받을 수 있습니다:

  • content - str 또는 bytes.
  • status_code - HTTP 상태코드를 나타내는 int.
  • headers - 문자열로 이루어진 dict.
  • media_type - 미디어 타입을 나타내는 str 예: "text/html".

FastAPI (실제로는 Starlette)가 자동으로 Content-Length 헤더를 포함시킵니다. 또한 media_type에 기반하여 Content-Type 헤더를 포함하며, 텍스트 타입의 경우 문자 집합을 추가 합니다.

from fastapi import FastAPI, Response

app = FastAPI()


@app.get("/legacy/")
def get_legacy_data():
    data = """<?xml version="1.0"?>
    <shampoo>
    <Header>
        Apply shampoo here.
    </Header>
    <Body>
        You'll have to use soap here.
    </Body>
    </shampoo>
    """
    return Response(content=data, media_type="application/xml")

HTMLResponse

텍스트 또는 바이트를 받아 HTML 응답을 반환합니다. 위에서 설명한 내용과 같습니다.

PlainTextResponse

텍스트 또는 바이트를 받아 일반 텍스트 응답을 반환합니다.

from fastapi import FastAPI
from fastapi.responses import PlainTextResponse

app = FastAPI()


@app.get("/", response_class=PlainTextResponse)
async def main():
    return "Hello World"

JSONResponse

데이터를 받아 application/json으로 인코딩된 응답을 반환합니다.

이는 위에서 설명했듯이 FastAPI에서 기본적으로 사용되는 응답 형식입니다.

ORJSONResponse

orjson을 사용하여 빠른 JSON 응답을 제공하는 대안입니다. 위에서 설명한 내용과 같습니다.

정보

이를 사용하려면 orjson을 설치해야합니다. 예: pip install orjson.

UJSONResponse

ujson을 사용한 또 다른 JSON 응답 형식입니다.

정보

이 응답을 사용하려면 ujson을 설치해야합니다. 예: 'pip install ujson`.

경고

ujson 은 일부 예외 경우를 처리하는 데 있어 Python 내장 구현보다 덜 엄격합니다.

from fastapi import FastAPI
from fastapi.responses import UJSONResponse

app = FastAPI()


@app.get("/items/", response_class=UJSONResponse)
async def read_items():
    return [{"item_id": "Foo"}]

ORJSONResponse가 더 빠른 대안일 가능성이 있습니다.

RedirectResponse

HTTP 리디렉션 응답을 반환합니다. 기본적으로 상태 코드는 307(임시 리디렉션)으로 설정됩니다.

RedirectResponse를 직접 반환할 수 있습니다.

from fastapi import FastAPI
from fastapi.responses import RedirectResponse

app = FastAPI()


@app.get("/typer")
async def redirect_typer():
    return RedirectResponse("https://typer.tiangolo.com")

또는 response_class 매개변수에서 사용할 수도 있습니다:

from fastapi import FastAPI
from fastapi.responses import RedirectResponse

app = FastAPI()


@app.get("/fastapi", response_class=RedirectResponse)
async def redirect_fastapi():
    return "https://fastapi.tiangolo.com"

이 경우, 경로 작업 함수에서 URL을 직접 반환할 수 있습니다.

이 경우, 사용되는 status_codeRedirectResponse의 기본값인 307 입니다.


status_code 매개변수를 response_class 매개변수와 함께 사용할 수도 있습니다:

from fastapi import FastAPI
from fastapi.responses import RedirectResponse

app = FastAPI()


@app.get("/pydantic", response_class=RedirectResponse, status_code=302)
async def redirect_pydantic():
    return "https://docs.pydantic.dev/"

StreamingResponse

비동기 제너레이터 또는 일반 제너레이터/이터레이터를 받아 응답 본문을 스트리밍 합니다.

from fastapi import FastAPI
from fastapi.responses import StreamingResponse

app = FastAPI()


async def fake_video_streamer():
    for i in range(10):
        yield b"some fake video bytes"


@app.get("/")
async def main():
    return StreamingResponse(fake_video_streamer())

파일과 같은 객체를 사용한 StreamingResponse

파일과 같은 객체(예: open()으로 반환된 객체)가 있는 경우, 해당 파일과 같은 객체를 반복(iterate)하는 제너레이터 함수를 만들 수 있습니다.

이 방식으로, 파일 전체를 메모리에 먼저 읽어들일 필요 없이, 제너레이터 함수를 StreamingResponse에 전달하여 반환할 수 있습니다.

이 방식은 클라우드 스토리지, 비디오 처리 등의 다양한 라이브러리와 함께 사용할 수 있습니다.

from fastapi import FastAPI
from fastapi.responses import StreamingResponse

some_file_path = "large-video-file.mp4"
app = FastAPI()


@app.get("/")
def main():
    def iterfile():  # (1)
        with open(some_file_path, mode="rb") as file_like:  # (2)
            yield from file_like  # (3)

    return StreamingResponse(iterfile(), media_type="video/mp4")
  1. 이것이 제너레이터 함수입니다. yield 문을 포함하고 있으므로 "제너레이터 함수"입니다.
  2. with 블록을 사용함으로써, 제너레이터 함수가 완료된 후 파일과 같은 객체가 닫히도록 합니다. 즉, 응답 전송이 끝난 후 닫힙니다.
  3. yield from은 함수가 file_like라는 객체를 반복(iterate)하도록 합니다. 반복된 각 부분은 이 제너레이터 함수(iterfile)에서 생성된 것처럼 yield 됩니다.

    이렇게 하면 "생성(generating)" 작업을 내부적으로 다른 무언가에 위임하는 제너레이터 함수가 됩니다.

이 방식을 사용하면 with 블록 안에서 파일을 열 수 있어, 작업이 완료된 후 파일과 같은 객체가 닫히는 것을 보장할 수 있습니다.

여기서 표준 open()을 사용하고 있기 때문에 asyncawait를 지원하지 않습니다. 따라서 경로 작업은 일반 def로 선언합니다.

FileResponse

파일을 비동기로 스트리밍하여 응답합니다.

다른 응답 유형과는 다른 인수를 사용하여 객체를 생성합니다:

  • path - 스트리밍할 파일의 경로.
  • headers - 딕셔너리 형식의 사용자 정의 헤더.
  • media_type - 미디어 타입을 나타내는 문자열. 설정되지 않은 경우 파일 이름이나 경로를 사용하여 추론합니다.
  • filename - 설정된 경우 응답의 Content-Disposition에 포함됩니다.

파일 응답에는 적절한 Content-Length, Last-Modified, 및 ETag 헤더가 포함됩니다.

from fastapi import FastAPI
from fastapi.responses import FileResponse

some_file_path = "large-video-file.mp4"
app = FastAPI()


@app.get("/")
async def main():
    return FileResponse(some_file_path)

또한 response_class 매개변수를 사용할 수도 있습니다:

from fastapi import FastAPI
from fastapi.responses import FileResponse

some_file_path = "large-video-file.mp4"
app = FastAPI()


@app.get("/", response_class=FileResponse)
async def main():
    return some_file_path

이 경우, 경로 작업 함수에서 파일 경로를 직접 반환할 수 있습니다.

사용자 정의 응답 클래스

Response를 상속받아 사용자 정의 응답 클래스를 생성하고 사용할 수 있습니다.

예를 들어, 포함된 ORJSONResponse 클래스에서 사용되지 않는 설정으로 orjson을 사용하고 싶다고 가정해봅시다.

만약 들여쓰기 및 포맷된 JSON을 반환하고 싶다면, orjson.OPT_INDENT_2 옵션을 사용할 수 있습니다.

CustomORJSONResponse를 생성할 수 있습니다. 여기서 핵심은 Response.render(content) 메서드를 생성하여 내용을 bytes로 반환하는 것입니다:

from typing import Any

import orjson
from fastapi import FastAPI, Response

app = FastAPI()


class CustomORJSONResponse(Response):
    media_type = "application/json"

    def render(self, content: Any) -> bytes:
        assert orjson is not None, "orjson must be installed"
        return orjson.dumps(content, option=orjson.OPT_INDENT_2)


@app.get("/", response_class=CustomORJSONResponse)
async def main():
    return {"message": "Hello World"}

이제 다음 대신:

{"message": "Hello World"}

이 응답은 이렇게 반환됩니다:

{
  "message": "Hello World"
}

물론 JSON 포맷팅보다 더 유용하게 활용할 방법을 찾을 수 있을 것입니다. 😉

기본 응답 클래스

FastAPI 클래스 객체 또는 APIRouter를 생성할 때 기본적으로 사용할 응답 클래스를 지정할 수 있습니다.

이를 정의하는 매개변수는 default_response_class입니다.

아래 예제에서 FastAPI는 모든 경로 작업에서 기본적으로 JSONResponse 대신 ORJSONResponse를 사용합니다.

from fastapi import FastAPI
from fastapi.responses import ORJSONResponse

app = FastAPI(default_response_class=ORJSONResponse)


@app.get("/items/")
async def read_items():
    return [{"item_id": "Foo"}]

여전히 이전처럼 경로 작업에서 response_class를 재정의할 수 있습니다.

추가 문서화

OpenAPI에서 responses를 사용하여 미디어 타입 및 기타 세부 정보를 선언할 수도 있습니다: OpenAPI에서 추가 응답.