CS

HTTP 완벽 가이드 - 백엔드 개발자가 알아야 할 모든 것

백엔드 개발자 김승원 2026. 3. 31. 15:07

들어가며

HTTP(HyperText Transfer Protocol)는 웹의 근간을 이루는 프로토콜입니다. 백엔드 개발자에게 HTTP에 대한 깊은 이해는 성능 최적화, 디버깅, API 설계 등 모든 영역에서 필수적입니다. 이 글에서는 HTTP/1.1부터 HTTP/3까지의 발전 과정과, TCP 핸드셰이크, TLS, Keep-Alive, 상태 코드 등 실무에서 반드시 알아야 할 내용을 정리합니다.

1. HTTP 프로토콜의 발전

HTTP/1.1 (1997)

현재까지도 널리 사용되는 버전입니다. 주요 특징은 다음과 같습니다.

  • Keep-Alive: 기본적으로 연결을 유지하여 매 요청마다 TCP 연결을 새로 맺지 않습니다.
  • 파이프라이닝: 이론적으로 여러 요청을 연속 전송할 수 있지만, Head-of-Line Blocking 문제로 실질적으로 거의 사용되지 않습니다.
  • 청크 전송 인코딩: Transfer-Encoding: chunked를 통해 응답 크기를 미리 모르더라도 스트리밍 전송이 가능합니다.

HTTP/2 (2015)

Google의 SPDY 프로토콜을 기반으로 표준화되었습니다.

  • 멀티플렉싱: 하나의 TCP 연결에서 여러 요청/응답을 동시에 처리합니다. HTTP/1.1의 HOL Blocking 문제를 해결합니다.
  • 헤더 압축 (HPACK): 중복되는 헤더를 정적/동적 테이블로 관리하여 전송량을 줄입니다.
  • 서버 푸시: 클라이언트가 요청하기 전에 서버가 필요한 리소스를 미리 전송할 수 있습니다.
  • 바이너리 프레이밍: 텍스트가 아닌 바이너리 형태로 데이터를 전송하여 파싱 효율을 높입니다.

HTTP/3 (2022)

TCP 대신 QUIC(UDP 기반) 프로토콜을 사용합니다.

  • TCP HOL Blocking 해결: HTTP/2는 멀티플렉싱을 지원하지만, TCP 계층에서 패킷 손실이 발생하면 모든 스트림이 차단됩니다. QUIC은 스트림 단위로 독립적이므로 이 문제가 없습니다.
  • 0-RTT 연결: 이전에 접속한 서버라면 핸드셰이크 없이 즉시 데이터를 전송할 수 있습니다.
  • 연결 마이그레이션: Wi-Fi에서 셀룰러로 전환되어도 연결이 끊어지지 않습니다. Connection ID 기반으로 연결을 식별하기 때문입니다.

2. TCP 3-Way Handshake

HTTP/1.1과 HTTP/2는 TCP 위에서 동작합니다. 클라이언트와 서버 간의 연결 수립 과정을 이해하는 것이 중요합니다.

클라이언트                    서버
    |                           |
    |--- SYN (seq=x) --------->|   ① 클라이언트가 연결 요청
    |                           |
    |<-- SYN-ACK (seq=y, ------|
    |    ack=x+1)               |   ② 서버가 요청 수락 + 자신의 SYN
    |                           |
    |--- ACK (ack=y+1) ------->|   ③ 클라이언트가 확인 응답
    |                           |
    |===== 연결 수립 완료 =======|

이 과정은 최소 1 RTT(Round Trip Time)가 소요됩니다. 서울에서 미국 서부 서버까지의 RTT가 약 150ms라면, 연결 수립에만 150ms가 걸립니다. 여기에 TLS 핸드셰이크까지 더하면 지연이 상당해집니다.

연결 종료: 4-Way Handshake

클라이언트                    서버
    |--- FIN --->|   ① 연결 종료 요청
    |<-- ACK ----|   ② 확인
    |<-- FIN ----|   ③ 서버도 종료 준비 완료
    |--- ACK --->|   ④ 최종 확인

연결 종료 시에는 양쪽 모두 종료 의사를 밝혀야 하므로 4단계가 필요합니다. 이 과정에서 TIME_WAIT 상태가 발생하며, 이는 서버에서 대량의 단기 연결을 처리할 때 포트 고갈 문제를 일으킬 수 있습니다.

3. TLS(Transport Layer Security) 핸드셰이크

HTTPS 통신을 위해서는 TCP 연결 수립 후 TLS 핸드셰이크가 추가로 필요합니다.

TLS 1.2 핸드셰이크 (2-RTT)

클라이언트                              서버
    |--- ClientHello ---------------->|   지원하는 암호화 스위트 목록 전송
    |<-- ServerHello, Certificate, ----|
    |    ServerKeyExchange, Done       |   서버 인증서 + 키 교환 파라미터
    |--- ClientKeyExchange, ---------->|
    |    ChangeCipherSpec, Finished    |   Pre-master secret 전송
    |<-- ChangeCipherSpec, Finished ---|
    |===== 암호화 통신 시작 ===========|

TLS 1.3 핸드셰이크 (1-RTT)

