Study Notes

Flask로 HTTP 요청 이해하기: GET/POST, Query String, Content-Type

callmeVANTA 2026. 2. 25. 19:00

이번 글의 목표는 “HTTP 정의 암기”가 아니라 채팅 UI의 입력이 어디로 흘러가서 어떤 형태로 돌아오는지를 흐름으로 정리하는 것이다. 지금 포트폴리오는 프론트 구현 + GitHub 업로드까지는 됐고, 이제 채팅창에서 보낸 질문을 받아 처리하고 다시 돌려줄 서버(API) 가 필요해졌다. 그래서 오늘은 “클라이언트 → HTTP → Flask 서버 → (LLM) → 응답(JSON)” 흐름에서, 특히 Flask/HTTP 구간이 무엇을 하는지를 정리하고 실습 확인 포인트를 남긴다.

[브라우저]
     ↓ HTTP 요청 (GET/POST)
     ↓ (URL / Header / Body)
[Flask 서버]
     ↓ request 객체로 파싱
     ↓ Python 함수 실행
     ↓ 응답 반환
[브라우저]

1. Flask란?

1-1. 정의

Flask: 파이썬으로 웹 서버/웹 API를 빠르게 만들 수 있게 해주는 마이크로(Micro) 웹 프레임워크

  • '마이크로'의 의미: 기능이 부족하다 X, 핵심(라우팅/요청·응답 처리)만 가볍게 제공 O
    로그인/DB/관리자페이지 같은 부가 기능은 필요한 것만 확장으로 붙이는 방식

1-2. Flask가 해주는 핵심

  • 라우팅(Routing): GET /sendMessage 같은 요청을 “어떤 함수가 처리할지” 연결한다.
  • 요청 파싱(Parsing): 쿼리/헤더/바디에서 값을 꺼내기 쉽게 정리한다.
  • 응답 생성(Response): JSON/상태코드 같은 응답을 규격에 맞게 만들어 반환한다.

1-3. 왜 Flask인가

  • HTTP를 맨바닥에서 직접 처리하려면(소켓 열기, 요청 문자열 파싱, 응답 포맷 직접 작성) 작업량이 매우 많다.
  • Flask는 HTTP의 귀찮은 부분을 표준화해서,
  • 사용자가 앱 로직(예: LLM 호출/결과 가공)에 집중할 수 있게 한다.

1-4. Flask에서 /hello 요청 보내기

from flask import Flask

app = Flask(__name__)

@app.get("/hello")
def hello():
    return "hello"

if __name__ == "__main__":
    app.run(debug=True)


2. HTTP 구조

2-1. HTTP는 본질적으로:

  • 클라이언트가 서버에 보내는 요청(Request) 메시지
  • 서버가 클라이언트에 돌려주는 응답(Response) 메시지

이 두가지 "메시지 형식(문법/규칙)"을 정의한다.

  • 기본적으로 무상태stateless: 각 요청이 독립적
  • 상태 유지는 보통 쿠키/세션/토큰 같은 추가 메커니즘으로 해결

2-2. 요청(Request)의 구조

1. Start Line요청 라인: 메서드 + 경로(+쿼리)

METHOD SP request-target SP HTTP-version CRLF // 형식
GET /sendMessage?message=hello HTTP/1.1 // 예시

2. Headers헤더: 부가 정보(예: Content-Type)

  • 라우팅/가상호스트: 어떤 도메인으로 온 요청인지 (HTTP/1.1에서 사실상 필수)
  • 바디 형식/길이(프레이밍): Content-Type(바디의 포맷), Content-Length(바디 바이트 길이), Transfer-Encoding: chunked(바디 길이를 처음에 못 정할 때)
  • 협상(클라이언트가 원하는 응답 형태): Accept(받고 싶은 응답 MIME 타입), Accept-Encoding(압축 방식), Accept-Language(선호 언어)
  • 인증/세션: Authorization: Bearer...(토큰 기반), Cookie:../응답에서는 Set-Cookie
  • 캐시/조건부 요청: Cache-Control, ETag, If-None-Match, Last-Modified, If-Modified-Since

3. CRLF빈 줄: 헤더가 끝나면 반드시 빈 줄이 오고, 그 다음이 바디

4. Body바디(선택): 실제 데이터(주로 POST에서 사용)

  • GET: 보통 바디가 없음(관례적으로)
  • POST/PUT/PATCH: 바디가 자주 있는 편 

2-3. 응답(Response)의 구조

1. Status Line상태 코드

  • 100번대: 정보 전송
  • 200번대: 성공
  • 300번대: 리다이렉트
  • 400번대: 클라이언트 측 에러
    • 401: 인증 필요
    • 403: 권한 없음
    • 404: 리소스 없음
  • 500번대: 서버 측 에러

2. Headers헤더: 부가 정보(예: Content-Type)

Content-Type: application/json; charset=utf-8
Content-Length: 123
Set-Cookie: session=...
Cache-Control: no-store

3. CRLF빈 줄

4. Body바디(선택): JSON/텍스트 등 실제 결과

{"reply":"..."}

 

결론:
서버를 만든다는 건 “요청 1개를 제대로 읽고, 응답 1개를 제대로 돌려주는 것”부터 시작한다.


