Krong Dev.
HTTP

HTTP 메서드, 상태 코드, 그리고 Header와 Body

HTTP 통신의 기본 구성 요소인 메서드, 상태 코드, 헤더와 바디의 역할과 구조를 정리합니다.

HTTP 메서드, 상태 코드, 그리고 Header와 Body

Express로 API를 만들면서 자연스럽게 HTTP 스펙을 다시 들여다보게 됐습니다.

GET과 POST 말고 다른 메서드들은 실제로 어떻게 쓰이는 거지?

Header에 들어가는 정보가 이렇게 많은데, 역할 구분이 어떻게 되는 거지?

이 글에서는 HTTP 메서드 9종의 역할, 상태 코드 체계, 그리고 Header와 Body의 구조를 정리합니다.


HTTP 메서드

자주 쓰는 5가지

메서드역할멱등성
GET리소스 조회. 데이터를 가져올 때만 사용O
POST리소스 생성, 제출. 서버 상태를 변경할 수 있음X
PUT리소스 전체 교체. 없으면 생성O
PATCH리소스 부분 수정X
DELETE리소스 삭제O

여기서 핵심은 멱등성(Idempotency) 입니다.

PUT은 멱등합니다. 같은 데이터를 여러 번 보내도 결과가 동일합니다. 반면 PATCH는 멱등하지 않을 수 있습니다. 예를 들어 사용자의 주소를 수정하면 배송비 재계산 로직이 실행되는 식으로 side effect가 발생할 수 있기 때문입니다.

POST도 기본적으로 멱등하지 않지만, 실무에서 멱등성을 보장해야 하는 경우가 있습니다. 대표적으로 결제 중복 클릭 방지 가 있습니다.

PUT은 리소스 전체를 교체하고 PATCH부분만 수정합니다. 본문의 유형은 Content-Type 헤더로 나타냅니다. CORS 환경에서 PATCH를 사용하려면 서버가 Access-Control-Allow-Methods 헤더에 PATCH를 포함해야 합니다. 브라우저가 preflight 요청으로 “PATCH 써도 되나요?”라고 물어볼 때 서버가 허용해줘야 통신이 가능합니다.

엔티티(Entity)란?

HTTP 맥락에서 엔티티는 클라이언트가 서버로 보내는 실제 데이터 덩어리입니다. 회원가입 시 입력한 ID와 비밀번호, 게시글 내용 등이 바로 엔티티입니다.

같은 “엔티티”라는 용어가 다른 맥락에서 다르게 쓰입니다.

맥락의미
HTTP 엔티티요청, 응답에 실려 전송되는 실제 데이터
DB 엔티티테이블 구조로 정의된 객체. 사용자 입력값 + 시스템 값 포함
HTML 엔티티&처럼 &로 시작하고 ;로 끝나는 특수문자 표현

DB 엔티티에는 createdAt, id 같은 시스템 값 이 포함되지만, HTTP 엔티티(요청 바디)에는 담기지 않습니다.

덜 쓰이지만 알아야 하는 4가지

HEADGET과 동일하지만 응답 바디 없이 상태줄과 헤더만 돌려줍니다. 파일 크기 확인이나 리소스 존재 여부 체크처럼 바디가 필요 없는 경우에 사용합니다.

OPTIONS — 서버가 허용하는 통신 옵션을 확인합니다. 브라우저의 CORS preflight 요청이 바로 이 메서드입니다. Same-Origin Policy 를 벗어나는 요청을 보내기 전에, 브라우저가 먼저 OPTIONS로 “이 요청 보내도 되는지” 확인합니다.

CONNECT — 프록시 서버에 TCP 터널을 열어달라고 요청하는 메서드입니다. 홉바이홉(Hop-by-hop) 메서드로, 최종 목적지 서버까지 전달되지 않고 프록시 선에서 처리됩니다. HTTPS 사이트에 접속할 때 프록시가 암호화된 데이터를 들여다보지 못하도록 터널을 형성하는 데 쓰입니다.

