(여담) 인코딩 이슈

Pt J·2026년 3월 24일
post-thumbnail

인코딩 이슈

FastAPI로부터 응답을 받았을 때 웹 브라우저에서 한글이 깨지는 경우
어떻게 해결할 수 있는지 알아보자.

원인

지금까지 우리가 작성한 코드는 텍스트 인코딩 방식을 명시하지 않았다.

보통의 웹사이트는 헤더 부분에 charset=utf-8 라는 메타 데이터를 가지고 있어
웹 브라우저가 웹사이트 정보를 받았을 때
UTF-8 방식으로 인코딩되어 있음을 인지할 수 있지만
그것이 명시되어 있지 않을 경우 시스템 기본 인코딩으로 보여준다.

이 '시스템 기본 인코딩'이 UTF-8이 아닌 다른 방식이었을 경우
인코딩 방식이 맞지 않아 잘못 해석된 텍스트가 출력된다.

해결책

어차피 개발 단계가 아닌 프로젝트 완성 단계에서는
백엔드의 출력을 웹 브라우저에서 직접 보여줄 일이 없으니
프론트엔드에서 메타 데이터를 제대로 명시하면 문제 없긴 하다.

하지만 개발 단계에서 출력된 데이터를 확인할 수 있어야
정상적으로 작동하는지 원활하게 확인할 수 있으니 적절한 코드로 변경한다.

우리의 이전 실습에서 한글 텍스트를 반환했으니
그 코드를 기준으로 코드를 수정해 보겠다.

수정 전

app/main.py (수정 전)

from fastapi import FastAPI
import rust_engine
import time

app = FastAPI()

@app.get("/")
def read_root():
    return {
        "status": "200",
        "info": "비동기 작업 중에도 응답할 수 있습니다."
    }

@app.get("/async-rust/{seconds}")
async def run_rust_task(seconds: int):
    start = time.time()

    message = await rust_engine.async_compute(seconds)

    end = time.time()

    return {
        "result": message,
        "duration": f"{end - start:.2f}s",
        "engine": "Rust (via pyo3-async-runtimes)"
    }
~/workspace/asynchronous$ curl -i http://127.0.0.1:8000/
HTTP/1.1 200 OK
date: Tue, 24 Mar 2026 23:01:06 GMT
server: uvicorn
content-length: 80
content-type: application/json

{"status":"200","info":"비동기 작업 중에도 응답할 수 있습니다."}% 

수정 후

ORJSONResponse를 불러온다.
FastAPI의 기본 응답을 ORJSONResponse로 바꿔주기만 하면 된다.

이를 위해 orjson 라이브러리를 설치해야 한다.

~/workspace/asynchronous$ pip install orjson

기본 응답을 바꾸지 않고 각 함수의 반환값을 일일이

return ORJSONResponse(
    content=content,
    media_type="application/json; charset=utf-8")

와 같이 변경할 수도 있지만 기본 응답 자체를 바꿔주는 편이 효율적이다.

ORJSONResponse 클래스를 상속받아
media_type 값을 덮어씌운 클래스를 생성하고
FastAPI의 기본 응답을 그것으로 변경해 준다.

app/main.py (수정 후)

from fastapi import FastAPI
from fastapi.responses import ORJSONResponse
import rust_engine
import time

class UTF8ORJSONResponse(ORJSONResponse):
    media_type = "application/json; charset=utf-8"

app = FastAPI(default_response_class=UTF8ORJSONResponse)

@app.get("/")
def read_root():
    return {
        "status": "200",
        "info": "비동기 작업 중에도 응답할 수 있습니다."
    }

@app.get("/async-rust/{seconds}")
async def run_rust_task(seconds: int):
    start = time.time()

    message = await rust_engine.async_compute(seconds)

    end = time.time()

    return {
        "result": message,
        "duration": f"{end - start:.2f}s",
        "engine": "Rust (via pyo3-async-runtimes)"
    }
~$ curl -i http://127.0.0.1:8000/
HTTP/1.1 200 OK
date: Tue, 24 Mar 2026 23:04:15 GMT
server: uvicorn
content-length: 80
content-type: application/json; charset=utf-8

{"status":"200","info":"비동기 작업 중에도 응답할 수 있습니다."}%   

응답의 content-type 에 인코딩 정보가 추가되었다.
웹 브라우저로 테스트 해보아도 한글이 정상 출력되는 것을 확인할 수 있을 것이다.

비교

구분표준 JSONResponseORJSONResponse
직렬화 방식Python json.dumps 사용orjson.dumps (Rust/C) 사용
한글 처리\uXXXX 로 변환 (이스케이프)UTF-8 바이트 그대로 유지
헤더 처리유저가 직접 명시해야 함자동으로 UTF-8로 전송
성능보통매우 빠름

실습의 단순화를 위해 기본값을 사용했지만 사실 성능 측면에서도
ORJSONReponse를 사용하는 편이 이득이다.

여담

코드를 수정하기 전에도 웹 브라우저에는 한글이 깨져도
Swagger UI로 실행하면 응답의 한글이 잘 나온다.

이는 Swaager UI가 웹 브라우저 위에서 실행되는 웹앱인데
내부적으로 fetch() API를 사용하여 데이터를 가져온 뒤
UTF-8 기반으로 HTML 문서 안에 보기 좋게 출력해주기 때문이다.

Swagger UI의 기본 인코딩이 UTF-8이니
따로 명시해주지 않았어도 UTF-8로 해석하고 보여준 것이다.

profile
Peter J Online Space - since July 2020 | 아무데서나 채용해줬으면 좋겠다

0개의 댓글