1. 라이브러리
  2. HTTP와 웹
  3. HTTP 메서드

업데이트됨 1개월 전

GET은 질문이다. POST는 행동이다.

이것이 전부다. 링크를 클릭할 때, 브라우저는 "이 URL에 뭐가 있지?"라고 묻는다. 폼을 제출할 때, 브라우저는 "여기 데이터가 있어—이걸로 뭔가 해줘"라고 말한다. 모든 로그인, 모든 업로드, 모든 결제, 무언가를 생성하거나 변경하는 모든 API 호출: POST다.

POST가 하는 일

POST는 서버에 데이터를 전송하며, 일반적으로 상태 변경이나 부수 효과를 일으킨다. 서버는 데이터를 받아 무언가를 한다—사용자 계정을 생성하거나, 결제를 처리하거나, 파일을 저장하거나, 이메일을 보낸다.

일반적인 POST 작업:

  • 로그인 및 회원가입 폼 제출
  • 새 리소스 생성 (게시물, 주문, 계정)
  • 파일 업로드
  • API에 데이터 전송
  • 결제 처리

핵심 단어는 처리다. GET은 존재하는 것을 가져온다. POST는 무언가가 일어나게 만든다.

POST 요청 구조

POST 요청은 세 부분으로 구성된다:

요청 라인:

POST /api/users HTTP/1.1

헤더:

Host: example.com
Content-Type: application/json
Content-Length: 87

요청 본문:

{
  "username": "johndoe",
  "email": "john@example.com",
  "password": "securepass123"
}

본문은 POST가 데이터를 실어 보내는 곳이다. URL 쿼리 문자열에 파라미터를 붙여 보내는 GET과 달리, POST는 데이터를 본문에 담아 전송한다—더 큰 데이터를 보낼 수 있고, 민감한 정보가 URL, 로그, 브라우저 히스토리에 남지 않는다.

콘텐츠 타입

Content-Type 헤더는 서버에게 본문을 어떻게 파싱할지 알려준다.

application/x-www-form-urlencoded — HTML 폼의 기본값:

username=johndoe&email=john%40example.com&password=securepass123

multipart/form-data — 파일 업로드에 필요:

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="username"

johndoe
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="avatar"; filename="photo.jpg"
Content-Type: image/jpeg

[binary image data]
------WebKitFormBoundary7MA4YWxkTrZu0gW--

application/json — 현대 API의 표준:

{
  "username": "johndoe",
  "preferences": {
    "theme": "dark",
    "notifications": true
  }
}

POST vs. GET

항목GETPOST
목적데이터 조회변경을 일으키는 데이터 제출
데이터 위치URL 쿼리 문자열요청 본문
캐싱캐시 가능캐시되지 않음
북마크 가능 여부가능불가능
크기 제한~2,000-8,000자수 메가바이트 (서버에 따라 다름)
가시성URL, 로그, 히스토리에 파라미터 노출본문에만 데이터 존재
멱등성있음 (같은 요청 = 같은 결과)없음 (같은 요청이 다른 효과를 낼 수 있음)

마지막 행이 가장 중요하다. GET은 반복해도 안전하다—검색 페이지를 새로 고침해도 중복 검색이 생기지 않는다. POST는 반복하면 위험하다—결제 페이지를 새로 고침하면 카드가 두 번 청구될 수 있다.

POST를 사용해야 할 때

POST를 사용하세요:

  • 리소스를 생성, 수정, 또는 삭제할 때
  • 민감한 데이터를 다룰 때 (비밀번호, 결제 정보)
  • 대용량 데이터나 파일을 보낼 때
  • 실수로 반복되면 안 되는 작업

POST를 사용하지 마세요:

  • 단순히 데이터를 조회할 때 (GET을 사용하세요)
  • URL을 공유하거나 북마크하고 싶을 때
  • 작업을 반복해도 안전할 때

폼 제출

<form method="POST" action="/register">
  <input type="text" name="username" />
  <input type="email" name="email" />
  <input type="password" name="password" />
  <button type="submit">Register</button>
</form>

제출하면, 브라우저는 다음을 전송한다:

POST /register HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded

username=johndoe&email=john%40example.com&password=securepass123

파일 업로드의 경우, 폼 태그에 enctype="multipart/form-data"를 추가하세요.

API에서 POST

fetch('https://api.example.com/articles', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token123'
  },
  body: JSON.stringify({
    title: 'Understanding HTTP POST',
    content: 'POST is used to submit data...',
    tags: ['http', 'tutorial']
  })
});

성공적으로 생성되면 다음이 반환된다:

HTTP/1.1 201 Created
Location: /articles/12345

{
  "id": 12345,
  "title": "Understanding HTTP POST",
  "created_at": "2024-05-15T10:30:00Z"
}

201 Created 상태 코드는 리소스가 생성되었음을 확인한다. Location 헤더는 해당 리소스를 어디서 찾을 수 있는지 알려준다.

