1. 시작하며
회사에서 진행 중인 프로젝트에서 프론트엔드와 백엔드 간 통신에서 예상치 못한 문제가 발생했습니다. API 설계 시 Spring Boot에서 GET 메서드에 @RequestBody
를 사용해 데이터 요청을 처리하도록 구성했는데, Postman을 이용한 테스트에서는 정상적으로 동작했지만, Axios를 사용하는 프론트엔드 클라이언트에서는 요청이 실패했습니다.
당시 Parameter 값이 많아 DTO를 record
로 정의한 뒤 @RequestBody
로 데이터를 전달받도록 설계했지만, 클라이언트와의 통신에서는 400 Bad Request
에러가 발생하며 정상적으로 동작하지 않았습니다. 이 글에서는 해당 문제를 해결하며 알게 된 GET 메서드와 Request Body의 관계, 그리고 RESTful API 설계에서의 올바른 사용법에 대해 다룹니다.
2. 문제점 발생
2.1 에러 상황
문제를 재현하기 위해 게시판 API를 설계했다고 가정합니다. 아래와 같이 게시물 목록 조회를 위한 API를 구현했습니다. 게시판 검색 기능에서 여러 검색 조건(제목, 작성자, 태그 등)을 받아야 하므로, @RequestBody
로 DTO를 전달받도록 설계했습니다.
게시판 게시물 목록 불러오기 API 예시 코드
@GetMapping("/board/posts")
public ResponseEntity<List<PostResponse>> getPosts(
@RequestParam(required = false) String category,
@RequestBody PostSearchRequest searchRequest) {
// Logic to retrieve posts...
}
category
: 게시물의 카테고리를 선택적으로 필터링searchRequest
: 제목, 작성자, 태그 등을 포함한 검색 조건
2.2 에러 발생
- Postman에서는 정상적으로 작동
- JSON 형식의 검색 조건을 Request Body에 담아 요청하면, 예상대로 게시물 목록이 반환되었습니다.
- 프론트엔드에서 Axios를 통해 Spring Boot API와 통신하는 과정에서 문제가 발생했습니다. 요청을 처리하던 중, Spring Boot 서버에서 다음과 같은 에러가 발생했습니다.
org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:157)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:130)
... (stack trace continues)
- 이는 Spring이 요청의
@RequestBody
부분을 읽지 못했기 때문에 발생한 예외로, GET 메서드와 Request Body의 조합을 허용하지 않는 Spring Boot의 기본 동작 때문입니다. - 브라우저 콘솔에서도 아래와 같은 에러 메시지가 출력되었습니다.
Failed to load resource: the server responded with a status of 400 (Bad Request)
이 에러는 Axios 요청 시 Request Body가 포함되지 않거나 브라우저가 해당 요청을 차단했기 때문에 발생한 것으로 추정됩니다.
2.3 원인 분석
1) Postman에서 정상 동작하는 이유
Postman은 비표준적인 HTTP 요청도 허용합니다. HTTP 표준에서 GET 메서드는 Request Body를 포함하지 않도록 설계되어 있지만, 일부 서버나 클라이언트 환경에서는 이를 허용할 수 있습니다. Postman은 이와 같은 비표준적인 동작을 지원하기 때문에, Request Body를 포함한 GET 요청을 성공적으로 처리할 수 있었습니다.
2) Axios에서 실패하는 이유
- HTTP 표준에 따르면 GET 메서드는 Request Body를 포함하지 않는 것이 원칙입니다.
- 브라우저 환경에서 동작하는 클라이언트(Axios, Fetch 등)는 이를 엄격하게 준수합니다. 따라서 GET 요청에 Body가 포함되면 요청을 거부하거나 Body를 제거한 상태로 전송합니다.
- 결과적으로, 서버는
@RequestBody
로 값을 받을 수 없으므로 400 Bad Request 에러가 발생했습니다.
3) Spring Boot의 기본 동작
Spring Boot는 기본적으로 GET 메서드와 @RequestBody
를 함께 사용하는 것을 허용하지 않습니다.
@RequestBody
는 POST, PUT과 같은 메서드에서 Request Body의 데이터를 바인딩하는 데 사용되며, GET 메서드에서는 Body를 처리하지 않으므로 HttpMessageNotReadableException
을 발생시킵니다.
3. 문제점 해결
3.1 HTTP 표준과 RESTful 설계 준수
GET 메서드와 Request Body를 함께 사용하는 것은 HTTP 표준에 맞지 않으며, 대부분의 클라이언트 환경에서 이를 지원하지 않습니다. 따라서 HTTP 표준에 따라 설계를 수정하는 것이 필요했습니다.
1) 쿼리 파라미터 사용
GET 메서드에서 데이터를 전달하려면 Request Body 대신 쿼리 파라미터(Query Parameters)를 사용해야 합니다.
수정된 API 코드
@GetMapping("/board/posts")
public ResponseEntity<List<PostDto>> getPosts(
@RequestParam(required = false) String category,
@RequestParam(required = false) String title,
@RequestParam(required = false) String author,
@RequestParam(required = false) List<String> tags) {
// Logic to retrieve posts...
}
- 쿼리 예시:
/board/posts?category=tech&title=Spring&author=John&tags=java,spring
- 장점
- HTTP 표준 준수: 모든 클라이언트와 호환 가능
- 단순한 검색 조건 전달에 적합
- 단점
- URL이 길어질 수 있으며, 쿼리 파라미터는 URL에 데이터가 노출되므로 민감한 정보를 포함해서는 안 됩니다.
2) PathVariable 활용
특정 리소스를 식별하거나 검색 조건이 간단한 경우, PathVariable을 활용하는 것도 좋은 방법입니다.
수정된 API 코드
@GetMapping("/board/posts/{category}/{author}")
public ResponseEntity<List<PostResponse>> getPostsByCategoryAndAuthor(
@PathVariable String category,
@PathVariable String author) {
// Logic to retrieve posts...
}
- 경로 예시:
/board/posts/tech/John
- 장점
- URL이 깔끔하며, 특정 리소스를 명확히 식별 가능
- 쿼리 파라미터를 사용하지 않으므로 URL 길이를 줄일 수 있음
- 단점
- 검색 조건이 많아질 경우 PathVariable로 모든 조건을 표현하기는 어렵습니다.
4. 결론
GET 메서드와 Request Body를 함께 사용하는 것은 HTTP 표준에 어긋나며, 대부분의 클라이언트(Axios, Fetch 등)에서 이를 지원하지 않습니다. Spring Boot 역시 기본적으로 GET 메서드와 @RequestBody
를 함께 사용하는 것을 허용하지 않습니다.
따라서 RESTful 설계를 준수하기 위해서는
- 간단한 조건 전달은 쿼리 파라미터를 활용
- 리소스를 명확히 식별할 때는 PathVariable을 사용
위와 같은 방법으로 설계를 변경하는 것이 바람직합니다. 이번 문제 해결 과정을 통해 HTTP 표준 준수와 RESTful 설계의 중요성을 다시 한 번 깨달을 수 있었습니다.
5. 참고
'트러블 슈팅' 카테고리의 다른 글
[트러블 슈팅] Jenkins Publish over SSH: BapPublisherException 오류 해결하기 (2) | 2025.01.05 |
---|---|
[트러블 슈팅] SSH 접속 오류: 포트 설정과 포트포워딩의 중요 (1) | 2025.01.05 |