클라이언트 → CONNECT 목적지:443 → 프록시
프록시 → 목적지 서버와 TCP 연결
연결 완료 → 프록시는 데이터를 그냥 통과(Proxying)
클라이언트 ↔ 암호화된 터널 ↔ 목적지 서버

TRACE — 요청이 서버에 도달하기까지 중간 프록시/게이트웨이가 요청을 어떻게 수정했는지 확인하는 루프백 테스트용 메서드입니다.
다만 보안 문제가 있습니다. 해커가 자바스크립트로 TRACE 요청을 보내면, 서버가 요청 헤더에 담긴 모든 정보를 응답 바디에 그대로 담아 보내버립니다. 이를 XST(Cross-Site Tracing) 공격이라 하며, HttpOnly 쿠키까지 탈취될 수 있어 대부분의 서버에서 비활성화합니다.


상태 코드

서버가 클라이언트에게 “요청을 어떻게 처리했는지” 알려주는 3자리 숫자입니다.

범위의미예시
1xx처리 중 (임시 응답)101 프로토콜 전환 (웹소켓)
2xx성공200 OK, 201 Created
3xx리다이렉션 필요301 영구 이동, 304 캐시 사용
4xx클라이언트 오류400 잘못된 요청, 401 인증 필요, 404 없음
5xx서버 오류500 내부 오류, 502 Bad Gateway

실무에서 자주 마주치는 코드를 정리하면:

  • 200 — 요청 성공. 가장 일반적인 응답
  • 201 — 리소스 생성 성공. POST 요청의 성공 응답으로 적합
  • 204 — 성공했지만 응답 바디 없음. DELETE 성공 시 사용
  • 301/302 — 영구/임시 리다이렉션
  • 304 — Not Modified. 캐시된 버전을 그대로 써도 된다는 의미
  • 400 — 요청 형식이 잘못됨
  • 401 — 인증이 필요함
  • 403 — 인증은 됐지만 권한이 없음
  • 404 — 리소스가 존재하지 않음
  • 500 — 서버 내부 오류

Header와 Body

Header의 역할

HTTP 헤더는 클라이언트와 서버가 요청, 응답에 부가적인 정보를 전송할 수 있도록 해줍니다. 헤더는 역할에 따라 4가지로 분류됩니다.

종류역할
일반 헤더요청, 응답 모두에 적용. 바디 데이터와 직접 관련 없음
요청 헤더클라이언트 정보, 인증, 선호 형식 등을 서버에 전달
응답 헤더서버 정보, 허용 메서드 등을 클라이언트에 전달
표현 헤더바디 데이터의 형식, 언어, 인코딩 등을 설명

표현 헤더와 응답 헤더는 헷갈리기 쉽습니다. 서버가 보낸 메시지에 들어있으면 무조건 ‘응답 헤더’라고 부르기 쉽지만, HTTP 표준에서는 역할에 따라 분류합니다. 응답 메시지 안에 있더라도 데이터(Content)를 설명하는 헤더는 ‘표현 헤더’입니다. — MDN : 응답 헤더

일반 헤더 (General Header)

Date: Mon, 18 Jul 2016 16:06:00 GMT
Connection: keep-alive
Cache-Control: no-cache, max-age=3600

Cache-Control 주요 지시자:

  • no-store — 캐시를 저장하지 않음
  • no-cache — 캐시를 쓰기 전 서버에 확인
  • must-revalidate — 만료된 캐시만 서버에 확인
  • public / private — 공유 캐시 허용 / 브라우저만 저장
  • max-age — 캐시 유효 시간(초)

요청 헤더 (Request Header)

Host: www.example.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; ...)
Accept: text/html, application/json
Accept-Encoding: gzip, deflate
Accept-Language: ko-KR, en-US
Cookie: session_id=abc123
Origin: https://www.example.com
Authorization: Bearer xxx.xxx.xxx

