안동민 개발노트 아이콘

안동민 개발노트

9장 : HTTP

HTTP 기본 구조

DNS가 도메인 이름을 IP 주소로 변환해 주었으니, 이제 실제로 서버와 데이터를 주고받을 차례입니다. 웹에서 이 통신을 담당하는 프로토콜이 HTTP(Hypertext Transfer Protocol)입니다.

웹 개발자가 매일 사용하는 프로토콜이지만, GET과 POST의 차이가 뭔가요?를 넘어서 HTTP의 구조를 정확히 이해하면, API 설계, 성능 최적화, 보안 설정에서 훨씬 나은 판단을 내릴 수 있습니다.


요청과 응답 포맷

HTTP는 요청-응답(Request-Response) 패턴으로 동작합니다. 클라이언트가 요청을 보내면 서버가 응답을 돌려보냅니다.

HTTP/1.1 메시지는 텍스트 기반으로 표현됩니다. 다만 HTTPS라면 TLS로 암호화되므로 패킷 캡처에서 본문을 그대로 읽을 수 없습니다. HTTP/2와 HTTP/3는 바이너리 프레이밍을 사용하지만, 메서드·URI·상태 코드·헤더·본문이라는 논리적 의미는 HTTP Semantics로 유지됩니다.


HTTP 메서드

HTTP 메서드는 클라이언트가 서버에 어떤 동작을 원하는지 표현합니다.

메서드용도본문멱등안전사용 예
GET조회보통 없음페이지 로드, API 데이터 조회
POST처리/생성있음회원가입, 게시글 작성, 파일 업로드
PUT전체 대체있음프로필 전체 수정
PATCH부분 수정있음설계에 따라이메일만 변경
DELETE삭제보통 없음계정 삭제, 게시글 삭제
OPTIONS메서드 확인보통 없음CORS 프리플라이트
HEAD헤더만 조회응답 본문 없음리소스 존재 확인, 크기 확인
GET vs POST 상세 비교
GET /search?q=network&page=1 HTTP/1.1
  * 데이터를 URL 쿼리스트링에 포함
  * 브라우저 히스토리에 남음
  * 북마크 가능
  * 실무상 브라우저/서버/프록시별 URL 길이 제한에 영향
  * 캐시 가능
  * 브라우저 뒤로 가기 시 재요청 없음

POST /api/users HTTP/1.1
Content-Type: application/json
{"name": "홍길동"}
  * 데이터를 본문(Body)에 포함
  * 히스토리에 안 남음
  * 북마크 불가
  * 크기 제한 없음 (서버 설정에 따라)
  * 기본적으로 캐시 활용이 어렵지만, 명시적 캐시 조건을 둘 수 있음
  * 뒤로 가기 시 "다시 제출?" 확인

GET 요청에도 메시지 본문 자체를 붙이는 것이 문법적으로 완전히 불가능한 것은 아니지만, 표준 의미가 일반적으로 정의되어 있지 않아 실무에서는 쓰지 않는 편이 안전합니다.


멱등성과 안전성

HTTP 메서드를 분류하는 두 가지 중요한 속성이 있습니다.

안전성(Safety): 클라이언트가 요청한 의미가 서버 상태 변경을 요구하지 않는 메서드입니다. GET, HEAD, OPTIONS가 안전합니다. 서버가 로그를 남기거나 통계를 갱신하는 부수 효과까지 모두 금지한다는 뜻은 아닙니다.

멱등성(Idempotency): 같은 요청을 한 번 보내든 여러 번 보내든 서버에 남는 의도한 효과가 동일한 메서드입니다. 응답 코드나 응답 본문이 매번 완전히 같아야 한다는 뜻은 아닙니다.

http_methods.py
import json
from http.client import HTTPSConnection

def http_request(method, host, path, body=None):
    """다양한 HTTP 메서드로 요청 전송"""
    conn = HTTPSConnection(host)

    headers = {
        "Content-Type": "application/json",
        "Accept": "application/json",
    }

    json_body = json.dumps(body) if body else None
    conn.request(method, path, body=json_body, headers=headers)

    response = conn.getresponse()
    data = response.read().decode()

    print(f"{method} {path}")
    print(f"  Status: {response.status} {response.reason}")
    print(f"  Content-Type: {response.getheader('Content-Type')}")
    if data and len(data) < 200:
        print(f"  Body: {data}")
    print()

    conn.close()
    return response.status, data

# 사용 예시 (httpbin.org는 HTTP 테스트 서비스)
# http_request("GET", "httpbin.org", "/get")
# http_request("POST", "httpbin.org", "/post", {"name": "test"})
# http_request("PUT", "httpbin.org", "/put", {"name": "updated"})
# http_request("DELETE", "httpbin.org", "/delete")

HTTP 버전별 차이

버전연도연결 방식특징
HTTP/1.01996요청마다 새 연결비효율적
HTTP/1.11997Keep-Alive (연결 재사용)파이프라이닝 지원, 실무 채택 제한
HTTP/22015멀티플렉싱 (1연결, 병렬 전송)바이너리, 헤더 압축, 서버 푸시(효용 제한)
HTTP/32022QUIC (UDP 기반)스트림 독립성, TLS 1.3, 0-RTT

HTTP/3는 TCP 연결 단위의 HOL Blocking을 QUIC 스트림 구조로 크게 완화합니다. 다만 같은 스트림 내부에서는 순서 있는 전달이 필요하므로, 손실의 영향이 완전히 사라지는 것은 아닙니다.

다음 절에서는 서버가 응답할 때 사용하는 상태 코드와 요청/응답에 포함되는 헤더를 살펴보겠습니다.