TLS 1.3에서는 핸드셰이크가 1-RTT로 줄었습니다. ClientHello에 키 교환 파라미터를 함께 전송하여 왕복 횟수를 줄였습니다. 또한 안전하지 않은 암호화 알고리즘(RC4, 3DES, SHA-1 등)이 완전히 제거되었습니다.

# Nginx에서 TLS 1.3 설정 예시
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;

4. Keep-Alive와 커넥션 관리

HTTP/1.0에서는 매 요청마다 TCP 연결을 새로 맺었습니다. HTTP/1.1부터는 Connection: keep-alive가 기본값으로, 하나의 TCP 연결을 재사용합니다.

# 요청 헤더
GET /api/users HTTP/1.1
Host: api.example.com
Connection: keep-alive

# 응답 헤더
HTTP/1.1 200 OK
Connection: keep-alive
Keep-Alive: timeout=60, max=100

timeout=60은 유휴 상태에서 60초간 연결을 유지하겠다는 의미이고, max=100은 이 연결로 최대 100개의 요청을 처리하겠다는 의미입니다.

Spring Boot에서의 설정

# application.yml - 내장 Tomcat 설정
server:
  tomcat:
    keep-alive-timeout: 60000     # Keep-Alive 타임아웃 (ms)
    max-keep-alive-requests: 100  # 최대 Keep-Alive 요청 수
    connection-timeout: 20000     # 연결 타임아웃 (ms)
    max-connections: 8192         # 최대 동시 연결 수
    threads:
      max: 200                    # 최대 스레드 수
      min-spare: 10               # 최소 유휴 스레드 수

5. HTTP 상태 코드 완벽 정리

상태 코드를 정확히 사용하는 것은 좋은 API 설계의 기본입니다.

2xx - 성공

코드 의미 사용 시점
200 OK 요청 성공 일반적인 GET, PUT 요청의 성공 응답
201 Created 리소스 생성 POST로 새 리소스가 생성되었을 때. Location 헤더에 새 리소스의 URI를 포함해야 합니다.
204 No Content 본문 없음 DELETE 성공 시 또는 PUT으로 업데이트 후 응답 본문이 불필요할 때

3xx - 리다이렉션

코드 의미 사용 시점
301 Moved Permanently 영구 이동 URI가 영구적으로 변경. 검색 엔진이 새 URI로 인덱스를 갱신합니다.
302 Found 임시 이동 일시적으로 다른 URI를 사용해야 할 때
304 Not Modified 변경 없음 캐시된 리소스가 여전히 유효할 때. 본문 없이 헤더만 응답합니다.

4xx - 클라이언트 오류

코드 의미 사용 시점
400 Bad Request 잘못된 요청 요청 본문의 유효성 검증 실패, 잘못된 파라미터
401 Unauthorized 인증 필요 인증되지 않은 요청. 로그인이 필요합니다.
403 Forbidden 접근 금지 인증은 되었지만 권한이 없는 경우
404 Not Found 찾을 수 없음 요청한 리소스가 존재하지 않을 때
409 Conflict 충돌 리소스 상태와 요청이 충돌할 때 (예: 중복 가입)
429 Too Many Requests 요청 과다 Rate Limit 초과 시. Retry-After 헤더와 함께 응답합니다.

5xx - 서버 오류

코드 의미 사용 시점
500 Internal Server Error 서버 내부 오류 예상치 못한 서버 오류. 클라이언트에게 상세 정보를 노출하면 안 됩니다.
502 Bad Gateway 게이트웨이 오류 리버스 프록시(Nginx)가 업스트림 서버로부터 유효하지 않은 응답을 받았을 때
503 Service Unavailable 서비스 불가 서버 점검 또는 과부하 상태. Retry-After 헤더를 포함해야 합니다.
504 Gateway Timeout 게이트웨이 타임아웃 업스트림 서버가 응답 시간 내에 응답하지 않았을 때

6. HTTP 캐싱

효율적인 캐싱은 서버 부하를 줄이고 응답 속도를 높이는 핵심 전략입니다.

# Spring Boot에서 캐시 헤더 설정
@GetMapping("/api/products/{id}")
public ResponseEntity<Product> getProduct(@PathVariable Long id) {
    Product product = productService.findById(id);
    
    return ResponseEntity.ok()
            .cacheControl(CacheControl.maxAge(Duration.ofHours(1))
                    .mustRevalidate())
            .eTag(String.valueOf(product.getVersion()))
            .body(product);
}

Cache-Control: max-age=3600, must-revalidate는 1시간 동안 캐시를 사용하되, 만료 후에는 반드시 서버에 재검증하라는 의미입니다. ETag는 리소스의 버전을 식별하는 값으로, 조건부 요청(If-None-Match)에 활용됩니다.

마치며

HTTP는 단순해 보이지만, 그 내부에는 수십 년간 축적된 설계 철학과 최적화 기법이 담겨 있습니다. TCP 핸드셰이크부터 TLS, Keep-Alive, 캐싱까지 각 계층의 동작 원리를 이해하면 성능 병목을 진단하고 최적화하는 능력이 크게 향상됩니다. 특히 HTTP/3와 QUIC의 등장으로 네트워크 프로토콜은 계속 진화하고 있으므로, 지속적으로 관심을 가지고 학습하는 것을 추천합니다.