응답 헤더 (Response Header)

Server: nginx/1.10.2
Allow: GET, POST, PUT
Content-Disposition: attachment; filename="report.pdf"

Content-Disposition은 응답 본문을 브라우저가 어떻게 표시할지 지정합니다. inline이면 화면에 표시, attachment면 다운로드를 유도합니다.

표현 헤더 (Representation Header)

Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Language: ko-KR
Content-Length: 3495

예를 들어 서버가 index.htmlgzip으로 압축해서 보낼 때:

  • Content-Type: text/html — 원래 데이터의 정체
  • Content-Encoding: gzip — 현재 압축 상태

실제 응답 헤더는 이렇게 생겼습니다.

200 OK
Access-Control-Allow-Origin: *
Connection: Keep-Alive
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Mon, 18 Jul 2016 16:06:00 GMT
Etag: "c561c68d0ba92bbeb8b0f612a9199f722e3a621a"
Keep-Alive: timeout=5, max=997
Last-Modified: Mon, 18 Jul 2016 02:36:04 GMT
Server: Apache
Set-Cookie: mykey=myvalue; expires=Mon, 17-Jul-2017 16:06:00 GMT; Max-Age=31449600; Path=/; secure
Transfer-Encoding: chunked
x-frame-options: DENY

Content-Type과 MIME Sniffing

Content-Type 헤더는 리소스의 미디어 타입을 나타냅니다. 원래 MIME Type 이라 불렸으며, 파일의 형식을 나타내는 문자열로 content와 함께 전송됩니다.

문제는 MIME Sniffing 입니다. 일부 브라우저가 서버의 Content-Type을 무시하고 자체적으로 미디어 타입을 추측하는 동작입니다. 공격자가 악성 JavaScript를 이미지 파일로 속여 올리면, 서버는 Content-Type: image/png로 보내지만, 브라우저는 JavaScript로 인식해 실행해버릴 수 있습니다.

이를 막기 위해 X-Content-Type-Options: nosniff 헤더를 사용합니다. 브라우저에게 “서버가 지정한 Content-Type을 그대로 따르라”고 강제하는 보안 헤더입니다.

Body의 역할

Body는 서버와 클라이언트가 실제로 주고 받으려는 데이터 그 자체입니다. JSON, HTML, 이미지, 파일 등 다양한 형태가 될 수 있으며, 어떤 형태인지를 Content-Type 헤더로 알립니다.

콘텐츠 협상

클라이언트와 서버가 “어떤 형태의 데이터를 주고 받을지” 합의하는 과정을 콘텐츠 협상(Content Negotiation)이라 합니다.

  • 서버 주도 — 클라이언트가 Accept 헤더로 선호 형식을 알리면, 서버가 적절한 표현을 선택합니다. 빠르지만, 헤더가 많아지고 개인정보 노출 위험이 있습니다.
  • 에이전트 주도 — 서버가 선택지 목록을 보내고 클라이언트가 고릅니다. 깔끔하지만, 여러 번 왔다 갔다 해야 해서 느립니다.

마치며

HTTP 메서드, 상태 코드, Header와 Body는 결국 클라이언트와 서버가 어떻게 대화하는지를 정의하는 규약입니다. Express로 API를 만들 때 이 기본기를 알고 있으면, 라우트 설계부터 에러 핸들링까지 더 명확한 판단을 내릴 수 있습니다.

정리하면서 확실히 와닿은 점은:

  • 멱등성은 단순한 개념이 아니라, 결제 중복 방지처럼 실무 설계에 직접 영향을 주는 속성이라는 것
  • Header는 단순히 “부가 정보”가 아니라, 보안(X-Content-Type-Options), 캐싱(Cache-Control), 인증(Authorization) 등 서버 동작을 결정하는 핵심 요소라는 것

다음 글에서는 콘텐츠 협상과 에이전트 주도 협상에 대해 자세히 정리할 예정입니다.