3. GET vs POST

3-1. GET: 조회(Read)

  • 리소스를 조회하는 메서드
  • 요청을 식별하는 조건(검색어/필터)은 보통 쿼리스트링(Query String) 으로 전달
    • 예: /sendMessage?msg=hello
  • 의미적으로 서버 상태를 바꾸지 않는 조회(safe) 에 적합
    • 관례적으로 캐시/북마크/공유에 유리

3-2. POST: 생성/처리/행동 수행(Write/Action)

  • 서버에 처리를 요청하거나 리소스를 생성하는 메서드
  • 전송 데이터는 보통 Request Body에 담고, 형식은 Content-Type으로 명시
    • 예(대표): JSON 바디
{ "msg": "hello" }
  • 구조화된 데이터(객체/배열) 또는 큰 데이터 전달에 적합
    • 또는 form-urlencoded / multipart 같은 form 계열도 POST에서 흔함

4. 서버에게 데이터 보내는 방식

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.get("/query")
def read_query():
    name = request.args.get("name")
    age = request.args.get("age", type=int)
    return jsonify({"name": name, "age": age, "type": "query"})

@app.post("/form")
def read_form():
    # Content-Type: application/x-www-form-urlencoded
    # 또는 multipart/form-data(파일 없이도 가능)
    username = request.form.get("username")
    password = request.form.get("password")
    return jsonify({"username": username, "password": password})

@app.post("/upload")
def upload_file():
    # Content-Type: multipart/form-data
    f = request.files.get("file")       
    desc = request.form.get("desc")     
    if not f:
        return jsonify({"error": "no file"}), 400
    return jsonify({"filename": f.filename, "desc": desc})

@app.post("/json")
def read_json():
    # Content-Type: application/json
    data = request.get_json(silent=True) or {}
    message = data.get("message")
    return jsonify({"message": message})



if __name__ == "__main__":
    app.run(debug=True)

1. Query String

  • 위치: URL의 ? 뒤 (Body 아님)
  • 형태: key=value&key2=value2 (퍼센트 인코딩)
  • 예시: /sendMessage?msg=hello&lang=ko
  • 언제: 조회 조건/필터/페이지네이션 같은 “작고 단순한 값”
  • 주의: URL 길이 제한(환경마다 다름), 로그/히스토리에 남기 쉬움, 민감정보에 부적절
  • Flask: name=request.args.get("name")
curl.exe "http://127.0.0.1:5000/query?name=vanta&age=20"

2. form-urlencoded (application/x-www-form-urlencoded)

  • 위치: Body
  • 헤더: Content-Type: application/x-www-form-urlencoded
  • 형태: QueryString과 거의 동일한 a=1&b=2 포맷을 Body에 넣는 방식
  • 예시: msg=hello&lang=ko
  • 언제: 전통적인 HTML <form> 기본 제출 방식(파일 업로드 없을 때)
  • Flask: password = request.form.get("password")
curl.exe -X POST "http://127.0.0.1:5000/form" 
  -H "Content-Type: application/x-www-form-urlencoded" 
  -d "username=kim&password=1234"

3. form-data (multipart/form-data)

  • 정식 명칭: multipart/form-data
  • 위치: Body
  • 헤더: Content-Type: multipart/form-data; boundary=...(직접 신경 쓸 필요 없음)
  • 형태: Body를 여러 파트로 쪼개서 보냄(텍스트 + 파일 혼합 가능)
  • 언제: 파일 업로드가 필요할 때(이미지/문서 등)
  • Flask: desc = request.form.get("desc") # 텍스트 파트 file = request.files.get("file") # 파일 파트
Set-Content -Path demo.txt -Value "hello upload"
curl.exe -X POST "http://127.0.0.1:5000/upload"
	-F "desc=demo upload"
	-F "file=@demo.txt"

4. JSON (application/json)

  • 위치: Body
  • 헤더: Content-Type: application/json
  • 형태: { "msg": "hello", "meta": {...} }
  • 언제: API에서 제일 흔함(중첩 구조/배열 등 구조화 데이터에 강함)
  • Flask: data = request.get_json(silent=True) or {} message = data.get("message")
curl.exe -X POST "http://127.0.0.1:5000/json"
	-H "Content-Type: application/json" 
	-d '{"message":"hello_from_json"}'
Invoke-RestMethod -Method Post -Uri "http://127.0.0.1:5000/json" 
	-ContentType "application/json" 
	-Body '{"message":"hello_from_json"}'

5. Path Parameter (경로 변수)

  • 위치: URL Path 자체
  • 예시: /users/123, /posts/2026-02-26
  • Flask: @app.get("/users/<user_id>") def get_user(user_id): ...

6. Headers (메타/정책/인증 전달)

  • 위치: Header
  • 예시
    • Authorization: Bearer <token>
    • Accept: application/json
  • Flask: token = request.headers.get("Authorization")

7. Cookies (세션/상태 유지용)

  • 위치: Header (Cookie:)
  • Flask: session_id = request.cookies.get("session")

 

정리:

  • QueryString/Path = URL 쪽
  • form-urlencoded / multipart(form-data) / JSON = Body 쪽 (Content-Type이 해석 규칙)
  • Headers/Cookies = 메타/인증/세션 쪽