HTTP 요청과 응답을 읽는 기본기
HTTP는 요청과 응답으로 서비스를 연결하고, 상태 코드와 헤더로 문제의 단서를 남기는 통신 규약이다. 이 스크립트는 요청이 서버에 도달하는 과정, 버전별 성능 차이, 캐싱과 Keep-Alive, 실무 디버깅 포인트를 함께 정리한다.
Script Companion
오디오와 함께 스크립트 보기
- 01
HTTP는 BackOps에서 다루는 거의 모든 서비스의 바탕이 된다. API 호출이 실패했을 때, 배포 후 서비스가 열리지 않을 때, 외부 연동이 막힐 때도 결국 요청과 응답을 읽어야 원인을 좁힐 수 있다. 이 기본 구조를 모르면 에러 로그에 상태 코드가 있어도 클라이언트 문제인지, 서버 문제인지, 중간 인프라 문제인지 판단하기 어렵다. 그래서 HTTP 학습의 출발점은 멋진 기능보다 요청과 응답의 모양을 정확히 보는 것이다.
- 02
HTTP의 가장 기본 흐름은 클라이언트가 요청을 보내고 서버가 응답을 돌려주는 구조다. 요청에는 메서드, URL, 헤더, 바디가 들어가고, 응답에는 상태 코드, 헤더, 바디가 들어간다. 브라우저에서 주소를 입력하면 먼저 DNS 조회로 도메인을 IP 주소로 바꾸고, TCP 3-way Handshake로 연결을 만든다. HTTPS라면 TLS Handshake에서 인증서 검증과 암호화 키 교환이 이어지고, 그 뒤에 HTTP 요청이 서버로 전송된다. 서버는 라우팅, 비즈니스 로직, DB 조회 같은 처리를 거쳐 응답을 만든다.
- 03
HTTP 메서드는 요청의 의도를 표현한다. GET은 데이터 조회에 쓰이고 바디가 없으며, POST는 데이터 생성처럼 바디에 데이터를 담아 보내는 상황에 쓰인다. PUT은 전체 수정, PATCH는 일부 수정, DELETE는 삭제를 의미한다. 상태 코드는 결과를 빠르게 알려주는 신호다. 200 OK는 성공, 201 Created는 생성 성공, 204 No Content는 성공했지만 응답 바디가 없다는 뜻이다. 400 Bad Request는 요청 자체가 잘못된 경우이고, 401 Unauthorized는 인증이 안 된 경우이며, 403 Forbidden은 로그인은 되었지만 접근 권한이 없는 경우다.
- 04
상태 코드를 볼 때는 첫 자리만 봐도 큰 방향을 잡을 수 있다. 2xx는 성공, 4xx는 클라이언트 쪽 요청 문제, 5xx는 서버나 중간 서버 쪽 문제에 가깝다. 404 Not Found는 리소스가 없다는 뜻이고, 429 Too Many Requests는 요청 횟수 초과를 의미한다. 500 Internal Server Error는 서버 내부 오류이고, 502 Bad Gateway는 프록시나 로드밸런서 같은 중간 서버가 뒤쪽 서버에서 잘못된 응답을 받았다는 뜻이다. 503 Service Unavailable은 서버 과부하나 점검 중인 상태, 504 Gateway Timeout은 중간 서버가 뒤쪽 서버의 응답을 기다리다 타임아웃된 상태다.
- 05
헤더는 요청과 응답에 붙는 메타데이터다. Content-Type은 데이터 형식, Authorization은 인증 정보, Cache-Control은 캐싱 규칙을 전달할 때 자주 쓰인다. HTTP는 stateless, 즉 무상태라는 성질을 가진다. 서버는 이전 요청을 자동으로 기억하지 않기 때문에 인증이 필요한 모든 요청마다 Authorization 헤더를 포함해야 한다. Session과 Cookie가 필요한 이유도 이 무상태 성질 때문이다. HTTPS는 HTTP에 TLS 암호화를 더한 형태이며, 데이터가 중간에 탈취되더라도 읽기 어렵게 만든다.
- 06
성능 관점에서는 HTTP 버전의 차이도 중요하다. HTTP/1.1은 하나의 TCP 연결에서 요청을 직렬로 처리하는 문제가 있고, 브라우저는 이를 우회하려고 보통 6개에서 8개의 TCP 연결을 병렬로 열지만 서버 리소스를 낭비할 수 있다. HTTP/2는 멀티플렉싱으로 하나의 TCP 연결에서 여러 요청과 응답을 동시에 주고받고, 스트림 ID로 각 요청을 구분한다. 또 HPACK으로 반복되는 헤더를 압축하고, 바이너리 프레이밍으로 데이터를 이진 형식으로 전송한다. NestJS는 기본적으로 HTTP/1.1로 동작하며, AWS ALB가 클라이언트와 HTTP/2로 통신하고 내부적으로 HTTP/1.1로 NestJS에 전달하는 방식이 일반적이다.
- 07
HTTP/3는 HTTP/2에 남아 있던 TCP 수준의 Head-of-Line Blocking 문제를 줄인다. HTTP/2에서는 TCP 계층에서 하나의 패킷이 손실되면 모든 스트림이 기다려야 하지만, HTTP/3의 QUIC는 각 스트림이 독립적이라 하나의 패킷 손실이 다른 스트림에 영향을 주지 않는다. 원문 기준으로 2025년 10월 Cloudflare 데이터에서 전 세계 트래픽의 35퍼센트가 HTTP/3로 전송된다. AWS CloudFront와 Cloudflare는 이미 HTTP/3를 기본 지원한다. BackOps 관점에서는 NestJS 앱 자체가 여전히 HTTP/1.1 또는 HTTP/2로 동작하더라도, 엣지인 ALB나 CloudFront가 클라이언트와 HTTP/3로 통신해 혜택을 줄 수 있다는 점이 중요하다.
- 08
Keep-Alive와 캐싱은 같은 데이터를 더 효율적으로 주고받기 위한 장치다. Keep-Alive는 한 번 맺은 TCP 연결을 여러 요청에 재사용해서 3-way handshake와 TLS Handshake 비용을 줄인다. 다만 Node.js 서버의 기본 Keep-Alive 타임아웃은 5초이고 AWS ALB의 기본 idle timeout은 60초라서, ALB가 연결을 재사용하려는 순간 NestJS 서버가 먼저 끊으면 502가 발생할 수 있다. 캐싱에서는 Cache-Control max-age=300처럼 일정 시간 캐시된 응답을 쓰도록 할 수 있고, ETag와 If-None-Match를 통해 변경이 없으면 304 Not Modified를 반환할 수 있다. 304는 바디 없이 기존 리소스를 재사용하게 해 네트워크 전송을 줄인다.
- 09
실무 디버깅에서는 증상과 상태 코드를 함께 읽어야 한다. CORS 에러는 브라우저가 다른 Origin의 요청을 막는 경우이며, 응답 헤더의 Access-Control-Allow-Origin이 없거나 잘못된 값일 수 있다. 프로덕션에서 origin 별표는 보안 위험이므로 허용할 도메인을 명시해야 한다. 배포 직후 502가 보이면 앱 컨테이너가 아직 기동 중인지, 헬스체크 포트가 맞는지, 앱이 크래시 후 재시작 중인지 확인한다. 401은 토큰이 없거나 만료된 인증 문제이고, 403은 토큰은 유효하지만 역할이나 RBAC 정책, AWS IAM 정책상 접근 권한이 없는 문제다. 정리하면 HTTP 디버깅의 출발점은 항상 메서드, 상태 코드, 헤더, 바디를 읽는 것이다.
같은 레이어
L1에서 이어 듣기
- 오디오 파일
- /podcasts/l1-http-basics.mp3