포스트

조회는 GET, 변경은 POST — 메서드를 다시 잡다

오래된 화면에서 변경이 GET으로, 조회가 POST로 뒤섞인 걸 바로잡았다. GET의 안전성·멱등성·캐시 가능성과 변경에 GET을 쓸 때 터지는 사고를 정리한다.

조회는 GET, 변경은 POST — 메서드를 다시 잡다

그 주엔 오래된 화면 여러 곳의 HTTP 메서드를 작업 성격에 맞게 정정하는 리팩토링을 했다. 묵은 코드를 들여다보면 데이터를 바꾸는데 GET으로 호출하고, 단순 조회를 POST로 던지는 경우가 흔하다. 동작은 한다. 그래서 오래 방치된다. 하지만 HTTP 메서드는 단순한 관습이 아니라 의미를 가진 계약이고, 이걸 어기면 우리가 통제하지 않는 곳(브라우저·프록시·크롤러)이 그 계약을 믿고 사고를 친다.

안전성과 멱등성 — 메서드의 두 속성

HTTP 명세는 메서드에 두 가지 속성을 정의한다.

  • 안전(safe): 서버 상태를 바꾸지 않는다. GET, HEAD가 여기 속한다. “읽기만 한다”는 약속이다.
  • 멱등(idempotent): 같은 요청을 여러 번 보내도 결과가 한 번 보낸 것과 같다. GET, PUT, DELETE가 멱등이다. POST는 멱등이 아니다.
메서드안전멱등의미
GETOO조회
POSTXX생성·변경
PUTXO전체 교체
DELETEXO삭제

이 속성이 중요한 이유는, HTTP 인프라 전체가 이 약속을 신뢰하고 동작하기 때문이다. 브라우저는 GET을 마음대로 다시 보낸다. 프록시·CDN은 GET 응답을 캐시한다. 크롤러는 페이지의 모든 링크(GET)를 자동으로 따라간다. 브라우저의 프리페치 기능은 사용자가 클릭하기도 전에 링크를 미리 GET으로 당겨본다.

변경에 GET을 쓰면 벌어지는 일

/banner/delete?id=10 같은 링크를 만들었다고 하자. 동작 테스트는 통과한다. 그런데:

  • 검색 엔진 크롤러가 어드민 페이지를 긁다가 이 링크를 발견하면, “삭제”인 줄 모르고 그냥 따라간다. 페이지를 한 바퀴 도는 사이에 배너가 줄줄이 지워진다.
  • 브라우저 프리페치가 마우스 호버만으로 링크를 미리 당겨 데이터를 바꾼다.
  • 사용자가 뒤로가기→앞으로가기를 하면 브라우저가 GET을 다시 보내 같은 변경이 또 일어난다.

이건 “GET은 안전하다”는 약속을 어겼기 때문에 생기는 사고다. 변경은 반드시 POST(또는 PUT/DELETE)로 보내야 한다. 그래야 브라우저가 함부로 재전송하지 않고(“폼을 다시 제출하시겠습니까?” 경고), 크롤러·프리페치가 건드리지 않는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 잘못: 변경인데 GET — 크롤러·프리페치가 호출할 수 있다
@GetMapping("/banner/delete")
public String delete(@RequestParam Long id) { ... }

// 정정: 변경은 POST
@PostMapping("/banner/{id}/delete")
public ResponseEntity<Void> delete(@PathVariable Long id) {
    bannerService.delete(id);
    return ResponseEntity.noContent().build();
}

// 조회는 GET — 캐시·북마크·재요청이 모두 안전
@GetMapping("/banner/{id}")
public BannerView get(@PathVariable Long id) { ... }

조회를 POST로 두면 잃는 것

반대로 조회를 POST로 두면 사고는 안 나지만 이점을 버린다. GET이라야 응답을 캐시할 수 있고, URL을 북마크·공유할 수 있고, 실패 시 안전하게 재시도할 수 있다. POST 조회는 이걸 다 포기한다. 단, 검색 조건이 너무 길어 URL 한도를 넘거나 민감 정보가 쿼리스트링에 남으면 안 될 때는 조회라도 POST를 쓰는 예외가 있다.

운영 함정

메서드를 바꾸면 호출하는 쪽도 다 바꿔야 한다. GET을 POST로 바꾸는 순간, 그 엔드포인트를 호출하던 모든 링크·폼·자바스크립트·외부 연동이 깨진다. 메서드 정정은 단독 변경이 아니라 호출부 전체를 함께 고치는 회귀 작업이다. 호출 지점을 다 찾았는지가 핵심이고, 빠뜨리면 운영에서 404가 난다.

캐시 헤더와 메서드가 어긋난다. 변경 작업에 GET을 쓰던 시절 캐시가 끼었다면, 정정 후에도 중간 프록시에 옛 GET 응답이 남아 있을 수 있다. 메서드를 바꿀 땐 캐시 정책도 함께 점검한다.

핵심 요약

  • GET은 안전·멱등, POST는 둘 다 아니다. 이건 관습이 아니라 인프라가 신뢰하는 계약이다.
  • 변경에 GET을 쓰면 크롤러·프리페치·뒤로가기가 데이터를 멋대로 바꾼다.
  • 조회는 GET이라야 캐시·북마크·재시도 이점을 누린다.
  • 메서드 정정은 호출부 전체를 함께 바꾸는 회귀 작업이다.

면접 한 줄 Q&A Q. 변경 작업에 GET을 쓰면 왜 위험한가? A. GET은 “안전하다”고 약속된 메서드라 크롤러·프리페치·브라우저 재요청이 사용자 의도 없이 호출한다. 그 호출이 데이터를 바꾸면 통제 불가능한 변경 사고가 난다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.