HTTP 1.1 이후로, GET에도 Body에 데이터를 실을 수 있게 되었습니다. 그럼에도 불구하고 왜 아직도 이런 방식을 지양하는 것일까요?

HTTP 1.1 스펙상 GET 요청에 Body를 포함하는 것이 금지되지는 않지만, 대부분의 인프라와 라이브러리가 GET Body를 무시하거나 제거하기 때문에 사실상 신뢰할 수 없는 방식입니다.

HTTP 스펙에서의 GET Body

[RFC 7231 (HTTP/1.1)]
"A payload within a GET request message has no defined semantics"

→ GET 요청에 Body를 보내는 것 자체는 금지하지 않음
→ 하지만 Body에 "정의된 의미가 없다"고 명시
→ 서버가 Body를 어떻게 처리할지 보장하지 않음

[RFC 9110 (2022, 최신)]
"Although request message framing is independent of the method used,
 content received in a GET request has no generally defined semantics"

→ 여전히 GET Body에 대한 의미를 정의하지 않음

지양하는 이유

1. 중간 인프라가 GET Body를 제거하거나 무시

클라이언트 → 프록시 → CDN → 로드밸런서 → 서버

GET /search HTTP/1.1
Content-Type: application/json

{"query": "홍길동", "filters": {"age": 25}}

프록시: GET에 Body? → Body 제거 후 전달 X
CDN: GET 요청 캐싱 → Body 무시하고 URL만으로 캐시 키 생성 X
로드밸런서: Body 없는 GET으로 전달 X

→ 서버에 도달할 때 Body가 사라져 있을 수 있음
인프라GET Body 처리
Nginx기본적으로 전달하지만 proxy 설정에 따라 제거될 수 있음
AWS ALB/CloudFrontGET Body를 제거할 수 있음
CDN (Cloudflare 등)캐시 키에 Body를 포함하지 않음 → 무시
일부 프록시/방화벽GET Body를 비정상 요청으로 판단하여 차단

2. 클라이언트 라이브러리가 지원하지 않거나 제한

// JavaScript fetch — GET Body 지원 안 함
fetch("/search", {
    method: "GET",
    body: JSON.stringify({query: "홍길동"})  // 일부 브라우저에서 에러 X
});

// XMLHttpRequest — 스펙상 GET Body 전송 가능하나 브라우저마다 다름
// Java HttpURLConnection — GET Body 지원하지 않음
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setDoOutput(true);  // GET에서는 예외 발생할 수 있음 X

// RestTemplate — GET Body 기본 미지원
restTemplate.exchange(
    url, HttpMethod.GET,
    new HttpEntity<>(body, headers),  // Body가 무시될 수 있음
    String.class
);
[다양한 클라이언트의 GET Body 지원 현황]
브라우저 fetch API:  X 미지원 (에러 또는 무시)
axios:              X GET 요청 시 data 무시
OkHttp:             X GET에 Body 설정 시 예외
curl:               O -d 옵션으로 가능 (비표준 사용)
Postman:            O 가능하지만 경고 표시

3. 캐싱이 불가능

[GET의 가장 큰 장점 — 캐싱]
GET /members?name=홍길동&age=25
→ URL이 캐시 키 → 같은 URL이면 캐시된 응답 반환

[GET Body 사용 시 — 캐싱 불가]
GET /members
Body: {"name": "홍길동", "age": 25}

→ 브라우저, CDN, 프록시 모두 URL만으로 캐시 키 생성
→ Body가 다르더라도 URL이 같으면 같은 캐시 반환
→ 잘못된 결과 반환 X
GET /search (Body: {"query": "사과"}) → 결과A 캐싱
GET /search (Body: {"query": "바나나"}) → URL 동일 → 결과A 반환 X

→ Body를 무시하고 URL만으로 캐싱하므로
→ 다른 검색어임에도 같은 결과를 반환하는 문제 발생

4. 브라우저 히스토리, 북마크, 링크 공유 불가

[쿼리스트링 사용 시]
GET /search?query=홍길동&age=25
→ URL 복사하여 공유 가능 O
→ 브라우저 북마크 가능 O
→ 뒤로가기 시 동일 결과 O

[GET Body 사용 시]
GET /search (Body: {"query": "홍길동", "age": 25})
→ URL만으로는 검색 조건을 알 수 없음
→ 링크 공유 불가 X
→ 북마크 불가 X
→ 뒤로가기 시 Body가 없어 동일 결과 보장 불가 X

그럼 복잡한 검색 조건은 어떻게 전달하나

방법 1: 쿼리스트링으로 전달 (기본)

GET /members?name=홍길동&age=25&sort=name,asc&page=0&size=20 HTTP/1.1
@GetMapping("/members")
public ResponseEntity<Page<MemberResponse>> search(
        @RequestParam String name,
        @RequestParam int age,
        @PageableDefault(sort = "name") Pageable pageable) {
    return ResponseEntity.ok(memberService.search(name, age, pageable));
}

방법 2: POST를 사용 (검색 조건이 매우 복잡한 경우)

POST /members/search HTTP/1.1
Content-Type: application/json

{
    "name": "홍길동",
    "ageRange": {"min": 20, "max": 30},
    "addresses": ["서울", "경기"],
    "skills": ["Java", "Spring"],
    "joinDateRange": {"from": "2024-01-01", "to": "2025-12-31"}
}
@PostMapping("/members/search")
public ResponseEntity<Page<MemberResponse>> complexSearch(
        @RequestBody ComplexSearchRequest request,
        @PageableDefault Pageable pageable) {
    return ResponseEntity.ok(memberService.complexSearch(request, pageable));
}

방법 3: Elasticsearch 스타일

// Elasticsearch는 GET Body를 허용하는 대표적인 사례
// 자체 HTTP 클라이언트를 사용하여 인프라 제약을 우회

GET /index/_search
{
    "query": {
        "bool": {
            "must": [{"match": {"name": "홍길동"}}]
        }
    }
}

// 하지만 Elasticsearch도 POST를 대안으로 허용
POST /index/_search  ← 동일 동작

판단 기준

상황권장 방식
단순 조회 (파라미터 소수)GET + 쿼리스트링
복잡한 검색 (중첩 객체, 배열 등)POST + Body
URL 길이 제한 초과 (약 2,048자)POST + Body
검색 조건에 민감 정보 포함POST + Body
캐싱/북마크/공유가 필요GET + 쿼리스트링

면접 포인트

CSLOG에서 보기