본문으로 건너뛰기
김신건의 로그

[Flask] Request & Response

· 수정 · 📖 약 2분 · 592자/단어 #python #flask #web-framework #request #response
Flask Request, Flask Response, Flask jsonify, Flask make_response, Flask streaming response, Flask 요청 응답

정의

Flask Request/Response 는 Werkzeug 의 Request / Response 래퍼를 확장한 것입니다. Thread-local 프록시 flask.request 로 뷰 함수 어디서든 현재 요청에 접근하고, return 값을 자동으로 Response 로 변환합니다.

Request 객체

기본 속성

from flask import request

@app.post("/inspect")
def inspect():
    # URL & method
    method = request.method              # "POST"
    url = request.url                    # "http://.../inspect?x=1"
    path = request.path                  # "/inspect"
    full_path = request.full_path        # "/inspect?x=1"

    # Query string
    q = request.args.get("q", default="")
    tags = request.args.getlist("tag")   # 여러 값

    # JSON body
    data = request.get_json(silent=True)     # 실패 시 None
    data_or_error = request.get_json()       # 실패 시 400

    # Form (application/x-www-form-urlencoded, multipart/form-data)
    name = request.form.get("name")

    # 파일 업로드
    upload = request.files.get("file")
    if upload:
        upload.save(f"/tmp/{upload.filename}")

    # 원본 body
    raw = request.data                    # bytes
    text = request.get_data(as_text=True) # str

    # Headers
    ua = request.headers.get("User-Agent")

    # Cookies
    sid = request.cookies.get("session")

    # 원격
    remote_addr = request.remote_addr

    # 프록시 뒤에서는
    forwarded_for = request.headers.get("X-Forwarded-For")

    return {"ok": True}

JSON body

data = request.get_json()             # 실패 시 400
data = request.get_json(silent=True)  # 실패 시 None
data = request.get_json(force=True)   # Content-Type 무시

silent=True 를 기본으로 하고 자체 검증하는 편이 안전.

Form 과 파일

@app.post("/upload")
def upload():
    file = request.files.get("upload")
    if not file:
        return {"error": "no file"}, 400
    if not file.filename:
        return {"error": "empty filename"}, 400

    from werkzeug.utils import secure_filename
    filename = secure_filename(file.filename)   # 보안 처리
    file.save(f"/uploads/{filename}")
    return {"filename": filename}

secure_filename: 슬래시, 유니코드, 특수문자 제거. 절대 raw filename 을 파일 경로에 쓰지 마세요.

Multipart 여러 파일

@app.post("/uploads")
def uploads():
    files = request.files.getlist("files")
    for f in files:
        f.save(f"/uploads/{secure_filename(f.filename)}")
    return {"count": len(files)}

큰 요청 제한

app.config["MAX_CONTENT_LENGTH"] = 10 * 1024 * 1024   # 10 MB

초과 시 413 자동 반환. 파일 업로드 서버에서 반드시 설정.

Response 반환 형식

Flask 는 뷰 함수의 return 값을 유연하게 해석합니다.

문자열

@app.get("/")
def index():
    return "Hello"                   # 200, text/html
    # 또는
    return "Hello", 201              # status code
    # 또는
    return "Hello", 201, {"X-A": "1"}  # + headers

Dict / list (JSON 자동)

@app.get("/api")
def api():
    return {"key": "value"}          # 200 application/json
    # 또는
    return [1, 2, 3]                 # 200 application/json
    # 또는
    return {"key": "value"}, 201, {"X-Custom": "yes"}

Flask 2.0+ 는 dict/list 자동 jsonify. 이전에는 jsonify() 명시 필요.

jsonify()

from flask import jsonify

@app.get("/api")
def api():
    return jsonify({"key": "value"})           # 200
    return jsonify(key="value", n=42)          # kwargs 형태
    return jsonify(["a", "b"])                 # array

jsonify()application/json Content-Type 을 명시적으로 설정. datetime, Decimal, UUID 등도 자동 직렬화 (Flask 3.x 개선).

make_response

세밀한 응답 제어:

from flask import make_response

@app.get("/custom")
def custom():
    resp = make_response({"data": 123})
    resp.status_code = 201
    resp.headers["X-Custom"] = "value"
    resp.set_cookie("session", "abc", httponly=True, secure=True, samesite="Lax")
    return resp

Response 객체 직접

from flask import Response

@app.get("/xml")
def xml():
    return Response(
        "<xml><a>1</a></xml>",
        mimetype="application/xml",
        status=200,
    )

리디렉트

from flask import redirect, url_for

@app.get("/old")
def old():
    return redirect(url_for("new"), code=308)   # 기본 302

@app.get("/new")
def new():
    return "new page"
  • 301: Moved Permanently (SEO 영향)
  • 302: Found (기본, 임시)
  • 307: Temporary Redirect (method 유지)
  • 308: Permanent Redirect (method 유지, SEO 영향)

