이번 글의 목표는 “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 = 메타/인증/세션 쪽
'Study Notes' 카테고리의 다른 글
| 채팅창 입력이 AI 응답으로 돌아오기까지: 프론트엔드, 백엔드, API 호출 흐름 정리 (0) | 2026.03.15 |
|---|---|
| Flask로 Gemini API 붙이기: /sendMessage(GET)로 한 줄 Q&A 만들기 (0) | 2026.03.01 |
| Git/GitHub · Vercel · DNS : ‘배포 URL’에서 ‘내 도메인’으로 (0) | 2026.02.01 |
| 포트폴리오 배포 준비 ② ㅡ Vercel 배포 & 도메인 구조 이해하기 (0) | 2026.02.01 |
| 포트폴리오 배포 준비 ① ㅡ GitHub 업로드까지 (Windows / Cursor) (0) | 2026.01.30 |