HTTP 요청·응답 읽는 법: 메서드·상태 코드·연결 재사용

반응형

HTTP는 클라이언트가 리소스에 대한 요청을 보내고 서버가 응답을 돌려주는 애플리케이션 계층 규약이다. HTTP/1.1, HTTP/2, HTTP/3는 메시지를 운반하는 방식이 다르지만, 메서드와 상태 코드 같은 핵심 의미는 공통으로 RFC 9110에 정의되어 있다.

장애를 볼 때는 “HTTP가 안 된다”로 묶지 말고 요청 대상, 메서드, 헤더, 본문, 상태 코드, 응답 헤더를 순서대로 나누는 편이 빠르다. 연결 재사용과 다중화는 그다음 계층의 문제다.

요청과 응답은 무엇으로 구성될까

HTTP/1.1 메시지는 사람이 읽을 수 있는 형태라 구조를 익히기 좋다. 다음은 상품 한 건을 조회하는 요청을 단순화한 예다.

GET /products/42 HTTP/1.1
Host: api.example.com
Accept: application/json
If-None-Match: "product-42-v7"
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 38
ETag: "product-42-v8"
Cache-Control: private, max-age=60

{"id":42,"name":"keyboard","stock":3}

요청에는 대체로 다음 정보가 들어간다.

  • 메서드: 리소스에 어떤 의미의 작업을 요청하는지 나타낸다.
  • 요청 대상: 경로와 쿼리 문자열로 어떤 리소스를 가리키는지 표현한다.
  • 헤더: 표현 형식, 인증 정보, 조건부 요청 같은 부가 조건을 전달한다.
  • 본문: 생성·수정할 데이터처럼 메시지에 실어 보낼 내용이다.

응답은 상태 코드, 응답 헤더, 선택적인 본문으로 구성된다. HTTP/2와 HTTP/3에서는 전송 형식이 바이너리 프레임으로 바뀌지만 애플리케이션이 해석하는 메서드·헤더·상태 코드의 의미는 이어진다.

메서드는 이름보다 의미를 먼저 본다

자주 쓰는 메서드를 단순히 CRUD에 일대일 대응시키면 놓치는 부분이 생긴다.

메서드 표준상 핵심 의미 안전성 멱등성
GET 현재 표현을 조회한다.
HEAD GET과 같은 헤더를 본문 없이 요청한다.
POST 대상 리소스가 정의한 방식으로 내용을 처리한다. 아니요 보장 안 됨
PUT 대상 리소스의 상태를 요청 본문으로 생성하거나 교체한다. 아니요
PATCH 리소스에 부분 변경을 적용한다. 아니요 보장 안 됨
DELETE 대상 리소스와의 연결을 제거한다. 아니요

안전한 메서드는 읽기 전용이라는 의도를 가진다. 멱등한 메서드는 같은 요청을 여러 번 보내도 서버가 의도한 최종 효과가 한 번 보낸 것과 같다는 뜻이다. 응답 코드나 로그 행 수까지 항상 같다는 의미는 아니다.

재시도 정책을 만들 때 이 구분이 중요하다. 네트워크가 끊긴 뒤 POST /payments를 무조건 다시 보내면 중복 결제가 생길 수 있다. 결제처럼 중복이 치명적인 작업은 메서드 이름만 믿지 말고 서버가 멱등 키와 중복 방지 규칙을 제공하는지 확인해야 한다.

상태 코드는 첫 숫자와 구체적인 의미를 함께 본다

상태 코드의 범주는 다음처럼 나뉜다.

범위 의미 자주 확인할 코드
1xx 요청 처리 중의 중간 정보 100 Continue
2xx 요청을 성공적으로 처리함 200 OK, 201 Created, 204 No Content
3xx 다른 위치 또는 캐시된 표현을 사용하도록 안내함 301, 302, 304, 307, 308
4xx 요청 쪽에서 해결해야 할 조건이 있음 400, 401, 403, 404, 409, 429
5xx 서버가 유효한 요청을 처리하지 못함 500, 502, 503, 504