abort

from flask import abort

@app.get("/secret/<int:id>")
def secret(id):
    if not authorized():
        abort(403, description="Forbidden")
    if not exists(id):
        abort(404)
    return "..."

abortHTTPException 서브클래스 raise. errorhandler 로 커스텀 처리:

@app.errorhandler(404)
def not_found(e):
    return {"error": "not found"}, 404

@app.errorhandler(Exception)
def any_error(e):
    return {"error": str(e)}, 500

Streaming Response

from flask import stream_with_context, Response

@app.get("/stream")
def stream():
    def generate():
        for i in range(100):
            yield f"chunk {i}\n"
    return Response(stream_with_context(generate()), mimetype="text/plain")

stream_with_context 는 generator 안에서 request 접근을 유지. 안 쓰면 request context 이탈.

Server-Sent Events (SSE)

@app.get("/events")
def events():
    def stream_events():
        while True:
            data = get_next_event()
            yield f"data: {json.dumps(data)}\n\n"
            time.sleep(1)
    return Response(stream_with_context(stream_events()), mimetype="text/event-stream")

File Download

from flask import send_file, send_from_directory

@app.get("/download/<name>")
def download(name):
    return send_file(
        f"/data/{name}.pdf",
        as_attachment=True,
        download_name=f"{name}.pdf",
        mimetype="application/pdf",
    )

@app.get("/uploads/<path:filename>")
def uploaded(filename):
    return send_from_directory("/uploads/", filename)

send_from_directory 는 path traversal 방지 (.. 차단).

Before/After Request

@app.before_request
def before_each_request():
    g.request_id = str(uuid.uuid4())
    g.start = time.time()

@app.after_request
def after_each_request(response):
    duration = time.time() - g.start
    response.headers["X-Duration"] = f"{duration:.3f}"
    response.headers["X-Request-ID"] = g.request_id
    return response

@app.teardown_request
def teardown(exc):
    if exc:
        logger.exception(exc)
  • before_request: 요청 처리 전. 인증, DB 세션 시작.
  • after_request: 응답 반환 전. 헤더 추가, 로깅.
  • teardown_request: request context 종료 시 (예외 여부 무관). 리소스 정리.

Blueprint 는 자기만의 before_request / after_request 가능:

@bp.before_request
def before_bp():
    ...

Response class 커스터마이제이션

class ApiResponse(Response):
    default_mimetype = "application/json"

app.response_class = ApiResponse

함정

WARNING

request.form vs request.get_json(). Content-Type 에 따라 다름. 클라이언트가 잘못 보내면 실패. 양쪽 지원 시 명시 코드.

CAUTION

stream_with_context 안 쓰면 generator 안에서 request 접근 시 오류. 스트리밍 시 필수.

WARNING

파일 업로드 filename 은 신뢰 불가. 반드시 secure_filename + 확장자 화이트리스트.

IMPORTANT

get_json(silent=True) 를 기본으로. 실패 시 400 자동 대신 앱에서 처리하는 편이 유연.

CAUTION

send_file 절대 경로 사용. 상대 경로는 flask app root 기준으로 오해. 명확히 절대 경로.

관련 위키

이 글의 용어 (7개)
[FastAPI] Routing (Path Operations)fastapi
정의 FastAPI Routing 은 HTTP method + URL 을 Python 함수 (path operation) 에 매핑하는 시스템입니다. Starlette 의 라우팅 …
[Flask] Blueprintsflask
정의 Blueprint 는 Flask 앱을 여러 모듈로 나누는 표준 도구입니다. 라우트, 정적파일, 템플릿, before/after hook, error handler 를 그룹화…
[Flask] Routing (URL Rules, Converters, url_for)flask
정의 Flask Routing 은 Werkzeug 의 시스템에 기반한 URL 매핑입니다. 데코레이터 ( , , ...) 로 URL 패턴을 뷰 함수에 연결하고, converter …
[Flask] Sessions & Cookiesflask
정의 Flask Session 은 요청 간 상태를 유지하는 방법입니다. 기본 구현은 서명된 쿠키 (secure cookie) 로 클라이언트에 저장 (server-less). ex…
[Flask] Templates (Jinja2)flask
정의 Flask Templates 는 Jinja2 템플릿 엔진의 Flask 통합입니다. 로 컨텍스트 (변수) 를 넘겨 HTML/텍스트를 렌더링하고, template inherit…
[Flask] Testingflask
정의 Flask Testing 은 앱 인스턴스를 직접 감싸 HTTP 요청을 시뮬레이션하는 workflow 입니다. (Werkzeug) 로 라우팅/뷰/미들웨어까지 실제 실행하고 응…
[Python] Flaskflask
정의 Flask 는 Armin Ronacher 가 2010년 발표한 WSGI 기반 Python 마이크로프레임워크 입니다. Pallets Projects 가 유지관리하고, 2026…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기