응답 코드

코드의미
200 OK요청 성공
201 Created새 리소스 생성됨
202 Accepted처리가 시작되었으나 완료되지 않음
204 No Content성공했으나 반환할 내용 없음
400 Bad Request유효하지 않은 데이터 제출
401 Unauthorized인증 필요
403 Forbidden허용되지 않음
409 Conflict기존 상태와 충돌
422 Unprocessable Entity유효성 검사 실패
500 Internal Server Error서버 내부 오류

중복 제출 문제

HTTP는 무상태(stateless)다. 각 요청은 처음인 것처럼 서버에 도착한다. 서버는 이전에 무슨 일이 있었는지 기억하지 못한다.

이것이 문제를 만든다: 사용자가 "제출"을 두 번 클릭하거나, 일시적인 네트워크 오류로 브라우저가 요청을 재시도한다. 그러면 갑자기 카드가 두 번 청구되거나 주문이 중복 생성된다.

해결책:

Post-Redirect-Get: 성공적인 POST 후 GET으로 리다이렉트:

POST /create-order → 303 See Other → GET /order/12345

이제 사용자가 새로 고침하면 위험한 POST가 아니라 안전한 GET을 새로 고침한다.

멱등성 키: 클라이언트가 각 제출에 고유 ID를 생성한다:

fetch('/api/orders', {
  method: 'POST',
  headers: { 'Idempotency-Key': 'unique-key-12345' },
  body: JSON.stringify(orderData)
});

서버는 같은 키를 가진 중복 요청을 무시한다.

CSRF 토큰: 폼에 일회용 토큰을 삽입한다. 각 제출은 해당 토큰을 무효화한다.

보안

HTTPS 사용: POST 데이터는 URL에 노출되지 않지만, HTTP를 통해서는 여전히 평문으로 전송된다. 연결을 암호화하세요.

CSRF 방어: 사이트 간 요청 위조(Cross-Site Request Forgery, CSRF)는 브라우저를 속여 승인되지 않은 POST를 보내게 만든다. CSRF 토큰, SameSite 쿠키, Origin/Referer 헤더 검증을 사용하세요.

서버 측 유효성 검사: 클라이언트 데이터를 절대 신뢰하지 마세요. 모든 것을 검증하세요.

속도 제한: POST 빈도를 제한하여 남용을 방지하세요.

본문 크기 제한: 대용량 페이로드로 인한 메모리 고갈을 방지하세요.

핵심 요약

  • POST는 서버 측 변경을 일으키는 데이터를 제출한다—질문이 아니라 행동이다
  • 데이터는 요청 본문에 담겨 전송되므로 URL, 로그, 브라우저 히스토리에 남지 않는다
  • POST는 멱등성이 없다: 같은 요청이 매번 다른 효과를 낼 수 있다
  • Post-Redirect-Get과 멱등성 키를 사용하여 중복 제출을 방지하라
  • 민감한 데이터를 처리하는 POST 엔드포인트에는 항상 HTTPS와 CSRF 보호를 사용하라

HTTP POST 메서드에 관한 자주 묻는 질문

POST 요청을 캐시할 수 있나요?

기본적으로는 불가능하다. POST 응답은 정적 리소스가 아닌 어떤 행동의 결과를 나타내기 때문에, 브라우저와 프록시는 이를 캐시하지 않는다. 명시적인 캐시 헤더를 추가하면 캐시 가능하게 만들 수 있지만, 이는 드문 경우이며 보통은 GET을 사용해야 한다는 신호다.

페이지를 새로 고침할 때 데이터 재전송 경고가 뜨는 이유는 뭔가요?

POST로 로드된 페이지를 새로 고침하면, 브라우저가 해당 POST 요청을 다시 보내야 한다. POST는 부수 효과를 일으킬 수 있기 때문에(예: 카드 이중 청구), 브라우저가 경고를 표시한다. 이것이 Post-Redirect-Get 패턴이 존재하는 이유다—히스토리에서 POST를 안전한 GET으로 대체한다.

POST가 GET보다 더 안전한가요?

POST는 데이터를 URL에서 숨기기 때문에, 브라우저 히스토리, 서버 로그, 북마크에 나타나지 않는다. 하지만 POST 데이터도 HTTP를 통해서는 평문으로 전송된다. 진정한 보안은 HTTPS에서 나온다—HTTPS는 메서드와 관계없이 요청 전체를 암호화한다.

POST 대신 PUT을 언제 사용해야 하나요?

POST는 최종 URL을 모를 때 새 리소스를 생성한다(POST /articles는 어딘가에 새 글을 생성한다). PUT은 알려진 URL에서 리소스를 교체한다(PUT /articles/123은 글 123을 교체한다). PUT은 멱등성이 있다—두 번 보내도 한 번 보낸 것과 효과가 같다. POST는 그렇지 않다.

이 페이지가 도움이 되었나요?

😔
🤨
😃