같은 범주라도 조치가 다르다. 401 Unauthorized는 보통 인증 자격 증명이 필요하다는 뜻이고, 403 Forbidden은 서버가 요청을 이해했지만 허용하지 않는다는 뜻이다. 502 Bad Gateway는 중간 서버가 상류 서버에서 유효한 응답을 받지 못한 경우이고, 504 Gateway Timeout은 상류 응답을 정해진 시간 안에 받지 못한 경우다.

리다이렉션도 Location만 보지 말아야 한다. 307308은 원래 메서드를 유지하도록 정의되어 있어, POST 요청의 동작을 추적할 때 301·302와 구분할 필요가 있다.

비지속 연결과 지속 연결의 차이

초기 HTTP 설명에서 자주 등장하는 non-persistent connection은 응답마다 TCP 연결을 닫는 모델이다. 이 방식은 연결 설정 비용이 반복되고, TLS까지 쓰면 암호화 협상 비용도 더해진다.

HTTP/1.1은 기본적으로 지속 연결을 사용하며 한 연결에서 여러 요청을 순차적으로 처리할 수 있다. 다만 응답 순서 제약 때문에 느린 요청이 뒤 요청을 막는 문제가 생길 수 있다.

  • HTTP/1.1: 연결을 재사용하지만 한 연결에서의 병렬 처리에는 제약이 크다.
  • HTTP/2: 한 연결 안에 여러 스트림을 다중화한다. TCP 패킷 손실은 그 연결의 스트림들에 영향을 줄 수 있다.
  • HTTP/3: QUIC 위에서 스트림을 다중화해 한 스트림의 손실 복구가 다른 스트림의 진행을 직접 막지 않도록 설계됐다.

HTTP/2나 HTTP/3라고 해서 요청 의미가 바뀌는 것은 아니다. 성능을 볼 때는 브라우저 개발자 도구의 Protocol 열, 연결 재사용 여부, 서버의 프로토콜 지원, 왕복 시간과 손실을 함께 확인한다.

curl로 요청과 응답을 분리해 보기

curl -v는 송수신 헤더와 연결 협상 과정을 함께 보여 준다.

curl -v https://example.com/

응답 헤더만 보고 싶다면 다음처럼 요청한다.

curl -sS -D - -o /dev/null https://example.com/

리다이렉션을 따라간 최종 결과만 보면 중간 응답을 놓치기 쉽다. 우선 따라가지 않은 상태에서 Location과 상태 코드를 확인하고, 필요할 때 -L을 붙인다.

curl -sS -D - -o /dev/null http://example.com/
curl -sS -L -D - -o /dev/null http://example.com/

조건부 요청은 ETag를 이용해 재현할 수 있다.

curl -i https://example.com/data.json
curl -i -H 'If-None-Match: "확인한-etag"' https://example.com/data.json

서버가 저장된 표현이 아직 유효하다고 판단하면 본문 대신 304 Not Modified를 돌려줄 수 있다. 쿠키와 캐시 헤더의 역할은 HTTP 쿠키와 캐시 차이에서 더 자세히 이어진다.

장애를 읽는 순서

  1. URL의 scheme, host, port, path가 의도와 맞는지 확인한다.
  2. DNS와 TCP·QUIC 연결이 성립했는지 분리한다.
  3. 실제로 전송된 메서드·요청 헤더·본문을 확인한다.
  4. 첫 응답의 상태 코드와 Location, WWW-Authenticate, Retry-After 같은 관련 헤더를 본다.
  5. 프록시나 게이트웨이가 있다면 어느 구간에서 상태 코드가 만들어졌는지 찾는다.
  6. 재시도 전에 메서드의 멱등성과 서버의 중복 방지 계약을 확인한다.

REST·GraphQL·gRPC처럼 HTTP 위에서 사용하는 API 방식의 선택은 REST·GraphQL·gRPC·WebSocket 차이, 지속적인 서버 이벤트 전달은 실시간 통신 방식 비교에서 별도로 다룬다.

참고 자료

반응형
KEEP READING
카테고리 전체 보기 →

댓글